diff --git a/site/src/components/Resources/ResourceCard.stories.tsx b/site/src/components/Resources/ResourceCard.stories.tsx index d1be96424275a..2759ab4bd5230 100644 --- a/site/src/components/Resources/ResourceCard.stories.tsx +++ b/site/src/components/Resources/ResourceCard.stories.tsx @@ -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 = { - title: "components/ResourceCard", + title: "components/Resources/ResourceCard", component: ResourceCard, args: { resource: MockWorkspaceResource, - agentRow: (agent) => ( - { - return; - }, - clearProxy: () => { - return; - }, - refetchProxyLatencies: (): Date => { - return new Date(); - }, - }} - > - - - ), + agentRow: getAgentRow, }, }; @@ -96,34 +69,38 @@ export const BunchOfMetadata: Story = { }, ], }, - agentRow: (agent) => ( - { - return; - }, - clearProxy: () => { - return; - }, - refetchProxyLatencies: (): Date => { - return new Date(); - }, - }} - > - - - ), + agentRow: getAgentRow, }, }; + +function getAgentRow(agent: WorkspaceAgent): JSX.Element { + return ( + { + return; + }, + clearProxy: () => { + return; + }, + refetchProxyLatencies: (): Date => { + return new Date(); + }, + }} + > + + + ); +} diff --git a/site/src/components/Resources/ResourceCard.tsx b/site/src/components/Resources/ResourceCard.tsx index f6cec21e5669e..44dcf881462fa 100644 --- a/site/src/components/Resources/ResourceCard.tsx +++ b/site/src/components/Resources/ResourceCard.tsx @@ -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 = ({ 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 (
= ({ resource, agentRow }) => {
- -
- {resource.daily_cost > 0 && ( -
-
- cost -
+
+ {resource.daily_cost > 0 && ( +
+
+ cost +
+
{resource.daily_cost}
+
+ )} + {visibleMetadata.map((meta) => { + return ( +
+
{meta.key}
- {resource.daily_cost} + {meta.sensitive ? ( + + ) : ( + + {meta.value} + + )}
- )} - {visibleMetadata.map((meta) => { - return ( -
-
{meta.key}
-
- {meta.sensitive ? ( - - ) : ( - - {meta.value} - - )} -
-
- ); - })} -
- - 4}> - + {metadataLength > 4 && ( + + { + setShouldDisplayAllMetadata((value) => !value); + }} + size="large" > - { - setShouldDisplayAllMetadata((value) => !value); - }} - size="large" - > - {shouldDisplayAllMetadata ? ( - - ) : ( - - )} - - - - + {shouldDisplayAllMetadata ? ( + + ) : ( + + )} + + + )} {resource.agents && resource.agents.length > 0 && ( @@ -109,7 +112,7 @@ export const ResourceCard: FC = ({ resource, agentRow }) => { ); }; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((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, diff --git a/site/src/components/Resources/Resources.stories.tsx b/site/src/components/Resources/Resources.stories.tsx new file mode 100644 index 0000000000000..7e107b79ad315 --- /dev/null +++ b/site/src/components/Resources/Resources.stories.tsx @@ -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 = { + title: "components/Resources/Resources", + component: Resources, + args: { + resources: [MockWorkspaceResource], + agentRow: getAgentRow, + }, +}; + +export default meta; +type Story = StoryObj; + +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 ( + { + return; + }, + clearProxy: () => { + return; + }, + refetchProxyLatencies: (): Date => { + return new Date(); + }, + }} + > + + + ); +} diff --git a/site/src/pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPageView.stories.tsx b/site/src/pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPageView.stories.tsx index 46d2e8adc7c19..1cca2f6a7ac9d 100644 --- a/site/src/pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPageView.stories.tsx +++ b/site/src/pages/TemplatePage/TemplateSummaryPage/TemplateSummaryPageView.stories.tsx @@ -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], }, }; diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx index 0789e985faef3..bc40bf5cd38ef 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx @@ -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, ], }, }; diff --git a/site/src/pages/WorkspacePage/Workspace.stories.tsx b/site/src/pages/WorkspacePage/Workspace.stories.tsx index dddbf55a2d2b5..046daf7cb788d 100644 --- a/site/src/pages/WorkspacePage/Workspace.stories.tsx +++ b/site/src/pages/WorkspacePage/Workspace.stories.tsx @@ -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, diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index ace4333128026..6e048e22bd735 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -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: "api_key", value: "12345678", sensitive: true }], + metadata: [{ key: "size", value: "32GB", sensitive: false }], daily_cost: 10, }; -export const MockWorkspaceResource2: TypesGen.WorkspaceResource = { +export const MockWorkspaceResourceSensitive: TypesGen.WorkspaceResource = { + ...MockWorkspaceResource, + id: "test-workspace-resource-sensitive", + name: "workspace-resource-sensitive", + metadata: [{ key: "api_key", value: "12345678", sensitive: true }], +}; + +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 = diff --git a/site/src/testHelpers/handlers.ts b/site/src/testHelpers/handlers.ts index 7b3cde88d0ca4..21e580faa2458 100644 --- a/site/src/testHelpers/handlers.ts +++ b/site/src/testHelpers/handlers.ts @@ -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, + ]), ); }, ),