fix: give more room to lonely resource metadata items (#9832)

This commit is contained in:
Kayla Washburn 2023-09-25 09:40:51 -06:00 committed by GitHub
parent 3757005e82
commit 47d3161b0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 337 additions and 152 deletions

View File

@ -8,41 +8,14 @@ import { AgentRow } from "./AgentRow";
import { ResourceCard } from "./ResourceCard";
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
import type { Meta, StoryObj } from "@storybook/react";
import { type WorkspaceAgent } from "api/typesGenerated";
const meta: Meta<typeof ResourceCard> = {
title: "components/ResourceCard",
title: "components/Resources/ResourceCard",
component: ResourceCard,
args: {
resource: MockWorkspaceResource,
agentRow: (agent) => (
<ProxyContext.Provider
value={{
proxyLatencies: MockProxyLatencies,
proxy: getPreferredProxy([], undefined),
proxies: [],
isLoading: false,
isFetched: true,
setProxy: () => {
return;
},
clearProxy: () => {
return;
},
refetchProxyLatencies: (): Date => {
return new Date();
},
}}
>
<AgentRow
showApps
key={agent.id}
agent={agent}
workspace={MockWorkspace}
serverVersion=""
onUpdateAgent={action("updateAgent")}
/>
</ProxyContext.Provider>
),
agentRow: getAgentRow,
},
};
@ -96,34 +69,38 @@ export const BunchOfMetadata: Story = {
},
],
},
agentRow: (agent) => (
<ProxyContext.Provider
value={{
proxyLatencies: MockProxyLatencies,
proxy: getPreferredProxy([], undefined),
proxies: [],
isLoading: false,
isFetched: true,
setProxy: () => {
return;
},
clearProxy: () => {
return;
},
refetchProxyLatencies: (): Date => {
return new Date();
},
}}
>
<AgentRow
showApps
key={agent.id}
agent={agent}
workspace={MockWorkspace}
serverVersion=""
onUpdateAgent={action("updateAgent")}
/>
</ProxyContext.Provider>
),
agentRow: getAgentRow,
},
};
function getAgentRow(agent: WorkspaceAgent): JSX.Element {
return (
<ProxyContext.Provider
value={{
proxyLatencies: MockProxyLatencies,
proxy: getPreferredProxy([], undefined),
proxies: [],
isLoading: false,
isFetched: true,
setProxy: () => {
return;
},
clearProxy: () => {
return;
},
refetchProxyLatencies: (): Date => {
return new Date();
},
}}
>
<AgentRow
showApps
key={agent.id}
agent={agent}
workspace={MockWorkspace}
serverVersion=""
onUpdateAgent={action("updateAgent")}
/>
</ProxyContext.Provider>
);
}

View File

@ -10,8 +10,8 @@ import {
} from "components/DropdownArrows/DropdownArrows";
import IconButton from "@mui/material/IconButton";
import Tooltip from "@mui/material/Tooltip";
import { Maybe } from "components/Conditionals/Maybe";
import { CopyableValue } from "components/CopyableValue/CopyableValue";
import { type Theme } from "@mui/material/styles";
export interface ResourceCardProps {
resource: WorkspaceResource;
@ -21,12 +21,20 @@ export interface ResourceCardProps {
export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
const [shouldDisplayAllMetadata, setShouldDisplayAllMetadata] =
useState(false);
const styles = useStyles();
const metadataToDisplay = resource.metadata ?? [];
const visibleMetadata = shouldDisplayAllMetadata
? metadataToDisplay
: metadataToDisplay.slice(0, 4);
// Add one to `metadataLength` if the resource has a cost, and hide one
// additional metadata item, because cost is displayed in the same grid.
let metadataLength = resource.metadata?.length ?? 0;
if (resource.daily_cost > 0) {
metadataLength += 1;
visibleMetadata.pop();
}
const styles = useStyles({ metadataLength });
return (
<div key={resource.id} className={`${styles.resourceCard} resource-card`}>
<Stack
@ -49,57 +57,52 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
</div>
</Stack>
<Stack alignItems="flex-start" direction="row" spacing={5}>
<div className={styles.metadataHeader}>
{resource.daily_cost > 0 && (
<div className={styles.metadata}>
<div className={styles.metadataLabel}>
<b>cost</b>
</div>
<div className={styles.metadataHeader}>
{resource.daily_cost > 0 && (
<div className={styles.metadata}>
<div className={styles.metadataLabel}>
<b>cost</b>
</div>
<div className={styles.metadataValue}>{resource.daily_cost}</div>
</div>
)}
{visibleMetadata.map((meta) => {
return (
<div className={styles.metadata} key={meta.key}>
<div className={styles.metadataLabel}>{meta.key}</div>
<div className={styles.metadataValue}>
{resource.daily_cost}
{meta.sensitive ? (
<SensitiveValue value={meta.value} />
) : (
<CopyableValue value={meta.value}>
{meta.value}
</CopyableValue>
)}
</div>
</div>
)}
{visibleMetadata.map((meta) => {
return (
<div className={styles.metadata} key={meta.key}>
<div className={styles.metadataLabel}>{meta.key}</div>
<div className={styles.metadataValue}>
{meta.sensitive ? (
<SensitiveValue value={meta.value} />
) : (
<CopyableValue value={meta.value}>
{meta.value}
</CopyableValue>
)}
</div>
</div>
);
})}
</div>
<Maybe condition={metadataToDisplay.length > 4}>
<Tooltip
title={
shouldDisplayAllMetadata ? "Hide metadata" : "Show all metadata"
}
);
})}
</div>
{metadataLength > 4 && (
<Tooltip
title={
shouldDisplayAllMetadata ? "Hide metadata" : "Show all metadata"
}
>
<IconButton
onClick={() => {
setShouldDisplayAllMetadata((value) => !value);
}}
size="large"
>
<IconButton
onClick={() => {
setShouldDisplayAllMetadata((value) => !value);
}}
size="large"
>
{shouldDisplayAllMetadata ? (
<CloseDropdown margin={false} />
) : (
<OpenDropdown margin={false} />
)}
</IconButton>
</Tooltip>
</Maybe>
</Stack>
{shouldDisplayAllMetadata ? (
<CloseDropdown margin={false} />
) : (
<OpenDropdown margin={false} />
)}
</IconButton>
</Tooltip>
)}
</Stack>
{resource.agents && resource.agents.length > 0 && (
@ -109,7 +112,7 @@ export const ResourceCard: FC<ResourceCardProps> = ({ resource, agentRow }) => {
);
};
const useStyles = makeStyles((theme) => ({
const useStyles = makeStyles<Theme, { metadataLength: number }>((theme) => ({
resourceCard: {
background: theme.palette.background.paper,
borderRadius: theme.shape.borderRadius,
@ -141,12 +144,15 @@ const useStyles = makeStyles((theme) => ({
},
},
metadataHeader: {
metadataHeader: (props) => ({
flexGrow: 2,
display: "grid",
gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
gridTemplateColumns: `repeat(${
props.metadataLength === 1 ? 1 : 4
}, minmax(0, 1fr))`,
gap: theme.spacing(5),
rowGap: theme.spacing(3),
},
}),
metadata: {
...theme.typography.body2,

View File

@ -0,0 +1,164 @@
import { action } from "@storybook/addon-actions";
import {
MockProxyLatencies,
MockWorkspace,
MockWorkspaceResource,
MockWorkspaceResourceMultipleAgents,
} from "testHelpers/entities";
import { AgentRow } from "./AgentRow";
import { Resources } from "./Resources";
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
import type { Meta, StoryObj } from "@storybook/react";
import { type WorkspaceAgent } from "api/typesGenerated";
const meta: Meta<typeof Resources> = {
title: "components/Resources/Resources",
component: Resources,
args: {
resources: [MockWorkspaceResource],
agentRow: getAgentRow,
},
};
export default meta;
type Story = StoryObj<typeof Resources>;
export const Example: Story = {};
export const MultipleAgents: Story = {
args: {
resources: [MockWorkspaceResourceMultipleAgents],
},
};
const nullDevice = {
created_at: "",
job_id: "",
workspace_transition: "start",
type: "null_resource",
hide: false,
icon: "",
daily_cost: 0,
} as const;
const short = {
key: "Short",
value: "Hi!",
sensitive: false,
};
const long = {
key: "Long",
value: "The quick brown fox jumped over the lazy dog",
sensitive: false,
};
const reallyLong = {
key: "Really long",
value:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
sensitive: false,
};
export const BunchOfDevicesWithMetadata: Story = {
args: {
resources: [
MockWorkspaceResource,
{
...nullDevice,
id: "e8c846da",
name: "Short",
metadata: [short],
},
{
...nullDevice,
id: "a1b11343",
name: "Long",
metadata: [long],
},
{
...nullDevice,
id: "09ab7e8c",
name: "Really long",
metadata: [reallyLong],
},
{
...nullDevice,
id: "0a09fa91",
name: "Many short",
metadata: Array.from({ length: 8 }, (_, i) => ({
...short,
key: `Short ${i}`,
})),
},
{
...nullDevice,
id: "d0b9eb9d",
name: "Many long",
metadata: Array.from({ length: 4 }, (_, i) => ({
...long,
key: `Long ${i}`,
})),
},
{
...nullDevice,
id: "3af84e31",
name: "Many really long",
metadata: Array.from({ length: 8 }, (_, i) => ({
...reallyLong,
key: `Really long ${i}`,
})),
},
{
...nullDevice,
id: "d0b9eb9d",
name: "Couple long",
metadata: Array.from({ length: 2 }, (_, i) => ({
...long,
key: `Long ${i}`,
})),
},
{
...nullDevice,
id: "a6c69587",
name: "Short and long",
metadata: Array.from({ length: 8 }, (_, i) =>
i % 2 === 0
? { ...short, key: `Short ${i}` }
: { ...long, key: `Long ${i}` },
),
},
],
agentRow: getAgentRow,
},
};
function getAgentRow(agent: WorkspaceAgent): JSX.Element {
return (
<ProxyContext.Provider
value={{
proxyLatencies: MockProxyLatencies,
proxy: getPreferredProxy([], undefined),
proxies: [],
isLoading: false,
isFetched: true,
setProxy: () => {
return;
},
clearProxy: () => {
return;
},
refetchProxyLatencies: (): Date => {
return new Date();
},
}}
>
<AgentRow
showApps
key={agent.id}
agent={agent}
workspace={MockWorkspace}
serverVersion=""
onUpdateAgent={action("updateAgent")}
/>
</ProxyContext.Provider>
);
}

View File

@ -4,7 +4,7 @@ import {
MockTemplateVersion,
MockTemplateVersion3,
MockWorkspaceResource,
MockWorkspaceResource2,
MockWorkspaceVolumeResource,
} from "testHelpers/entities";
import { TemplateSummaryPageView } from "./TemplateSummaryPageView";
@ -20,7 +20,7 @@ export const Example: Story = {
args: {
template: MockTemplate,
activeVersion: MockTemplateVersion,
resources: [MockWorkspaceResource, MockWorkspaceResource2],
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
},
};
@ -28,7 +28,7 @@ export const NoIcon: Story = {
args: {
template: { ...MockTemplate, icon: "" },
activeVersion: MockTemplateVersion,
resources: [MockWorkspaceResource, MockWorkspaceResource2],
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
},
};
@ -49,7 +49,7 @@ export const SmallViewport: Story = {
\`\`\`
`,
},
resources: [MockWorkspaceResource, MockWorkspaceResource2],
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
},
};
@ -61,6 +61,6 @@ export const WithDeprecatedParameters: Story = {
args: {
template: MockTemplate,
activeVersion: MockTemplateVersion3,
resources: [MockWorkspaceResource, MockWorkspaceResource2],
resources: [MockWorkspaceResource, MockWorkspaceVolumeResource],
},
};

View File

@ -3,10 +3,13 @@ import {
MockTemplateVersion,
MockTemplateVersionFileTree,
MockWorkspaceBuildLogs,
MockWorkspaceContainerResource,
MockWorkspaceExtendedBuildLogs,
MockWorkspaceImageResource,
MockWorkspaceResource,
MockWorkspaceResource2,
MockWorkspaceResource3,
MockWorkspaceResourceMultipleAgents,
MockWorkspaceResourceSensitive,
MockWorkspaceVolumeResource,
} from "testHelpers/entities";
import { TemplateVersionEditor } from "./TemplateVersionEditor";
import type { Meta, StoryObj } from "@storybook/react";
@ -40,8 +43,11 @@ export const Resources: Story = {
buildLogs: MockWorkspaceBuildLogs,
resources: [
MockWorkspaceResource,
MockWorkspaceResource2,
MockWorkspaceResource3,
MockWorkspaceResourceSensitive,
MockWorkspaceResourceMultipleAgents,
MockWorkspaceVolumeResource,
MockWorkspaceImageResource,
MockWorkspaceContainerResource,
],
},
};

View File

@ -80,9 +80,10 @@ export const Running: Story = {
handleStart: action("start"),
handleStop: action("stop"),
resources: [
Mocks.MockWorkspaceResource,
Mocks.MockWorkspaceResource2,
Mocks.MockWorkspaceResource3,
Mocks.MockWorkspaceResourceMultipleAgents,
Mocks.MockWorkspaceVolumeResource,
Mocks.MockWorkspaceImageResource,
Mocks.MockWorkspaceContainerResource,
],
builds: [Mocks.MockWorkspaceBuild],
canUpdateWorkspace: true,

View File

@ -718,57 +718,78 @@ export const MockWorkspaceAgentOff: TypesGen.WorkspaceAgent = {
};
export const MockWorkspaceResource: TypesGen.WorkspaceResource = {
agents: [
MockWorkspaceAgent,
MockWorkspaceAgentConnecting,
MockWorkspaceAgentOutdated,
],
created_at: "",
id: "test-workspace-resource",
job_id: "",
name: "a-workspace-resource",
agents: [MockWorkspaceAgent],
created_at: "",
job_id: "",
type: "google_compute_disk",
workspace_transition: "start",
hide: false,
icon: "",
metadata: [{ key: "size", value: "32GB", sensitive: false }],
daily_cost: 10,
};
export const MockWorkspaceResourceSensitive: TypesGen.WorkspaceResource = {
...MockWorkspaceResource,
id: "test-workspace-resource-sensitive",
name: "workspace-resource-sensitive",
metadata: [{ key: "api_key", value: "12345678", sensitive: true }],
daily_cost: 10,
};
export const MockWorkspaceResource2: TypesGen.WorkspaceResource = {
export const MockWorkspaceResourceMultipleAgents: TypesGen.WorkspaceResource = {
...MockWorkspaceResource,
id: "test-workspace-resource-multiple-agents",
name: "workspace-resource-multiple-agents",
agents: [
MockWorkspaceAgent,
MockWorkspaceAgentDisconnected,
MockWorkspaceAgentOutdated,
],
};
export const MockWorkspaceResourceHidden: TypesGen.WorkspaceResource = {
...MockWorkspaceResource,
id: "test-workspace-resource-hidden",
name: "workspace-resource-hidden",
hide: true,
};
export const MockWorkspaceVolumeResource: TypesGen.WorkspaceResource = {
id: "test-workspace-volume-resource",
created_at: "",
id: "test-workspace-resource-2",
job_id: "",
name: "another-workspace-resource",
type: "google_compute_disk",
workspace_transition: "start",
type: "docker_volume",
name: "home_volume",
hide: false,
icon: "",
metadata: [{ key: "size", value: "32GB", sensitive: false }],
daily_cost: 10,
daily_cost: 0,
};
export const MockWorkspaceResource3: TypesGen.WorkspaceResource = {
agents: [
MockWorkspaceAgent,
MockWorkspaceAgentDisconnected,
MockWorkspaceAgentOutdated,
],
export const MockWorkspaceImageResource: TypesGen.WorkspaceResource = {
id: "test-workspace-image-resource",
created_at: "",
id: "test-workspace-resource-3",
job_id: "",
name: "another-workspace-resource",
type: "google_compute_disk",
workspace_transition: "start",
hide: true,
type: "docker_image",
name: "main",
hide: false,
icon: "",
metadata: [{ key: "size", value: "32GB", sensitive: false }],
daily_cost: 20,
daily_cost: 0,
};
export const MockWorkspaceContainerResource: TypesGen.WorkspaceResource = {
id: "test-workspace-container-resource",
created_at: "",
job_id: "",
workspace_transition: "start",
type: "docker_container",
name: "workspace",
hide: false,
icon: "",
daily_cost: 0,
};
export const MockWorkspaceAutostartDisabled: TypesGen.UpdateWorkspaceAutostartRequest =

View File

@ -96,7 +96,12 @@ export const handlers = [
async (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([M.MockWorkspaceResource, M.MockWorkspaceResource2]),
ctx.json([
M.MockWorkspaceResource,
M.MockWorkspaceVolumeResource,
M.MockWorkspaceImageResource,
M.MockWorkspaceContainerResource,
]),
);
},
),
@ -254,7 +259,12 @@ export const handlers = [
(req, res, ctx) => {
return res(
ctx.status(200),
ctx.json([M.MockWorkspaceResource, M.MockWorkspaceResource2]),
ctx.json([
M.MockWorkspaceResource,
M.MockWorkspaceVolumeResource,
M.MockWorkspaceImageResource,
M.MockWorkspaceContainerResource,
]),
);
},
),