diff --git a/site/src/modules/provisioners/ProvisionerGroup.tsx b/site/src/modules/provisioners/ProvisionerGroup.tsx index 69546609027dd..b6077da9614bc 100644 --- a/site/src/modules/provisioners/ProvisionerGroup.tsx +++ b/site/src/modules/provisioners/ProvisionerGroup.tsx @@ -1,6 +1,7 @@ import { type Interpolation, type Theme, useTheme } from "@emotion/react"; -import Business from "@mui/icons-material/Business"; -import Person from "@mui/icons-material/Person"; +import BusinessIcon from "@mui/icons-material/Business"; +import PersonIcon from "@mui/icons-material/Person"; +import TagIcon from "@mui/icons-material/Sell"; import Button from "@mui/material/Button"; import Link from "@mui/material/Link"; import Tooltip from "@mui/material/Tooltip"; @@ -21,6 +22,7 @@ import { } from "components/Popover/Popover"; import { Stack } from "components/Stack/Stack"; import { StatusIndicator } from "components/StatusIndicator/StatusIndicator"; +import isEqual from "lodash/isEqual"; import { type FC, useState } from "react"; import { createDayString } from "utils/createDayString"; import { docs } from "utils/docs"; @@ -30,7 +32,8 @@ type ProvisionerGroupType = "builtin" | "psk" | "key"; interface ProvisionerGroupProps { readonly buildInfo?: BuildInfoResponse; - readonly keyName?: string; + readonly keyName: string; + readonly keyTags: Record; readonly type: ProvisionerGroupType; readonly provisioners: readonly ProvisionerDaemon[]; } @@ -38,6 +41,7 @@ interface ProvisionerGroupProps { export const ProvisionerGroup: FC = ({ buildInfo, keyName, + keyTags, type, provisioners, }) => { @@ -61,7 +65,7 @@ export const ProvisionerGroup: FC = ({ provisioners.length === 1 ? "1 provisioner" : `${provisioners.length} provisioners`; - const extraTags = Object.entries(firstProvisioner.tags).filter( + const extraTags = Object.entries(keyTags).filter( ([key]) => key !== "scope" && key !== "owner", ); @@ -90,6 +94,10 @@ export const ProvisionerGroup: FC = ({ ? "1 provisioner" : `${provisionersWithWarnings} provisioners`; + const hasMultipleTagVariants = + type === "psk" && + provisioners.some((it) => !isEqual(it.tags, { scope: "organization" })); + return (
= ({ justifyContent: "right", }} > - - : } - > - {daemonScope} + {!hasMultipleTagVariants ? ( + + + ) : ( + + ) + } + > + {daemonScope} + + + ) : ( + }> + Multiple tags - + )} {type === "key" && extraTags.map(([key, value]) => ( @@ -172,9 +192,9 @@ export const ProvisionerGroup: FC = ({
{provisionersWithWarningInfo.map((provisioner) => ( @@ -186,7 +206,6 @@ export const ProvisionerGroup: FC = ({ border: `1px solid ${theme.palette.divider}`, fontSize: 14, padding: "14px 18px", - width: 375, }, provisioner.warningCount > 0 && styles.warningBorder, ]} @@ -222,7 +241,7 @@ export const ProvisionerGroup: FC = ({ )}
- {type === "psk" && ( + {hasMultipleTagVariants && ( )} @@ -317,7 +336,8 @@ interface PskProvisionerTagsProps { const PskProvisionerTags: FC = ({ tags }) => { const daemonScope = tags.scope || "organization"; - const iconScope = daemonScope === "organization" ? : ; + const iconScope = + daemonScope === "organization" ? : ; const extraTags = Object.entries(tags).filter( ([tag]) => tag !== "scope" && tag !== "owner", @@ -343,6 +363,7 @@ const PskProvisionerTags: FC = ({ tags }) => { css={{ "& .MuiPaper-root": { padding: 20, + minWidth: "unset", maxWidth: 340, width: "fit-content", }, diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx index 19544a941b5b8..439fba6c2f432 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; +import { screen, userEvent } from "@storybook/test"; import { MockBuildInfo, MockProvisioner, @@ -38,7 +39,11 @@ export const Provisioners: Story = { ], }, { - key: { ...MockProvisionerKey, id: "ジャイデン", name: "ジャイデン" }, + key: MockProvisionerPskKey, + daemons: [MockProvisioner, MockProvisioner2], + }, + { + key: { ...MockProvisionerKey, id: "ジェイデン", name: "ジェイデン" }, daemons: [MockProvisioner, MockProvisioner2], }, { @@ -53,21 +58,50 @@ export const Provisioners: Story = { ], }, { - key: { ...MockProvisionerKey, id: "ケイラ", name: "ケイラ" }, - daemons: [ - { - ...MockProvisioner, - tags: { - ...MockProvisioner.tags, - 都市: "ユタ", - きっぷ: "yes", - ちいさい: "no", - }, + key: { + ...MockProvisionerKey, + id: "ケイラ", + name: "ケイラ", + tags: { + ...MockProvisioner.tags, + 都市: "ユタ", + きっぷ: "yes", + ちいさい: "no", }, - ], + }, + daemons: Array.from({ length: 117 }, (_, i) => ({ + ...MockProvisioner, + id: `ケイラ-${i}`, + name: `ケイラ-${i}`, + })), }, ], }, + play: async ({ step }) => { + await step("open all details", async () => { + const expandButtons = await screen.findAllByRole("button", { + name: "Show provisioner details", + }); + for (const it of expandButtons) { + await userEvent.click(it); + } + }); + + await step("close uninteresting/large details", async () => { + const collapseButtons = await screen.findAllByRole("button", { + name: "Hide provisioner details", + }); + + await userEvent.click(collapseButtons[2]); + await userEvent.click(collapseButtons[3]); + await userEvent.click(collapseButtons[5]); + }); + + await step("show version popover", async () => { + const outOfDate = await screen.findByText("Out of date"); + await userEvent.hover(outOfDate); + }); + }, }; export const Empty: Story = { diff --git a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx index 5f0b5f4047806..e8203bb5f0050 100644 --- a/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx +++ b/site/src/pages/ManagementSettingsPage/OrganizationProvisionersPageView.tsx @@ -25,11 +25,17 @@ export const OrganizationProvisionersPageView: FC< > = ({ buildInfo, provisioners }) => { const isEmpty = provisioners.every((group) => group.daemons.length === 0); + const provisionerGroupsCount = provisioners.length; + const provisionersCount = provisioners.reduce( + (a, group) => a + group.daemons.length, + 0, + ); + return (
} @@ -42,22 +48,34 @@ export const OrganizationProvisionersPageView: FC< > Provisioners + {isEmpty ? ( + } + target="_blank" + href={docs("/admin/provisioners")} + > + Show me how to create a provisioner + + } + /> + ) : ( +
({ + margin: 0, + fontSize: 12, + paddingBottom: 18, + color: theme.palette.text.secondary, + })} + > + Showing {provisionerGroupsCount} groups and {provisionersCount}{" "} + provisioners +
+ )} - {isEmpty && ( - } - target="_blank" - href={docs("/admin/provisioners")} - > - Show me how to create a provisioner - - } - /> - )} {provisioners.map((group) => { const type = getGroupType(group.key); @@ -74,6 +92,7 @@ export const OrganizationProvisionersPageView: FC< key={group.key.id} buildInfo={buildInfo} keyName={group.key.name} + keyTags={group.key.tags} type={type} provisioners={group.daemons} />