fix: improve provisioner details layout and show count line (#14749)

* きれい

* とても大きい

* improve storybook test

* ボタン

* アイコンの名前

* ジェ
This commit is contained in:
Kayla Washburn-Love
2024-09-20 14:59:09 -06:00
committed by GitHub
parent 35017822d5
commit 3338f32489
3 changed files with 118 additions and 44 deletions
@@ -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<string, string>;
readonly type: ProvisionerGroupType;
readonly provisioners: readonly ProvisionerDaemon[];
}
@@ -38,6 +41,7 @@ interface ProvisionerGroupProps {
export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
buildInfo,
keyName,
keyTags,
type,
provisioners,
}) => {
@@ -61,7 +65,7 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
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<ProvisionerGroupProps> = ({
? "1 provisioner"
: `${provisionersWithWarnings} provisioners`;
const hasMultipleTagVariants =
type === "psk" &&
provisioners.some((it) => !isEqual(it.tags, { scope: "organization" }));
return (
<div
css={[
@@ -153,14 +161,26 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
justifyContent: "right",
}}
>
<Tooltip title="Scope">
<Pill
size="lg"
icon={daemonScope === "organization" ? <Business /> : <Person />}
>
<span css={{ textTransform: "capitalize" }}>{daemonScope}</span>
{!hasMultipleTagVariants ? (
<Tooltip title="Scope">
<Pill
size="lg"
icon={
daemonScope === "organization" ? (
<BusinessIcon />
) : (
<PersonIcon />
)
}
>
<span css={{ textTransform: "capitalize" }}>{daemonScope}</span>
</Pill>
</Tooltip>
) : (
<Pill size="lg" icon={<TagIcon />}>
Multiple tags
</Pill>
</Tooltip>
)}
{type === "key" &&
extraTags.map(([key, value]) => (
<ProvisionerTag key={key} tagName={key} tagValue={value} />
@@ -172,9 +192,9 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
<div
css={{
padding: "0 24px 24px",
display: "flex",
display: "grid",
gap: 12,
flexWrap: "wrap",
gridTemplateColumns: "repeat(auto-fill, minmax(385px, 1fr))",
}}
>
{provisionersWithWarningInfo.map((provisioner) => (
@@ -186,7 +206,6 @@ export const ProvisionerGroup: FC<ProvisionerGroupProps> = ({
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<ProvisionerGroupProps> = ({
)}
</span>
</div>
{type === "psk" && (
{hasMultipleTagVariants && (
<PskProvisionerTags tags={provisioner.tags} />
)}
</Stack>
@@ -317,7 +336,8 @@ interface PskProvisionerTagsProps {
const PskProvisionerTags: FC<PskProvisionerTagsProps> = ({ tags }) => {
const daemonScope = tags.scope || "organization";
const iconScope = daemonScope === "organization" ? <Business /> : <Person />;
const iconScope =
daemonScope === "organization" ? <BusinessIcon /> : <PersonIcon />;
const extraTags = Object.entries(tags).filter(
([tag]) => tag !== "scope" && tag !== "owner",
@@ -343,6 +363,7 @@ const PskProvisionerTags: FC<PskProvisionerTagsProps> = ({ tags }) => {
css={{
"& .MuiPaper-root": {
padding: 20,
minWidth: "unset",
maxWidth: 340,
width: "fit-content",
},
@@ -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 = {
@@ -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 (
<div>
<PageHeader
// The deployment settings layout already has padding.
css={{ paddingTop: 0 }}
css={{ paddingTop: 0, paddingBottom: 12 }}
actions={
<Button
endIcon={<OpenInNewIcon />}
@@ -42,22 +48,34 @@ export const OrganizationProvisionersPageView: FC<
>
<PageHeaderTitle>Provisioners</PageHeaderTitle>
</PageHeader>
{isEmpty ? (
<EmptyState
message="No provisioners"
description="A provisioner is required before you can create templates and workspaces. You can connect your first provisioner by following our documentation."
cta={
<Button
endIcon={<OpenInNewIcon />}
target="_blank"
href={docs("/admin/provisioners")}
>
Show me how to create a provisioner
</Button>
}
/>
) : (
<div
css={(theme) => ({
margin: 0,
fontSize: 12,
paddingBottom: 18,
color: theme.palette.text.secondary,
})}
>
Showing {provisionerGroupsCount} groups and {provisionersCount}{" "}
provisioners
</div>
)}
<Stack spacing={4.5}>
{isEmpty && (
<EmptyState
message="No provisioners"
description="A provisioner is required before you can create templates and workspaces. You can connect your first provisioner by following our documentation."
cta={
<Button
endIcon={<OpenInNewIcon />}
target="_blank"
href={docs("/admin/provisioners")}
>
Show me how to create a provisioner
</Button>
}
/>
)}
{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}
/>