From 899e88f3d7b15981803dc99e4523e8d15a76bc2e Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Mon, 13 Jan 2025 15:28:50 +0000 Subject: [PATCH 01/15] WIP: Setup base users count chart --- site/src/api/api.ts | 13 +++ site/src/api/queries/insights.ts | 7 ++ .../UsersCountChart.stories.tsx | 41 +++++++ .../GeneralSettingsPage/UsersCountChart.tsx | 101 ++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.stories.tsx create mode 100644 site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.tsx diff --git a/site/src/api/api.ts b/site/src/api/api.ts index f3a91f176ab88..ac4ef4a1ca340 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -2089,6 +2089,19 @@ class ApiMethods { return response.data; }; + getInsightsUserStatusCounts = async ( + offset = Math.trunc(new Date().getTimezoneOffset() / 60), + ): Promise => { + const searchParams = new URLSearchParams({ + tz_offset: offset.toString(), + }); + const response = await this.axios.get( + `/api/v2/insights/user-status-counts?${searchParams}`, + ); + + return response.data; + }; + getInsightsTemplate = async ( params: InsightsTemplateParams, ): Promise => { diff --git a/site/src/api/queries/insights.ts b/site/src/api/queries/insights.ts index a7044a2f2469f..19957523e9548 100644 --- a/site/src/api/queries/insights.ts +++ b/site/src/api/queries/insights.ts @@ -20,3 +20,10 @@ export const insightsUserActivity = (params: InsightsParams) => { queryFn: () => API.getInsightsUserActivity(params), }; }; + +export const insightsUserStatusCounts = () => { + return { + queryKey: ["insights", "userStatusCounts"], + queryFn: () => API.getInsightsUserStatusCounts(), + }; +}; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.stories.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.stories.tsx new file mode 100644 index 0000000000000..8734f2b4f5613 --- /dev/null +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.stories.tsx @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { UsersCountChart } from "./UsersCountChart"; + +const meta: Meta = { + title: "pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart", + component: UsersCountChart, + args: { + active: [ + { date: "1/1/2024", amount: 150 }, + { date: "1/2/2024", amount: 165 }, + { date: "1/3/2024", amount: 180 }, + { date: "1/4/2024", amount: 155 }, + { date: "1/5/2024", amount: 190 }, + { date: "1/6/2024", amount: 200 }, + { date: "1/7/2024", amount: 210 }, + ], + dormant: [ + { date: "1/1/2024", amount: 80 }, + { date: "1/2/2024", amount: 82 }, + { date: "1/3/2024", amount: 85 }, + { date: "1/4/2024", amount: 88 }, + { date: "1/5/2024", amount: 90 }, + { date: "1/6/2024", amount: 92 }, + { date: "1/7/2024", amount: 95 }, + ], + suspended: [ + { date: "1/1/2024", amount: 20 }, + { date: "1/2/2024", amount: 22 }, + { date: "1/3/2024", amount: 25 }, + { date: "1/4/2024", amount: 23 }, + { date: "1/5/2024", amount: 28 }, + { date: "1/6/2024", amount: 30 }, + { date: "1/7/2024", amount: 32 }, + ], + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.tsx new file mode 100644 index 0000000000000..f6af9557b9842 --- /dev/null +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.tsx @@ -0,0 +1,101 @@ +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "components/Collapsible/Collapsible"; +import type { FC } from "react"; + +const chartConfig = { + desktop: { + label: "Desktop", + color: "hsl(var(--chart-1))", + }, + mobile: { + label: "Mobile", + color: "hsl(var(--chart-2))", + }, +} satisfies ChartConfig; + +type Data = { + date: string; + amount: number; +}; + +export type UsersCountChartProps = { + active: Data[]; + dormant: Data[]; + suspended: Data[]; +}; + +export const UsersCountChart: FC = () => { + return ( +
+
+ +

User Engagement

+ + + + +

+ We consider a user “engaged” if they initiate a connection to + their workspace. The connection can be made through apps, web + terminal or SSH. +

+

+ The graph shows the number of unique users who were engaged at + least once during the day. +

+

You might also check:

+
    +
  • Activity Audit
  • +
  • License Consumption
  • +
+
+
+
+ +
+ + + + value.slice(0, 3)} + /> + } + /> + + + + +
+
+ ); +}; From 034c35919abb76dff557ad5d20fbae87321bf8d1 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 14 Jan 2025 14:52:45 +0000 Subject: [PATCH 02/15] Implement User Engagement component --- site/src/components/Chart/Chart.tsx | 4 +- .../UserEngagementChart.stories.tsx | 23 +++ .../UserEngagementChart.tsx | 158 ++++++++++++++++++ .../UsersCountChart.stories.tsx | 41 ----- .../GeneralSettingsPage/UsersCountChart.tsx | 101 ----------- site/tailwind.config.js | 1 + 6 files changed, 185 insertions(+), 143 deletions(-) create mode 100644 site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.stories.tsx create mode 100644 site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx delete mode 100644 site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.stories.tsx delete mode 100644 site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.tsx diff --git a/site/src/components/Chart/Chart.tsx b/site/src/components/Chart/Chart.tsx index ba5ff7e7ed43d..d8435c33337f8 100644 --- a/site/src/components/Chart/Chart.tsx +++ b/site/src/components/Chart/Chart.tsx @@ -66,6 +66,8 @@ export const ChartContainer = React.forwardRef< "[&_.recharts-sector[stroke='#fff']]:stroke-transparent", "[&_.recharts-sector]:outline-none", "[&_.recharts-surface]:outline-none", + "[&_.recharts-text]:fill-content-secondary [&_.recharts-text]:font-medium", + "[&_.recharts-cartesian-axis-line]:stroke-[hsl(var(--border-default))]", className, )} {...props} @@ -195,7 +197,7 @@ export const ChartTooltipContent = React.forwardRef<
diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.stories.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.stories.tsx new file mode 100644 index 0000000000000..154bf921908d3 --- /dev/null +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.stories.tsx @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { UserEngagementChart } from "./UserEngagementChart"; + +const meta: Meta = { + title: "pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart", + component: UserEngagementChart, + args: { + data: [ + { date: "1/1/2024", users: 150 }, + { date: "1/2/2024", users: 165 }, + { date: "1/3/2024", users: 180 }, + { date: "1/4/2024", users: 155 }, + { date: "1/5/2024", users: 190 }, + { date: "1/6/2024", users: 200 }, + { date: "1/7/2024", users: 210 }, + ], + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx new file mode 100644 index 0000000000000..3655062fcc9e5 --- /dev/null +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx @@ -0,0 +1,158 @@ +import { Button } from "components/Button/Button"; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig, +} from "components/Chart/Chart"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "components/Collapsible/Collapsible"; +import { ChevronDownIcon, ExternalLinkIcon } from "lucide-react"; +import type { FC } from "react"; +import { Link } from "react-router-dom"; +import { CartesianGrid, XAxis, Area, AreaChart, YAxis } from "recharts"; + +const chartConfig = { + users: { + label: "Users", + color: "hsl(var(--chart-1))", + }, +} satisfies ChartConfig; + +export type UserEngagementChartProps = { + data: { + date: string; + users: number; + }[]; +}; + +export const UserEngagementChart: FC = ({ data }) => { + return ( +
+
+ +
+

User Engagement

+ + + + +
+ + +

+ We consider a user “engaged” if they initiate a connection to + their workspace. The connection can be made through apps, web + terminal or SSH. +

+

+ The graph shows the number of unique users who were engaged at + least once during the day. +

+
+

You might also check:

+
    +
  • + + Activity Audit + + +
  • +
  • + + License Consumption + + +
  • +
+
+
+
+
+ +
+ + + + + new Date(value).toLocaleDateString(undefined, { + month: "short", + day: "numeric", + }) + } + /> + { + return value === 0 ? "" : value.toLocaleString(); + }} + /> + { + const item = p[0]; + return `${item.value} users`; + }} + formatter={(v, n, item) => { + const date = new Date(item.payload.date); + return date.toLocaleString(undefined, { + month: "long", + day: "2-digit", + }); + }} + /> + } + /> + + + + + + + + + + +
+
+ ); +}; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.stories.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.stories.tsx deleted file mode 100644 index 8734f2b4f5613..0000000000000 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.stories.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { UsersCountChart } from "./UsersCountChart"; - -const meta: Meta = { - title: "pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart", - component: UsersCountChart, - args: { - active: [ - { date: "1/1/2024", amount: 150 }, - { date: "1/2/2024", amount: 165 }, - { date: "1/3/2024", amount: 180 }, - { date: "1/4/2024", amount: 155 }, - { date: "1/5/2024", amount: 190 }, - { date: "1/6/2024", amount: 200 }, - { date: "1/7/2024", amount: 210 }, - ], - dormant: [ - { date: "1/1/2024", amount: 80 }, - { date: "1/2/2024", amount: 82 }, - { date: "1/3/2024", amount: 85 }, - { date: "1/4/2024", amount: 88 }, - { date: "1/5/2024", amount: 90 }, - { date: "1/6/2024", amount: 92 }, - { date: "1/7/2024", amount: 95 }, - ], - suspended: [ - { date: "1/1/2024", amount: 20 }, - { date: "1/2/2024", amount: 22 }, - { date: "1/3/2024", amount: 25 }, - { date: "1/4/2024", amount: 23 }, - { date: "1/5/2024", amount: 28 }, - { date: "1/6/2024", amount: 30 }, - { date: "1/7/2024", amount: 32 }, - ], - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.tsx deleted file mode 100644 index f6af9557b9842..0000000000000 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UsersCountChart.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { - Collapsible, - CollapsibleContent, - CollapsibleTrigger, -} from "components/Collapsible/Collapsible"; -import type { FC } from "react"; - -const chartConfig = { - desktop: { - label: "Desktop", - color: "hsl(var(--chart-1))", - }, - mobile: { - label: "Mobile", - color: "hsl(var(--chart-2))", - }, -} satisfies ChartConfig; - -type Data = { - date: string; - amount: number; -}; - -export type UsersCountChartProps = { - active: Data[]; - dormant: Data[]; - suspended: Data[]; -}; - -export const UsersCountChart: FC = () => { - return ( -
-
- -

User Engagement

- - - - -

- We consider a user “engaged” if they initiate a connection to - their workspace. The connection can be made through apps, web - terminal or SSH. -

-

- The graph shows the number of unique users who were engaged at - least once during the day. -

-

You might also check:

-
    -
  • Activity Audit
  • -
  • License Consumption
  • -
-
-
-
- -
- - - - value.slice(0, 3)} - /> - } - /> - - - - -
-
- ); -}; diff --git a/site/tailwind.config.js b/site/tailwind.config.js index 20615db8dd6f6..209a38b3a5695 100644 --- a/site/tailwind.config.js +++ b/site/tailwind.config.js @@ -16,6 +16,7 @@ module.exports = { fontSize: { "2xs": ["0.625rem", "0.875rem"], sm: ["0.875rem", "1.5rem"], + md: ["1rem", "1.5rem"], "3xl": ["2rem", "2.5rem"], }, borderRadius: { From 8eba4d4a5eebbc7bdcc963b4db3889034c891742 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 14 Jan 2025 17:10:12 +0000 Subject: [PATCH 03/15] Integrate chart with API data --- site/src/api/queries/insights.ts | 9 +- .../GeneralSettingsPage.tsx | 48 ++++- .../GeneralSettingsPageView.stories.tsx | 17 +- .../GeneralSettingsPageView.tsx | 29 +-- .../UserEngagementChart.stories.tsx | 14 +- .../UserEngagementChart.tsx | 175 ++++++++++-------- 6 files changed, 175 insertions(+), 117 deletions(-) diff --git a/site/src/api/queries/insights.ts b/site/src/api/queries/insights.ts index 19957523e9548..afdf9f7efedd0 100644 --- a/site/src/api/queries/insights.ts +++ b/site/src/api/queries/insights.ts @@ -1,4 +1,6 @@ import { API, type InsightsParams, type InsightsTemplateParams } from "api/api"; +import type { GetUserStatusCountsResponse } from "api/typesGenerated"; +import { type UseQueryOptions, UseQueryResult } from "react-query"; export const insightsTemplate = (params: InsightsTemplateParams) => { return { @@ -25,5 +27,10 @@ export const insightsUserStatusCounts = () => { return { queryKey: ["insights", "userStatusCounts"], queryFn: () => API.getInsightsUserStatusCounts(), - }; + select: (data) => data.status_counts, + } satisfies UseQueryOptions< + GetUserStatusCountsResponse, + unknown, + GetUserStatusCountsResponse["status_counts"] + >; }; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx index 2b094cbf89b26..8136ff00c2979 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx @@ -8,10 +8,12 @@ import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; import { pageTitle } from "utils/page"; import { GeneralSettingsPageView } from "./GeneralSettingsPageView"; +import { insightsUserStatusCounts } from "api/queries/insights"; +import type { UserStatusChangeCount } from "api/typesGenerated"; +import { eachDayOfInterval, isSameDay } from "date-fns"; const GeneralSettingsPage: FC = () => { const { deploymentConfig } = useDeploymentSettings(); - const deploymentDAUsQuery = useQuery(deploymentDAUs()); const safeExperimentsQuery = useQuery(availableExperiments()); const { metadata } = useEmbeddedMetadata(); @@ -24,6 +26,8 @@ const GeneralSettingsPage: FC = () => { return !safeExperiments.includes(exp); }) ?? []; + const { data: userStatusCount } = useQuery(insightsUserStatusCounts()); + return ( <> @@ -31,8 +35,7 @@ const GeneralSettingsPage: FC = () => { { ); }; +// TODO: Remove this function once the API returns values sorted by date and +// includes all dates within the specified range. The +// `/api/v2/insights/user-status-counts` endpoint does not return the +// `UserStatusChangeCount[]` items sorted by date, nor does it backfill missing +// dates within the specified range. +function normalizeStatusCount( + statusCount: UserStatusChangeCount[] | undefined, +) { + if (!statusCount) { + return undefined; + } + + const sortedCounts = statusCount.toSorted((a, b) => { + return new Date(a.date).getTime() - new Date(b.date).getTime(); + }); + + const dates = eachDayOfInterval({ + start: new Date(sortedCounts[0].date), + end: new Date(sortedCounts[sortedCounts.length - 1].date), + }); + + const backFilledCounts: UserStatusChangeCount[] = []; + dates.forEach((date, i) => { + const existingCount = sortedCounts.find((c) => + isSameDay(date, new Date(c.date)), + ); + if (existingCount) { + backFilledCounts.push(existingCount); + } else { + backFilledCounts.push({ + date: date.toISOString(), + count: backFilledCounts[i - 1].count, + }); + } + }); + + return backFilledCounts; +} + export default GeneralSettingsPage; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx index 05ed426d5dcc9..a31a33292e911 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx @@ -39,7 +39,7 @@ const meta: Meta = { hidden: false, }, ], - deploymentDAUs: MockDeploymentDAUResponse, + activeUsersCount: [], invalidExperiments: [], safeExperiments: [], entitlements: undefined, @@ -51,21 +51,6 @@ type Story = StoryObj; export const Page: Story = {}; -export const NoDAUs: Story = { - args: { - deploymentDAUs: undefined, - }, -}; - -export const DAUError: Story = { - args: { - deploymentDAUs: undefined, - deploymentDAUsError: mockApiError({ - message: "Error fetching DAUs.", - }), - }, -}; - export const allExperimentsEnabled: Story = { args: { deploymentOptions: [ diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index df5550d70e965..d21debc5bf062 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -1,15 +1,11 @@ import AlertTitle from "@mui/material/AlertTitle"; import LinearProgress from "@mui/material/LinearProgress"; import type { - DAUsResponse, Entitlements, Experiments, SerpentOption, + UserStatusChangeCount, } from "api/typesGenerated"; -import { - ActiveUserChart, - ActiveUsersTitle, -} from "components/ActiveUserChart/ActiveUserChart"; import { ErrorAlert } from "components/Alert/ErrorAlert"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; import { Stack } from "components/Stack/Stack"; @@ -18,12 +14,12 @@ import { useDeploymentOptions } from "utils/deployOptions"; import { docs } from "utils/docs"; import { Alert } from "../../../components/Alert/Alert"; import OptionsTable from "../OptionsTable"; +import { UserEngagementChart } from "./UserEngagementChart"; import { ChartSection } from "./ChartSection"; export type GeneralSettingsPageViewProps = { deploymentOptions: SerpentOption[]; - deploymentDAUs?: DAUsResponse; - deploymentDAUsError: unknown; + activeUsersCount: UserStatusChangeCount[] | undefined; entitlements: Entitlements | undefined; readonly invalidExperiments: Experiments | string[]; readonly safeExperiments: Experiments | string[]; @@ -31,8 +27,7 @@ export type GeneralSettingsPageViewProps = { export const GeneralSettingsPageView: FC = ({ deploymentOptions, - deploymentDAUs, - deploymentDAUsError, + activeUsersCount, entitlements, safeExperiments, invalidExperiments, @@ -51,16 +46,12 @@ export const GeneralSettingsPageView: FC = ({ docsHref={docs("/admin/setup")} /> - {Boolean(deploymentDAUsError) && ( - - )} - {deploymentDAUs && ( -
- }> - - -
- )} + ({ + date: i.date, + users: i.count, + }))} + /> {licenseUtilizationPercentage && ( = { export default meta; type Story = StoryObj; -export const Default: Story = {}; +export const Loaded: Story = {}; + +export const Empty: Story = { + args: { + data: [], + }, +}; + +export const Loading: Story = { + args: { + data: undefined, + }, +}; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx index 3655062fcc9e5..6b01470770a99 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx @@ -10,6 +10,7 @@ import { CollapsibleContent, CollapsibleTrigger, } from "components/Collapsible/Collapsible"; +import { Spinner } from "components/Spinner/Spinner"; import { ChevronDownIcon, ExternalLinkIcon } from "lucide-react"; import type { FC } from "react"; import { Link } from "react-router-dom"; @@ -23,10 +24,12 @@ const chartConfig = { } satisfies ChartConfig; export type UserEngagementChartProps = { - data: { - date: string; - users: number; - }[]; + data: + | { + date: string; + users: number; + }[] + | undefined; }; export const UserEngagementChart: FC = ({ data }) => { @@ -77,81 +80,99 @@ export const UserEngagementChart: FC = ({ data }) => {
- - - - - new Date(value).toLocaleDateString(undefined, { - month: "short", - day: "numeric", - }) - } - /> - { - return value === 0 ? "" : value.toLocaleString(); - }} - /> - { - const item = p[0]; - return `${item.value} users`; +
+ {data ? ( + data.length > 0 ? ( + + { - const date = new Date(item.payload.date); - return date.toLocaleString(undefined, { - month: "long", - day: "2-digit", - }); - }} - /> - } - /> - - - - - - + > + + + new Date(value).toLocaleDateString(undefined, { + month: "short", + day: "numeric", + }) + } + /> + { + return value === 0 ? "" : value.toLocaleString(); + }} + /> + { + const item = p[0]; + return `${item.value} users`; + }} + formatter={(v, n, item) => { + const date = new Date(item.payload.date); + return date.toLocaleString(undefined, { + month: "long", + day: "2-digit", + }); + }} + /> + } + /> + + + + + + - - - + + + + ) : ( +
+ No data available +
+ ) + ) : ( +
+ +
+ )} +
); From 223859019e8901d983faa3105b47b0ec6af064c5 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 14 Jan 2025 17:14:50 +0000 Subject: [PATCH 04/15] Run lint and format --- .../GeneralSettingsPage/GeneralSettingsPage.tsx | 6 +++--- .../GeneralSettingsPage/GeneralSettingsPageView.tsx | 2 +- .../GeneralSettingsPage/UserEngagementChart.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx index 8136ff00c2979..b556dab6d6919 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx @@ -1,6 +1,9 @@ import { deploymentDAUs } from "api/queries/deployment"; import { entitlements } from "api/queries/entitlements"; import { availableExperiments, experiments } from "api/queries/experiments"; +import { insightsUserStatusCounts } from "api/queries/insights"; +import type { UserStatusChangeCount } from "api/typesGenerated"; +import { eachDayOfInterval, isSameDay } from "date-fns"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import type { FC } from "react"; @@ -8,9 +11,6 @@ import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; import { pageTitle } from "utils/page"; import { GeneralSettingsPageView } from "./GeneralSettingsPageView"; -import { insightsUserStatusCounts } from "api/queries/insights"; -import type { UserStatusChangeCount } from "api/typesGenerated"; -import { eachDayOfInterval, isSameDay } from "date-fns"; const GeneralSettingsPage: FC = () => { const { deploymentConfig } = useDeploymentSettings(); diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index d21debc5bf062..577c9a7f52f61 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -14,8 +14,8 @@ import { useDeploymentOptions } from "utils/deployOptions"; import { docs } from "utils/docs"; import { Alert } from "../../../components/Alert/Alert"; import OptionsTable from "../OptionsTable"; -import { UserEngagementChart } from "./UserEngagementChart"; import { ChartSection } from "./ChartSection"; +import { UserEngagementChart } from "./UserEngagementChart"; export type GeneralSettingsPageViewProps = { deploymentOptions: SerpentOption[]; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx index 6b01470770a99..b3e03978d05b4 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx @@ -1,9 +1,9 @@ import { Button } from "components/Button/Button"; import { + type ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent, - type ChartConfig, } from "components/Chart/Chart"; import { Collapsible, @@ -14,7 +14,7 @@ import { Spinner } from "components/Spinner/Spinner"; import { ChevronDownIcon, ExternalLinkIcon } from "lucide-react"; import type { FC } from "react"; import { Link } from "react-router-dom"; -import { CartesianGrid, XAxis, Area, AreaChart, YAxis } from "recharts"; +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; const chartConfig = { users: { From 8373732fbfde3e1d939c0a5096d0a6d35882b216 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Tue, 14 Jan 2025 17:33:25 +0000 Subject: [PATCH 05/15] Update story to show active data --- .../GeneralSettingsPageView.stories.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx index a31a33292e911..e51a361910aeb 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx @@ -39,7 +39,15 @@ const meta: Meta = { hidden: false, }, ], - activeUsersCount: [], + activeUsersCount: [ + { date: "1/1/2024", count: 150 }, + { date: "1/2/2024", count: 165 }, + { date: "1/3/2024", count: 180 }, + { date: "1/4/2024", count: 155 }, + { date: "1/5/2024", count: 190 }, + { date: "1/6/2024", count: 200 }, + { date: "1/7/2024", count: 210 }, + ], invalidExperiments: [], safeExperiments: [], entitlements: undefined, From 090230d6e01f271241899d7ea6a4af33a352d5a8 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Wed, 15 Jan 2025 18:39:38 +0000 Subject: [PATCH 06/15] Adjust a few design topics from Chrsitin --- site/src/components/Chart/Chart.stories.tsx | 2 +- site/src/index.css | 12 +--- .../UserEngagementChart.tsx | 57 +++++++++---------- site/tailwind.config.js | 8 +-- 4 files changed, 32 insertions(+), 47 deletions(-) diff --git a/site/src/components/Chart/Chart.stories.tsx b/site/src/components/Chart/Chart.stories.tsx index df863d4abee0e..74fded80d2b4d 100644 --- a/site/src/components/Chart/Chart.stories.tsx +++ b/site/src/components/Chart/Chart.stories.tsx @@ -19,7 +19,7 @@ const chartData = [ const chartConfig = { users: { label: "Users", - color: "hsl(var(--chart-1))", + color: "hsl(var(--highlight-purple))", }, } satisfies ChartConfig; diff --git a/site/src/index.css b/site/src/index.css index 3be1e1143bfce..5dddcf00364c3 100644 --- a/site/src/index.css +++ b/site/src/index.css @@ -28,11 +28,7 @@ --border-success: 142 76% 36%; --border-destructive: 0 84% 60%; --radius: 0.5rem; - --chart-1: 12 76% 61%; - --chart-2: 173 58% 39%; - --chart-3: 197 37% 24%; - --chart-4: 43 74% 66%; - --chart-5: 27 87% 67%; + --highlight-purple: 262 83% 58%; --border: 240 5.9% 90%; --input: 240 5.9% 90%; --ring: 240 10% 3.9%; @@ -59,11 +55,7 @@ --border-default: 240 4% 16%; --border-success: 142 76% 36%; --border-destructive: 0 91% 71%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; + --highlight-purple: 252 95% 85%; --border: 240 3.7% 15.9%; --input: 240 3.7% 15.9%; --ring: 240 4.9% 83.9%; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx index b3e03978d05b4..d41f2f4bb49dc 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx @@ -11,7 +11,7 @@ import { CollapsibleTrigger, } from "components/Collapsible/Collapsible"; import { Spinner } from "components/Spinner/Spinner"; -import { ChevronDownIcon, ExternalLinkIcon } from "lucide-react"; +import { ChevronRightIcon, ExternalLinkIcon } from "lucide-react"; import type { FC } from "react"; import { Link } from "react-router-dom"; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; @@ -19,7 +19,7 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; const chartConfig = { users: { label: "Users", - color: "hsl(var(--chart-1))", + color: "hsl(var(--highlight-purple))", }, } satisfies ChartConfig; @@ -41,40 +41,32 @@ export const UserEngagementChart: FC = ({ data }) => {

User Engagement

- - -

- We consider a user “engaged” if they initiate a connection to - their workspace. The connection can be made through apps, web - terminal or SSH. -

+

- The graph shows the number of unique users who were engaged at - least once during the day. + A user is considered "engaged" if they initiate a connection to + their workspace via apps, web terminal, or SSH. The graph displays + the daily count of unique users who engaged at least once, with + additional insights available through the Activity Audit and + License Consumption tools.

-
-

You might also check:

-
    -
  • - - Activity Audit - - -
  • -
  • - - License Consumption - - -
  • -
-
@@ -163,7 +155,12 @@ export const UserEngagementChart: FC = ({ data }) => { ) : ( -
+
No data available
) diff --git a/site/tailwind.config.js b/site/tailwind.config.js index 209a38b3a5695..c2e1273b97577 100644 --- a/site/tailwind.config.js +++ b/site/tailwind.config.js @@ -51,12 +51,8 @@ module.exports = { }, input: "hsl(var(--input))", ring: "hsl(var(--ring))", - chart: { - 1: "hsl(var(--chart-1))", - 2: "hsl(var(--chart-2))", - 3: "hsl(var(--chart-3))", - 4: "hsl(var(--chart-4))", - 5: "hsl(var(--chart-5))", + highlight: { + purple: "hsl(var(--highlight-purple))", }, }, keyframes: { From c454ebad8864d4abe08abfe09f430af1ab9eb4bb Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 16 Jan 2025 17:57:26 +0000 Subject: [PATCH 07/15] Use DAU for user engagement chart --- .../GeneralSettingsPage.tsx | 46 +------------------ .../GeneralSettingsPageView.tsx | 11 ++--- 2 files changed, 7 insertions(+), 50 deletions(-) diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx index b556dab6d6919..5c4c58840b031 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx @@ -1,9 +1,6 @@ import { deploymentDAUs } from "api/queries/deployment"; import { entitlements } from "api/queries/entitlements"; import { availableExperiments, experiments } from "api/queries/experiments"; -import { insightsUserStatusCounts } from "api/queries/insights"; -import type { UserStatusChangeCount } from "api/typesGenerated"; -import { eachDayOfInterval, isSameDay } from "date-fns"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { useDeploymentSettings } from "modules/management/DeploymentSettingsProvider"; import type { FC } from "react"; @@ -26,7 +23,7 @@ const GeneralSettingsPage: FC = () => { return !safeExperiments.includes(exp); }) ?? []; - const { data: userStatusCount } = useQuery(insightsUserStatusCounts()); + const { data: dailyActiveUsers } = useQuery(deploymentDAUs()); return ( <> @@ -35,7 +32,7 @@ const GeneralSettingsPage: FC = () => { { ); }; -// TODO: Remove this function once the API returns values sorted by date and -// includes all dates within the specified range. The -// `/api/v2/insights/user-status-counts` endpoint does not return the -// `UserStatusChangeCount[]` items sorted by date, nor does it backfill missing -// dates within the specified range. -function normalizeStatusCount( - statusCount: UserStatusChangeCount[] | undefined, -) { - if (!statusCount) { - return undefined; - } - - const sortedCounts = statusCount.toSorted((a, b) => { - return new Date(a.date).getTime() - new Date(b.date).getTime(); - }); - - const dates = eachDayOfInterval({ - start: new Date(sortedCounts[0].date), - end: new Date(sortedCounts[sortedCounts.length - 1].date), - }); - - const backFilledCounts: UserStatusChangeCount[] = []; - dates.forEach((date, i) => { - const existingCount = sortedCounts.find((c) => - isSameDay(date, new Date(c.date)), - ); - if (existingCount) { - backFilledCounts.push(existingCount); - } else { - backFilledCounts.push({ - date: date.toISOString(), - count: backFilledCounts[i - 1].count, - }); - } - }); - - return backFilledCounts; -} - export default GeneralSettingsPage; diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 577c9a7f52f61..615b08ac76024 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -1,12 +1,11 @@ import AlertTitle from "@mui/material/AlertTitle"; import LinearProgress from "@mui/material/LinearProgress"; import type { + DAUsResponse, Entitlements, Experiments, SerpentOption, - UserStatusChangeCount, } from "api/typesGenerated"; -import { ErrorAlert } from "components/Alert/ErrorAlert"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; import { Stack } from "components/Stack/Stack"; import type { FC } from "react"; @@ -19,7 +18,7 @@ import { UserEngagementChart } from "./UserEngagementChart"; export type GeneralSettingsPageViewProps = { deploymentOptions: SerpentOption[]; - activeUsersCount: UserStatusChangeCount[] | undefined; + dailyActiveUsers: DAUsResponse | undefined; entitlements: Entitlements | undefined; readonly invalidExperiments: Experiments | string[]; readonly safeExperiments: Experiments | string[]; @@ -27,7 +26,7 @@ export type GeneralSettingsPageViewProps = { export const GeneralSettingsPageView: FC = ({ deploymentOptions, - activeUsersCount, + dailyActiveUsers, entitlements, safeExperiments, invalidExperiments, @@ -47,9 +46,9 @@ export const GeneralSettingsPageView: FC = ({ /> ({ + data={dailyActiveUsers?.entries.map((i) => ({ date: i.date, - users: i.count, + users: i.amount, }))} /> {licenseUtilizationPercentage && ( From 4b7bf9f198029ef4bbceef5bb6b58bf40518d13c Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 16 Jan 2025 18:14:33 +0000 Subject: [PATCH 08/15] Adjust engagement chart --- site/src/components/Link/Link.tsx | 4 ++-- .../GeneralSettingsPageView.stories.tsx | 10 +--------- .../UserEngagementChart.tsx | 19 +++++++++++++++---- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/site/src/components/Link/Link.tsx b/site/src/components/Link/Link.tsx index e70475899f825..616a24e9fa65f 100644 --- a/site/src/components/Link/Link.tsx +++ b/site/src/components/Link/Link.tsx @@ -1,4 +1,4 @@ -import { Slot } from "@radix-ui/react-slot"; +import { Slot, Slottable } from "@radix-ui/react-slot"; import { type VariantProps, cva } from "class-variance-authority"; import { SquareArrowOutUpRightIcon } from "lucide-react"; import { forwardRef } from "react"; @@ -38,7 +38,7 @@ export const Link = forwardRef( ref={ref} {...props} > - {children} + {children} - {isLoading && } +
+ {isLoading && ( + + )} - {!isLoading && licenses && licenses?.length > 0 && ( - - {licenses - ?.sort( - (a, b) => - new Date(b.claims.license_expires).valueOf() - - new Date(a.claims.license_expires).valueOf(), - ) - .map((license) => ( - - ))} - - )} + {!isLoading && licenses && licenses?.length > 0 && ( + + {licenses + ?.sort( + (a, b) => + new Date(b.claims.license_expires).valueOf() - + new Date(a.claims.license_expires).valueOf(), + ) + .map((license) => ( + + ))} + + )} - {!isLoading && licenses === null && ( -
- - - You don't have any licenses! - - You're missing out on high availability, RBAC, quotas, and - much more. Contact{" "} - sales or{" "} - - request a trial license - {" "} - to get started. - + {!isLoading && licenses === null && ( +
+ + + + You don't have any licenses! + + + You're missing out on high availability, RBAC, quotas, + and much more. Contact{" "} + sales or{" "} + + request a trial license + {" "} + to get started. + + - -
- )} +
+ )} + + ({ + date: i.date, + users: i.count, + limit: 80, + }))} + /> +
); }; diff --git a/site/tailwind.config.js b/site/tailwind.config.js index c2e1273b97577..09dddf9d60181 100644 --- a/site/tailwind.config.js +++ b/site/tailwind.config.js @@ -53,6 +53,7 @@ module.exports = { ring: "hsl(var(--ring))", highlight: { purple: "hsl(var(--highlight-purple))", + green: "hsl(var(--highlight-green))", }, }, keyframes: { From 372649b2138db0560e83f6141d80436a902ead1a Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 16 Jan 2025 19:45:25 +0000 Subject: [PATCH 10/15] Add storybook for license chart --- .../LicenseSeatConsumptionChart.stories.tsx | 37 +++++++++++++++++++ .../LicenseSeatConsumptionChart.tsx | 5 ++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.stories.tsx diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.stories.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.stories.tsx new file mode 100644 index 0000000000000..4a872d2470b7e --- /dev/null +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { LicenseSeatConsumptionChart } from "./LicenseSeatConsumptionChart"; + +const meta: Meta = { + title: + "pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart", + component: LicenseSeatConsumptionChart, + args: { + limit: 220, + data: [ + { date: "1/1/2024", users: 150 }, + { date: "1/2/2024", users: 165 }, + { date: "1/3/2024", users: 180 }, + { date: "1/4/2024", users: 155 }, + { date: "1/5/2024", users: 190 }, + { date: "1/6/2024", users: 200 }, + { date: "1/7/2024", users: 210 }, + ], + }, +}; + +export default meta; +type Story = StoryObj; + +export const Loaded: Story = {}; + +export const Empty: Story = { + args: { + data: [], + }, +}; + +export const Loading: Story = { + args: { + data: undefined, + }, +}; diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx index e2ef5b11ac39b..31ef987e01b19 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx @@ -146,6 +146,8 @@ export const LicenseSeatConsumptionChart: FC< } /> {limit && ( Date: Thu, 16 Jan 2025 19:46:06 +0000 Subject: [PATCH 11/15] Remove text-ms --- site/tailwind.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/site/tailwind.config.js b/site/tailwind.config.js index 09dddf9d60181..b37a12f52acea 100644 --- a/site/tailwind.config.js +++ b/site/tailwind.config.js @@ -16,7 +16,6 @@ module.exports = { fontSize: { "2xs": ["0.625rem", "0.875rem"], sm: ["0.875rem", "1.5rem"], - md: ["1rem", "1.5rem"], "3xl": ["2rem", "2.5rem"], }, borderRadius: { From 39ee475b26b41a8391e5d6acb574e952cea2c028 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 16 Jan 2025 19:58:52 +0000 Subject: [PATCH 12/15] Run make fmt --- .../GeneralSettingsPage/UserEngagementChart.tsx | 2 +- .../LicensesSettingsPage/LicenseSeatConsumptionChart.tsx | 2 +- .../LicensesSettingsPage/LicensesSettingsPage.tsx | 2 +- .../LicensesSettingsPage/LicensesSettingsPageView.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx index 72b03e189d61a..431141a148eb0 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/UserEngagementChart.tsx @@ -14,8 +14,8 @@ import { Link } from "components/Link/Link"; import { Spinner } from "components/Spinner/Spinner"; import { ChevronRightIcon } from "lucide-react"; import type { FC } from "react"; -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; import { Link as RouterLink } from "react-router-dom"; +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; const chartConfig = { users: { diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx index 31ef987e01b19..f57ae959ecda6 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx @@ -14,6 +14,7 @@ import { Link } from "components/Link/Link"; import { Spinner } from "components/Spinner/Spinner"; import { ChevronRightIcon } from "lucide-react"; import type { FC } from "react"; +import { Link as RouterLink } from "react-router-dom"; import { Area, AreaChart, @@ -22,7 +23,6 @@ import { XAxis, YAxis, } from "recharts"; -import { Link as RouterLink } from "react-router-dom"; const chartConfig = { users: { diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx index b6f81e54d6405..aafc00f4ee566 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPage.tsx @@ -1,6 +1,7 @@ import { API } from "api/api"; import { getErrorMessage } from "api/errors"; import { entitlements, refreshEntitlements } from "api/queries/entitlements"; +import { insightsUserStatusCounts } from "api/queries/insights"; import { displayError, displaySuccess } from "components/GlobalSnackbar/utils"; import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata"; import { type FC, useEffect, useState } from "react"; @@ -9,7 +10,6 @@ import { useMutation, useQuery, useQueryClient } from "react-query"; import { useSearchParams } from "react-router-dom"; import { pageTitle } from "utils/page"; import LicensesSettingsPageView from "./LicensesSettingsPageView"; -import { insightsUserStatusCounts } from "api/queries/insights"; const LicensesSettingsPage: FC = () => { const queryClient = useQueryClient(); diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index 9bb999926f6d3..7d06079607fee 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -7,6 +7,7 @@ import MuiLink from "@mui/material/Link"; import Skeleton from "@mui/material/Skeleton"; import Tooltip from "@mui/material/Tooltip"; import type { GetLicensesResponse } from "api/api"; +import type { UserStatusChangeCount } from "api/typesGenerated"; import { SettingsHeader } from "components/SettingsHeader/SettingsHeader"; import { Stack } from "components/Stack/Stack"; import { useWindowSize } from "hooks/useWindowSize"; @@ -14,7 +15,6 @@ import type { FC } from "react"; import Confetti from "react-confetti"; import { Link } from "react-router-dom"; import { LicenseCard } from "./LicenseCard"; -import type { UserStatusChangeCount } from "api/typesGenerated"; import { LicenseSeatConsumptionChart } from "./LicenseSeatConsumptionChart"; type Props = { From 11ce94747fd9536d5e38783ee6b8e99d45c53def Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 17 Jan 2025 13:14:46 +0000 Subject: [PATCH 13/15] Adjust charts --- .../GeneralSettingsPage.tsx | 2 - .../GeneralSettingsPageView.tsx | 41 ------------------- .../LicenseSeatConsumptionChart.tsx | 19 +++++++-- .../LicensesSettingsPageView.tsx | 19 +++++---- 4 files changed, 27 insertions(+), 54 deletions(-) diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx index 5c4c58840b031..77b9576f24152 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx @@ -14,7 +14,6 @@ const GeneralSettingsPage: FC = () => { const safeExperimentsQuery = useQuery(availableExperiments()); const { metadata } = useEmbeddedMetadata(); - const entitlementsQuery = useQuery(entitlements(metadata.entitlements)); const enabledExperimentsQuery = useQuery(experiments(metadata.experiments)); const safeExperiments = safeExperimentsQuery.data?.safe ?? []; @@ -33,7 +32,6 @@ const GeneralSettingsPage: FC = () => { diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 615b08ac76024..7cdd3c664b0a5 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -1,5 +1,4 @@ import AlertTitle from "@mui/material/AlertTitle"; -import LinearProgress from "@mui/material/LinearProgress"; import type { DAUsResponse, Entitlements, @@ -13,13 +12,11 @@ import { useDeploymentOptions } from "utils/deployOptions"; import { docs } from "utils/docs"; import { Alert } from "../../../components/Alert/Alert"; import OptionsTable from "../OptionsTable"; -import { ChartSection } from "./ChartSection"; import { UserEngagementChart } from "./UserEngagementChart"; export type GeneralSettingsPageViewProps = { deploymentOptions: SerpentOption[]; dailyActiveUsers: DAUsResponse | undefined; - entitlements: Entitlements | undefined; readonly invalidExperiments: Experiments | string[]; readonly safeExperiments: Experiments | string[]; }; @@ -27,16 +24,9 @@ export type GeneralSettingsPageViewProps = { export const GeneralSettingsPageView: FC = ({ deploymentOptions, dailyActiveUsers, - entitlements, safeExperiments, invalidExperiments, }) => { - const licenseUtilizationPercentage = - entitlements?.features?.user_limit?.actual && - entitlements?.features?.user_limit?.limit - ? entitlements.features.user_limit.actual / - entitlements.features.user_limit.limit - : undefined; return ( <> = ({ users: i.amount, }))} /> - {licenseUtilizationPercentage && ( - - - - {Math.round(licenseUtilizationPercentage * 100)}% used ( - {entitlements!.features.user_limit.actual}/ - {entitlements!.features.user_limit.limit} users) - - - )} {invalidExperiments.length > 0 && ( Invalid experiments in use: diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx index f57ae959ecda6..aac49b0d72e03 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicenseSeatConsumptionChart.tsx @@ -23,6 +23,7 @@ import { XAxis, YAxis, } from "recharts"; +import { docs } from "utils/docs"; const chartConfig = { users: { @@ -101,13 +102,25 @@ export const LicenseSeatConsumptionChart: FC< You might also check:
  • - Activity Audit + + Activity Audit +
  • - Daily user activity + + + Daily user activity + +
  • - More details on user account statuses + + More details on user account statuses +
diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index 7d06079607fee..1689eca67e46e 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -16,6 +16,7 @@ import Confetti from "react-confetti"; import { Link } from "react-router-dom"; import { LicenseCard } from "./LicenseCard"; import { LicenseSeatConsumptionChart } from "./LicenseSeatConsumptionChart"; +import { license } from "../../../../e2e/constants"; type Props = { showConfetti: boolean; @@ -133,14 +134,16 @@ const LicensesSettingsPageView: FC = ({ )} - ({ - date: i.date, - users: i.count, - limit: 80, - }))} - /> + {licenses && licenses.length > 0 && ( + ({ + date: i.date, + users: i.count, + limit: 80, + }))} + /> + )} ); From c2e7b959cac2789dcf43896a73bff5eb85758eaf Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 17 Jan 2025 15:19:27 +0000 Subject: [PATCH 14/15] Fix format --- .../LicensesSettingsPage/LicensesSettingsPageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx index 1689eca67e46e..86ab4be190f34 100644 --- a/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/LicensesSettingsPageView.tsx @@ -14,9 +14,9 @@ import { useWindowSize } from "hooks/useWindowSize"; import type { FC } from "react"; import Confetti from "react-confetti"; import { Link } from "react-router-dom"; +import { license } from "../../../../e2e/constants"; import { LicenseCard } from "./LicenseCard"; import { LicenseSeatConsumptionChart } from "./LicenseSeatConsumptionChart"; -import { license } from "../../../../e2e/constants"; type Props = { showConfetti: boolean; From f38361e08f28c33ea2f8f80e5a9d5cace728a105 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Fri, 17 Jan 2025 15:26:01 +0000 Subject: [PATCH 15/15] Fix stories --- .../GeneralSettingsPageView.stories.tsx | 78 +------------------ 1 file changed, 1 insertion(+), 77 deletions(-) diff --git a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx index 540493e0d38a8..50b04bb64228e 100644 --- a/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx @@ -1,9 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; -import { - MockDeploymentDAUResponse, - MockEntitlementsWithUserLimit, - mockApiError, -} from "testHelpers/entities"; +import { MockDeploymentDAUResponse } from "testHelpers/entities"; import { GeneralSettingsPageView } from "./GeneralSettingsPageView"; const meta: Meta = { @@ -42,7 +38,6 @@ const meta: Meta = { dailyActiveUsers: MockDeploymentDAUResponse, invalidExperiments: [], safeExperiments: [], - entitlements: undefined, }, }; @@ -122,74 +117,3 @@ export const invalidExperimentsEnabled: Story = { invalidExperiments: ["invalid"], }, }; - -export const WithLicenseUtilization: Story = { - args: { - entitlements: { - ...MockEntitlementsWithUserLimit, - features: { - ...MockEntitlementsWithUserLimit.features, - user_limit: { - ...MockEntitlementsWithUserLimit.features.user_limit, - enabled: true, - actual: 75, - limit: 100, - entitlement: "entitled", - }, - }, - }, - }, -}; - -export const HighLicenseUtilization: Story = { - args: { - entitlements: { - ...MockEntitlementsWithUserLimit, - features: { - ...MockEntitlementsWithUserLimit.features, - user_limit: { - ...MockEntitlementsWithUserLimit.features.user_limit, - enabled: true, - actual: 95, - limit: 100, - entitlement: "entitled", - }, - }, - }, - }, -}; - -export const ExceedsLicenseUtilization: Story = { - args: { - entitlements: { - ...MockEntitlementsWithUserLimit, - features: { - ...MockEntitlementsWithUserLimit.features, - user_limit: { - ...MockEntitlementsWithUserLimit.features.user_limit, - enabled: true, - actual: 100, - limit: 95, - entitlement: "entitled", - }, - }, - }, - }, -}; -export const NoLicenseLimit: Story = { - args: { - entitlements: { - ...MockEntitlementsWithUserLimit, - features: { - ...MockEntitlementsWithUserLimit.features, - user_limit: { - ...MockEntitlementsWithUserLimit.features.user_limit, - enabled: false, - actual: 0, - limit: 0, - entitlement: "entitled", - }, - }, - }, - }, -};