mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
chore(site): refactor stories and tests from components directory (#9578)
* Refactor Alert * Refactor Avatar and its stories * Refactor AvatarData and its stories * Refactor CodeExample and its tests * Refactor ServiceBanner stories * Refactor Navbar and its tests * Refactor ServiceBanner stories * Refactor LicenseBannerView stories * Refactor DeploymentBannerView stories * Extract optionValue into a module * Refactor DeleteDialog stories * Refactor ConfirmDialog tests * Refactor EmptyState tests * Flat ErrorBoundaryState and refactor stories * Refactor Expander stories * Refactor FormFooter stories * Refactor FullPageForm stories * Refactor EnterpriseSnackbar stories * Refactor GroupAvatar stories * Refactor HelpTooltip stories and remove index * Remove unecessary types module from IconField * Refactor LoadingButton stories * Refactor Margins stories * Refactor Markdown stories * Refactor PageHeader stories * Refactor PageButton tests * Refactor Pill stories * Refactor Resources stories * Refactor RichParameterInput stories and flat MultiTextField * Remove unecessary Stack story * Refactor TableRowMenu stories * Refactor TemplateLayout stories * Refactor Typography props * Refactor UserAutocomplete * Refactor WorkspaceBuildLogs components and tests * Refactor WorkspaceStatusBadge stories * Fix wrong imports * Remove Example.args pattern * Fix wrong import * Refactor EmptyState stories * Refactor HelpTooltip stories * Remove not valid ErrorAlert story * Fix AvatarData story * Add border back to CodeExample * Fix Navbar story * Fix AgentRow proxy in the stories
This commit is contained in:
@ -1,6 +1,5 @@
|
|||||||
import { Alert } from "./Alert";
|
import { Alert } from "./Alert";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Link from "@mui/material/Link";
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
const meta: Meta<typeof Alert> = {
|
const meta: Meta<typeof Alert> = {
|
||||||
@ -21,7 +20,6 @@ export const Success: Story = {
|
|||||||
args: {
|
args: {
|
||||||
children: "You're doing great!",
|
children: "You're doing great!",
|
||||||
severity: "success",
|
severity: "success",
|
||||||
onRetry: undefined,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -56,14 +54,3 @@ export const WarningWithActionAndDismiss: Story = {
|
|||||||
severity: "warning",
|
severity: "warning",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithChildren: Story = {
|
|
||||||
args: {
|
|
||||||
severity: "warning",
|
|
||||||
children: (
|
|
||||||
<div>
|
|
||||||
This is a message with a <Link href="#">link</Link>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
@ -8,14 +8,12 @@ import Box from "@mui/material/Box";
|
|||||||
export type AlertProps = MuiAlertProps & {
|
export type AlertProps = MuiAlertProps & {
|
||||||
actions?: ReactNode;
|
actions?: ReactNode;
|
||||||
dismissible?: boolean;
|
dismissible?: boolean;
|
||||||
onRetry?: () => void;
|
|
||||||
onDismiss?: () => void;
|
onDismiss?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Alert: FC<AlertProps> = ({
|
export const Alert: FC<AlertProps> = ({
|
||||||
children,
|
children,
|
||||||
actions,
|
actions,
|
||||||
onRetry,
|
|
||||||
dismissible,
|
dismissible,
|
||||||
severity,
|
severity,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
@ -34,13 +32,6 @@ export const Alert: FC<AlertProps> = ({
|
|||||||
{/* CTAs passed in by the consumer */}
|
{/* CTAs passed in by the consumer */}
|
||||||
{actions}
|
{actions}
|
||||||
|
|
||||||
{/* retry CTA */}
|
|
||||||
{onRetry && (
|
|
||||||
<Button variant="text" size="small" onClick={onRetry}>
|
|
||||||
Retry
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* close CTA */}
|
{/* close CTA */}
|
||||||
{dismissible && (
|
{dismissible && (
|
||||||
<Button
|
<Button
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import { mockApiError } from "testHelpers/entities";
|
import { mockApiError } from "testHelpers/entities";
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { action } from "@storybook/addon-actions";
|
|
||||||
import { ErrorAlert } from "./ErrorAlert";
|
import { ErrorAlert } from "./ErrorAlert";
|
||||||
|
|
||||||
const mockError = mockApiError({
|
const mockError = mockApiError({
|
||||||
@ -15,7 +14,6 @@ const meta: Meta<typeof ErrorAlert> = {
|
|||||||
args: {
|
args: {
|
||||||
error: mockError,
|
error: mockError,
|
||||||
dismissible: false,
|
dismissible: false,
|
||||||
onRetry: undefined,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,21 +53,6 @@ export const WithActionAndDismiss: Story = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithRetry: Story = {
|
|
||||||
args: {
|
|
||||||
onRetry: action("retry"),
|
|
||||||
dismissible: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithActionRetryAndDismiss: Story = {
|
|
||||||
args: {
|
|
||||||
actions: [ExampleAction],
|
|
||||||
onRetry: action("retry"),
|
|
||||||
dismissible: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithNonApiError: Story = {
|
export const WithNonApiError: Story = {
|
||||||
args: {
|
args: {
|
||||||
error: new Error("Non API error here"),
|
error: new Error("Non API error here"),
|
||||||
|
@ -1,63 +1,71 @@
|
|||||||
import { Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { Avatar, AvatarIcon, AvatarProps } from "./Avatar";
|
import { Avatar, AvatarIcon } from "./Avatar";
|
||||||
import PauseIcon from "@mui/icons-material/PauseOutlined";
|
import PauseIcon from "@mui/icons-material/PauseOutlined";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof Avatar> = {
|
||||||
title: "components/Avatar",
|
title: "components/Avatar",
|
||||||
component: Avatar,
|
component: Avatar,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<AvatarProps> = (args: AvatarProps) => (
|
export default meta;
|
||||||
<Avatar {...args} />
|
type Story = StoryObj<typeof Avatar>;
|
||||||
);
|
|
||||||
|
|
||||||
export const Letter = Template.bind({});
|
export const Letter: Story = {
|
||||||
Letter.args = {
|
args: {
|
||||||
children: "Coder",
|
children: "Coder",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LetterXL = Template.bind({});
|
export const LetterXL = {
|
||||||
LetterXL.args = {
|
args: {
|
||||||
children: "Coder",
|
children: "Coder",
|
||||||
size: "xl",
|
size: "xl",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LetterDarken = Template.bind({});
|
export const LetterDarken = {
|
||||||
LetterDarken.args = {
|
args: {
|
||||||
children: "Coder",
|
children: "Coder",
|
||||||
colorScheme: "darken",
|
colorScheme: "darken",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Image = Template.bind({});
|
export const Image = {
|
||||||
Image.args = {
|
args: {
|
||||||
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImageXL = Template.bind({});
|
export const ImageXL = {
|
||||||
ImageXL.args = {
|
args: {
|
||||||
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||||
size: "xl",
|
size: "xl",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MuiIcon = Template.bind({});
|
export const MuiIcon = {
|
||||||
MuiIcon.args = {
|
args: {
|
||||||
children: <PauseIcon />,
|
children: <PauseIcon />,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MuiIconDarken = Template.bind({});
|
export const MuiIconDarken = {
|
||||||
MuiIconDarken.args = {
|
args: {
|
||||||
children: <PauseIcon />,
|
children: <PauseIcon />,
|
||||||
colorScheme: "darken",
|
colorScheme: "darken",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MuiIconXL = Template.bind({});
|
export const MuiIconXL = {
|
||||||
MuiIconXL.args = {
|
args: {
|
||||||
children: <PauseIcon />,
|
children: <PauseIcon />,
|
||||||
size: "xl",
|
size: "xl",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvatarIconDarken = Template.bind({});
|
export const AvatarIconDarken = {
|
||||||
AvatarIconDarken.args = {
|
args: {
|
||||||
children: <AvatarIcon src="/icon/database.svg" />,
|
children: <AvatarIcon src="/icon/database.svg" />,
|
||||||
colorScheme: "darken",
|
colorScheme: "darken",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,6 @@ import MuiAvatar, { AvatarProps as MuiAvatarProps } from "@mui/material/Avatar";
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
import { combineClasses } from "utils/combineClasses";
|
||||||
import { firstLetter } from "./firstLetter";
|
|
||||||
|
|
||||||
export type AvatarProps = MuiAvatarProps & {
|
export type AvatarProps = MuiAvatarProps & {
|
||||||
size?: "sm" | "md" | "xl";
|
size?: "sm" | "md" | "xl";
|
||||||
@ -32,7 +31,6 @@ export const Avatar: FC<AvatarProps> = ({
|
|||||||
fitImage && styles.fitImage,
|
fitImage && styles.fitImage,
|
||||||
])}
|
])}
|
||||||
>
|
>
|
||||||
{/* If the children is a string, we always want to render the first letter */}
|
|
||||||
{typeof children === "string" ? firstLetter(children) : children}
|
{typeof children === "string" ? firstLetter(children) : children}
|
||||||
</MuiAvatar>
|
</MuiAvatar>
|
||||||
);
|
);
|
||||||
@ -46,6 +44,14 @@ export const AvatarIcon: FC<{ src: string }> = ({ src }) => {
|
|||||||
return <img src={src} alt="" className={styles.avatarIcon} />;
|
return <img src={src} alt="" className={styles.avatarIcon} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const firstLetter = (str: string): string => {
|
||||||
|
if (str.length > 0) {
|
||||||
|
return str[0].toLocaleUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
// Size styles
|
// Size styles
|
||||||
sm: {
|
sm: {
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import { firstLetter } from "./firstLetter";
|
|
||||||
|
|
||||||
describe("first-letter", () => {
|
|
||||||
it.each<[string, string]>([
|
|
||||||
["", ""],
|
|
||||||
["User", "U"],
|
|
||||||
["test", "T"],
|
|
||||||
])(`firstLetter(%p) returns %p`, (input, expected) => {
|
|
||||||
expect(firstLetter(input)).toBe(expected);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* firstLetter extracts the first character and returns it, uppercased.
|
|
||||||
*/
|
|
||||||
export const firstLetter = (str: string): string => {
|
|
||||||
if (str.length > 0) {
|
|
||||||
return str[0].toLocaleUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
};
|
|
@ -1,24 +1,22 @@
|
|||||||
import { Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { AvatarData, AvatarDataProps } from "./AvatarData";
|
import { AvatarData } from "./AvatarData";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof AvatarData> = {
|
||||||
title: "components/AvatarData",
|
title: "components/AvatarData",
|
||||||
component: AvatarData,
|
component: AvatarData,
|
||||||
|
args: {
|
||||||
|
title: "coder",
|
||||||
|
subtitle: "coder@coder.com",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<AvatarDataProps> = (args: AvatarDataProps) => (
|
export default meta;
|
||||||
<AvatarData {...args} />
|
type Story = StoryObj<typeof AvatarData>;
|
||||||
);
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const WithTitleAndSubtitle: Story = {};
|
||||||
Example.args = {
|
|
||||||
title: "coder",
|
|
||||||
subtitle: "coder@coder.com",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithImage = Template.bind({});
|
export const WithImage: Story = {
|
||||||
WithImage.args = {
|
args: {
|
||||||
title: "coder",
|
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
||||||
subtitle: "coder@coder.com",
|
},
|
||||||
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
|
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Avatar } from "components/Avatar/Avatar";
|
import { Avatar } from "components/Avatar/Avatar";
|
||||||
import { FC, PropsWithChildren } from "react";
|
import { FC } from "react";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ export interface AvatarDataProps {
|
|||||||
avatar?: React.ReactNode;
|
avatar?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AvatarData: FC<PropsWithChildren<AvatarDataProps>> = ({
|
export const AvatarData: FC<AvatarDataProps> = ({
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
src,
|
src,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { CodeExample, CodeExampleProps } from "./CodeExample";
|
import { CodeExample } from "./CodeExample";
|
||||||
|
|
||||||
const sampleCode = `echo "Hello, world"`;
|
const sampleCode = `echo "Hello, world"`;
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof CodeExample> = {
|
||||||
title: "components/CodeExample",
|
title: "components/CodeExample",
|
||||||
component: CodeExample,
|
component: CodeExample,
|
||||||
argTypes: {
|
argTypes: {
|
||||||
@ -11,16 +11,17 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<CodeExampleProps> = (args: CodeExampleProps) => (
|
export default meta;
|
||||||
<CodeExample {...args} />
|
type Story = StoryObj<typeof CodeExample>;
|
||||||
);
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const Example: Story = {
|
||||||
Example.args = {
|
args: {
|
||||||
code: sampleCode,
|
code: sampleCode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LongCode = Template.bind({});
|
export const LongCode: Story = {
|
||||||
LongCode.args = {
|
args: {
|
||||||
code: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICnKzATuWwmmt5+CKTPuRGN0R1PBemA+6/SStpLiyX+L",
|
code: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICnKzATuWwmmt5+CKTPuRGN0R1PBemA+6/SStpLiyX+L",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { screen } from "@testing-library/react";
|
|
||||||
import { render } from "../../testHelpers/renderHelpers";
|
|
||||||
import { CodeExample } from "./CodeExample";
|
|
||||||
|
|
||||||
describe("CodeExample", () => {
|
|
||||||
it("renders code", async () => {
|
|
||||||
// When
|
|
||||||
render(<CodeExample code="echo hello" />);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
// Both lines should be rendered
|
|
||||||
await screen.findByText("echo hello");
|
|
||||||
});
|
|
||||||
});
|
|
@ -7,33 +7,24 @@ import { Theme } from "@mui/material/styles";
|
|||||||
|
|
||||||
export interface CodeExampleProps {
|
export interface CodeExampleProps {
|
||||||
code: string;
|
code: string;
|
||||||
className?: string;
|
|
||||||
buttonClassName?: string;
|
|
||||||
tooltipTitle?: string;
|
|
||||||
inline?: boolean;
|
|
||||||
password?: boolean;
|
password?: boolean;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to show single-line code examples, with a copy button
|
* Component to show single-line code examples, with a copy button
|
||||||
*/
|
*/
|
||||||
export const CodeExample: FC<React.PropsWithChildren<CodeExampleProps>> = ({
|
export const CodeExample: FC<CodeExampleProps> = ({
|
||||||
code,
|
code,
|
||||||
|
password,
|
||||||
className,
|
className,
|
||||||
buttonClassName,
|
|
||||||
tooltipTitle,
|
|
||||||
inline,
|
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles({ inline: inline });
|
const styles = useStyles({ password });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={combineClasses([styles.root, className])}>
|
<div className={combineClasses([styles.root, className])}>
|
||||||
<code className={styles.code}>{code}</code>
|
<code className={styles.code}>{code}</code>
|
||||||
<CopyButton
|
<CopyButton text={code} />
|
||||||
text={code}
|
|
||||||
tooltipTitle={tooltipTitle}
|
|
||||||
buttonClassName={buttonClassName}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -48,14 +39,14 @@ const useStyles = makeStyles<Theme, styleProps>((theme) => ({
|
|||||||
display: props.inline ? "inline-flex" : "flex",
|
display: props.inline ? "inline-flex" : "flex",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
background: props.inline ? "rgb(0 0 0 / 30%)" : "hsl(223, 27%, 3%)",
|
background: "rgb(0 0 0 / 30%)",
|
||||||
border: props.inline ? undefined : `1px solid ${theme.palette.divider}`,
|
|
||||||
color: theme.palette.primary.contrastText,
|
color: theme.palette.primary.contrastText,
|
||||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
borderRadius: theme.shape.borderRadius,
|
borderRadius: theme.shape.borderRadius,
|
||||||
padding: theme.spacing(1),
|
padding: theme.spacing(1),
|
||||||
lineHeight: "150%",
|
lineHeight: "150%",
|
||||||
|
border: `1px solid ${theme.palette.divider}`,
|
||||||
}),
|
}),
|
||||||
code: {
|
code: {
|
||||||
padding: theme.spacing(0, 1),
|
padding: theme.spacing(0, 1),
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
import { Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { MockDeploymentStats } from "testHelpers/entities";
|
import { MockDeploymentStats } from "testHelpers/entities";
|
||||||
import {
|
import { DeploymentBannerView } from "./DeploymentBannerView";
|
||||||
DeploymentBannerView,
|
|
||||||
DeploymentBannerViewProps,
|
|
||||||
} from "./DeploymentBannerView";
|
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof DeploymentBannerView> = {
|
||||||
title: "components/DeploymentBannerView",
|
title: "components/DeploymentBannerView",
|
||||||
component: DeploymentBannerView,
|
component: DeploymentBannerView,
|
||||||
|
args: {
|
||||||
|
stats: MockDeploymentStats,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<DeploymentBannerViewProps> = (args) => (
|
export default meta;
|
||||||
<DeploymentBannerView {...args} />
|
type Story = StoryObj<typeof DeploymentBannerView>;
|
||||||
);
|
|
||||||
|
|
||||||
export const Preview = Template.bind({});
|
export const Preview: Story = {};
|
||||||
Preview.args = {
|
|
||||||
stats: MockDeploymentStats,
|
|
||||||
};
|
|
||||||
|
@ -1,34 +1,36 @@
|
|||||||
import { Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { LicenseBannerView, LicenseBannerViewProps } from "./LicenseBannerView";
|
import { LicenseBannerView } from "./LicenseBannerView";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof LicenseBannerView> = {
|
||||||
title: "components/LicenseBannerView",
|
title: "components/LicenseBannerView",
|
||||||
component: LicenseBannerView,
|
component: LicenseBannerView,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<LicenseBannerViewProps> = (args) => (
|
export default meta;
|
||||||
<LicenseBannerView {...args} />
|
type Story = StoryObj<typeof LicenseBannerView>;
|
||||||
);
|
|
||||||
|
|
||||||
export const OneWarning = Template.bind({});
|
export const OneWarning: Story = {
|
||||||
OneWarning.args = {
|
args: {
|
||||||
errors: [],
|
errors: [],
|
||||||
warnings: ["You have exceeded the number of seats in your license."],
|
warnings: ["You have exceeded the number of seats in your license."],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TwoWarnings = Template.bind({});
|
export const TwoWarnings: Story = {
|
||||||
TwoWarnings.args = {
|
args: {
|
||||||
errors: [],
|
errors: [],
|
||||||
warnings: [
|
warnings: [
|
||||||
"You have exceeded the number of seats in your license.",
|
"You have exceeded the number of seats in your license.",
|
||||||
"You are flying too close to the sun.",
|
"You are flying too close to the sun.",
|
||||||
],
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OneError = Template.bind({});
|
export const OneError: Story = {
|
||||||
OneError.args = {
|
args: {
|
||||||
errors: [
|
errors: [
|
||||||
"You have multiple replicas but high availability is an Enterprise feature. You will be unable to connect to workspaces.",
|
"You have multiple replicas but high availability is an Enterprise feature. You will be unable to connect to workspaces.",
|
||||||
],
|
],
|
||||||
warnings: [],
|
warnings: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,45 +1,34 @@
|
|||||||
import { Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { MockUser, MockUser2 } from "../../../testHelpers/entities";
|
import { MockUser, MockUser2 } from "../../../testHelpers/entities";
|
||||||
import { NavbarView, NavbarViewProps } from "./NavbarView";
|
import { NavbarView } from "./NavbarView";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof NavbarView> = {
|
||||||
title: "components/NavbarView",
|
title: "components/NavbarView",
|
||||||
component: NavbarView,
|
component: NavbarView,
|
||||||
argTypes: {
|
args: {
|
||||||
onSignOut: { action: "Sign Out" },
|
user: MockUser,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<NavbarViewProps> = (args: NavbarViewProps) => (
|
export default meta;
|
||||||
<NavbarView {...args} />
|
type Story = StoryObj<typeof NavbarView>;
|
||||||
);
|
|
||||||
|
|
||||||
export const ForAdmin = Template.bind({});
|
export const ForAdmin: Story = {};
|
||||||
ForAdmin.args = {
|
|
||||||
user: MockUser,
|
export const ForMember: Story = {
|
||||||
onSignOut: () => {
|
args: {
|
||||||
return Promise.resolve();
|
user: MockUser2,
|
||||||
|
canViewAuditLog: false,
|
||||||
|
canViewDeployment: false,
|
||||||
|
canViewAllUsers: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ForMember = Template.bind({});
|
export const SmallViewport: Story = {
|
||||||
ForMember.args = {
|
parameters: {
|
||||||
user: MockUser2,
|
viewport: {
|
||||||
onSignOut: () => {
|
defaultViewport: "tablet",
|
||||||
return Promise.resolve();
|
},
|
||||||
|
chromatic: { viewports: [420] },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SmallViewport = Template.bind({});
|
|
||||||
SmallViewport.args = {
|
|
||||||
user: MockUser,
|
|
||||||
onSignOut: () => {
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
SmallViewport.parameters = {
|
|
||||||
viewport: {
|
|
||||||
defaultViewport: "tablet",
|
|
||||||
},
|
|
||||||
chromatic: { viewports: [420] },
|
|
||||||
};
|
|
||||||
|
@ -2,7 +2,6 @@ import { screen } from "@testing-library/react";
|
|||||||
import {
|
import {
|
||||||
MockPrimaryWorkspaceProxy,
|
MockPrimaryWorkspaceProxy,
|
||||||
MockUser,
|
MockUser,
|
||||||
MockUser2,
|
|
||||||
} from "../../../testHelpers/entities";
|
} from "../../../testHelpers/entities";
|
||||||
import { renderWithAuth } from "../../../testHelpers/renderHelpers";
|
import { renderWithAuth } from "../../../testHelpers/renderHelpers";
|
||||||
import { Language as navLanguage, NavbarView } from "./NavbarView";
|
import { Language as navLanguage, NavbarView } from "./NavbarView";
|
||||||
@ -28,18 +27,6 @@ describe("NavbarView", () => {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const env = process.env;
|
|
||||||
|
|
||||||
// REMARK: copying process.env so we don't mutate that object or encounter conflicts between tests
|
|
||||||
beforeEach(() => {
|
|
||||||
process.env = { ...env };
|
|
||||||
});
|
|
||||||
|
|
||||||
// REMARK: restoring process.env
|
|
||||||
afterEach(() => {
|
|
||||||
process.env = env;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("workspaces nav link has the correct href", async () => {
|
it("workspaces nav link has the correct href", async () => {
|
||||||
renderWithAuth(
|
renderWithAuth(
|
||||||
<NavbarView
|
<NavbarView
|
||||||
@ -85,32 +72,6 @@ describe("NavbarView", () => {
|
|||||||
expect((userLink as HTMLAnchorElement).href).toContain("/users");
|
expect((userLink as HTMLAnchorElement).href).toContain("/users");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders profile picture for user", async () => {
|
|
||||||
// Given
|
|
||||||
const mockUser = {
|
|
||||||
...MockUser,
|
|
||||||
username: "bryan",
|
|
||||||
avatar_url: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
// When
|
|
||||||
renderWithAuth(
|
|
||||||
<NavbarView
|
|
||||||
proxyContextValue={proxyContextValue}
|
|
||||||
user={mockUser}
|
|
||||||
onSignOut={noop}
|
|
||||||
canViewAuditLog
|
|
||||||
canViewDeployment
|
|
||||||
canViewAllUsers
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
// There should be a 'B' avatar!
|
|
||||||
const element = await screen.findByText("B");
|
|
||||||
expect(element).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("audit nav link has the correct href", async () => {
|
it("audit nav link has the correct href", async () => {
|
||||||
renderWithAuth(
|
renderWithAuth(
|
||||||
<NavbarView
|
<NavbarView
|
||||||
@ -126,21 +87,6 @@ describe("NavbarView", () => {
|
|||||||
expect((auditLink as HTMLAnchorElement).href).toContain("/audit");
|
expect((auditLink as HTMLAnchorElement).href).toContain("/audit");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("audit nav link is hidden for members", async () => {
|
|
||||||
renderWithAuth(
|
|
||||||
<NavbarView
|
|
||||||
proxyContextValue={proxyContextValue}
|
|
||||||
user={MockUser2}
|
|
||||||
onSignOut={noop}
|
|
||||||
canViewAuditLog={false}
|
|
||||||
canViewDeployment
|
|
||||||
canViewAllUsers
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const auditLink = screen.queryByText(navLanguage.audit);
|
|
||||||
expect(auditLink).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("deployment nav link has the correct href", async () => {
|
it("deployment nav link has the correct href", async () => {
|
||||||
renderWithAuth(
|
renderWithAuth(
|
||||||
<NavbarView
|
<NavbarView
|
||||||
@ -157,19 +103,4 @@ describe("NavbarView", () => {
|
|||||||
"/deployment/general",
|
"/deployment/general",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deployment nav link is hidden for members", async () => {
|
|
||||||
renderWithAuth(
|
|
||||||
<NavbarView
|
|
||||||
proxyContextValue={proxyContextValue}
|
|
||||||
user={MockUser2}
|
|
||||||
onSignOut={noop}
|
|
||||||
canViewAuditLog={false}
|
|
||||||
canViewDeployment={false}
|
|
||||||
canViewAllUsers
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const auditLink = screen.queryByText(navLanguage.deployment);
|
|
||||||
expect(auditLink).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -3,8 +3,8 @@ import { makeStyles } from "@mui/styles";
|
|||||||
import CheckIcon from "@mui/icons-material/Check";
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import { ellipsizeText } from "../../../../../utils/ellipsizeText";
|
import { ellipsizeText } from "utils/ellipsizeText";
|
||||||
import { Typography } from "../../../../Typography/Typography";
|
import { Typography } from "components/Typography/Typography";
|
||||||
|
|
||||||
type BorderedMenuRowVariant = "narrow" | "wide";
|
type BorderedMenuRowVariant = "narrow" | "wide";
|
||||||
|
|
@ -1,26 +1,16 @@
|
|||||||
import Box from "@mui/material/Box";
|
|
||||||
import { Story } from "@storybook/react";
|
|
||||||
import { MockUser } from "../../../../testHelpers/entities";
|
import { MockUser } from "../../../../testHelpers/entities";
|
||||||
import { UserDropdown, UserDropdownProps } from "./UserDropdown";
|
import { UserDropdown } from "./UserDropdown";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof UserDropdown> = {
|
||||||
title: "components/UserDropdown",
|
title: "components/UserDropdown",
|
||||||
component: UserDropdown,
|
component: UserDropdown,
|
||||||
argTypes: {
|
args: {
|
||||||
onSignOut: { action: "Sign Out" },
|
user: MockUser,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<UserDropdownProps> = (args: UserDropdownProps) => (
|
export default meta;
|
||||||
<Box style={{ backgroundColor: "#000", width: 88 }}>
|
type Story = StoryObj<typeof UserDropdown>;
|
||||||
<UserDropdown {...args} />
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const Example: Story = {};
|
||||||
Example.args = {
|
|
||||||
user: MockUser,
|
|
||||||
onSignOut: () => {
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import { fireEvent, screen } from "@testing-library/react";
|
|
||||||
import { MockSupportLinks, MockUser } from "../../../../testHelpers/entities";
|
|
||||||
import { render } from "../../../../testHelpers/renderHelpers";
|
|
||||||
import { Language } from "./UserDropdownContent/UserDropdownContent";
|
|
||||||
import { UserDropdown, UserDropdownProps } from "./UserDropdown";
|
|
||||||
|
|
||||||
const renderAndClick = async (props: Partial<UserDropdownProps> = {}) => {
|
|
||||||
render(
|
|
||||||
<UserDropdown
|
|
||||||
user={props.user ?? MockUser}
|
|
||||||
supportLinks={MockSupportLinks}
|
|
||||||
onSignOut={props.onSignOut ?? jest.fn()}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
const trigger = await screen.findByTestId("user-dropdown-trigger");
|
|
||||||
fireEvent.click(trigger);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("UserDropdown", () => {
|
|
||||||
describe("when the trigger is clicked", () => {
|
|
||||||
it("opens the menu", async () => {
|
|
||||||
await renderAndClick();
|
|
||||||
expect(screen.getByText(Language.accountLabel)).toBeDefined();
|
|
||||||
expect(screen.getByText(MockSupportLinks[0].name)).toBeDefined();
|
|
||||||
expect(screen.getByText(MockSupportLinks[1].name)).toBeDefined();
|
|
||||||
expect(screen.getByText(MockSupportLinks[2].name)).toBeDefined();
|
|
||||||
expect(screen.getByText(Language.signOutLabel)).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -3,15 +3,15 @@ import MenuItem from "@mui/material/MenuItem";
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { useState, FC, PropsWithChildren, MouseEvent } from "react";
|
import { useState, FC, PropsWithChildren, MouseEvent } from "react";
|
||||||
import { colors } from "theme/colors";
|
import { colors } from "theme/colors";
|
||||||
import * as TypesGen from "../../../../api/typesGenerated";
|
import * as TypesGen from "api/typesGenerated";
|
||||||
import { navHeight } from "../../../../theme/constants";
|
import { navHeight } from "theme/constants";
|
||||||
import { BorderedMenu } from "./BorderedMenu/BorderedMenu";
|
import { BorderedMenu } from "./BorderedMenu";
|
||||||
import {
|
import {
|
||||||
CloseDropdown,
|
CloseDropdown,
|
||||||
OpenDropdown,
|
OpenDropdown,
|
||||||
} from "../../../DropdownArrows/DropdownArrows";
|
} from "components/DropdownArrows/DropdownArrows";
|
||||||
import { UserAvatar } from "../../../UserAvatar/UserAvatar";
|
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||||
import { UserDropdownContent } from "./UserDropdownContent/UserDropdownContent";
|
import { UserDropdownContent } from "./UserDropdownContent";
|
||||||
import { BUTTON_SM_HEIGHT } from "theme/theme";
|
import { BUTTON_SM_HEIGHT } from "theme/theme";
|
||||||
|
|
||||||
export interface UserDropdownProps {
|
export interface UserDropdownProps {
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import { MockUser } from "testHelpers/entities";
|
||||||
|
import { UserDropdownContent } from "./UserDropdownContent";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
const meta: Meta<typeof UserDropdownContent> = {
|
||||||
|
title: "components/UserDropdownContent",
|
||||||
|
component: UserDropdownContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof UserDropdownContent>;
|
||||||
|
|
||||||
|
export const ExampleNoRoles: Story = {
|
||||||
|
args: {
|
||||||
|
user: {
|
||||||
|
...MockUser,
|
||||||
|
roles: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExampleOneRole: Story = {
|
||||||
|
args: {
|
||||||
|
user: {
|
||||||
|
...MockUser,
|
||||||
|
roles: [{ name: "member", display_name: "Member" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExampleThreeRoles: Story = {
|
||||||
|
args: {
|
||||||
|
user: {
|
||||||
|
...MockUser,
|
||||||
|
roles: [
|
||||||
|
{ name: "admin", display_name: "Admin" },
|
||||||
|
{ name: "member", display_name: "Member" },
|
||||||
|
{ name: "auditor", display_name: "Auditor" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,36 @@
|
|||||||
|
import { screen } from "@testing-library/react";
|
||||||
|
import { MockUser } from "testHelpers/entities";
|
||||||
|
import { render } from "testHelpers/renderHelpers";
|
||||||
|
import { Language, UserDropdownContent } from "./UserDropdownContent";
|
||||||
|
|
||||||
|
describe("UserDropdownContent", () => {
|
||||||
|
it("has the correct link for the account item", () => {
|
||||||
|
render(
|
||||||
|
<UserDropdownContent
|
||||||
|
user={MockUser}
|
||||||
|
onSignOut={jest.fn()}
|
||||||
|
onPopoverClose={jest.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const link = screen.getByText(Language.accountLabel).closest("a");
|
||||||
|
if (!link) {
|
||||||
|
throw new Error("Anchor tag not found for the account menu item");
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(link.getAttribute("href")).toBe("/settings/account");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls the onSignOut function", () => {
|
||||||
|
const onSignOut = jest.fn();
|
||||||
|
render(
|
||||||
|
<UserDropdownContent
|
||||||
|
user={MockUser}
|
||||||
|
onSignOut={onSignOut}
|
||||||
|
onPopoverClose={jest.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
screen.getByText(Language.signOutLabel).click();
|
||||||
|
expect(onSignOut).toBeCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
@ -8,7 +8,7 @@ import LaunchIcon from "@mui/icons-material/LaunchOutlined";
|
|||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import * as TypesGen from "../../../../../api/typesGenerated";
|
import * as TypesGen from "api/typesGenerated";
|
||||||
import DocsIcon from "@mui/icons-material/MenuBook";
|
import DocsIcon from "@mui/icons-material/MenuBook";
|
||||||
import LogoutIcon from "@mui/icons-material/ExitToAppOutlined";
|
import LogoutIcon from "@mui/icons-material/ExitToAppOutlined";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
import { combineClasses } from "utils/combineClasses";
|
@ -1,43 +0,0 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { MockUser } from "../../../../../testHelpers/entities";
|
|
||||||
import {
|
|
||||||
UserDropdownContent,
|
|
||||||
UserDropdownContentProps,
|
|
||||||
} from "./UserDropdownContent";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "components/UserDropdownContent",
|
|
||||||
component: UserDropdownContent,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Template: Story<UserDropdownContentProps> = (args) => (
|
|
||||||
<UserDropdownContent {...args} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ExampleNoRoles = Template.bind({});
|
|
||||||
ExampleNoRoles.args = {
|
|
||||||
user: {
|
|
||||||
...MockUser,
|
|
||||||
roles: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ExampleOneRole = Template.bind({});
|
|
||||||
ExampleOneRole.args = {
|
|
||||||
user: {
|
|
||||||
...MockUser,
|
|
||||||
roles: [{ name: "member", display_name: "Member" }],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ExampleThreeRoles = Template.bind({});
|
|
||||||
ExampleThreeRoles.args = {
|
|
||||||
user: {
|
|
||||||
...MockUser,
|
|
||||||
roles: [
|
|
||||||
{ name: "admin", display_name: "Admin" },
|
|
||||||
{ name: "member", display_name: "Member" },
|
|
||||||
{ name: "auditor", display_name: "Auditor" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,77 +0,0 @@
|
|||||||
import { screen } from "@testing-library/react";
|
|
||||||
import {
|
|
||||||
MockBuildInfo,
|
|
||||||
MockSupportLinks,
|
|
||||||
MockUser,
|
|
||||||
} from "../../../../../testHelpers/entities";
|
|
||||||
import { render } from "../../../../../testHelpers/renderHelpers";
|
|
||||||
import { Language, UserDropdownContent } from "./UserDropdownContent";
|
|
||||||
|
|
||||||
describe("UserDropdownContent", () => {
|
|
||||||
const env = process.env;
|
|
||||||
|
|
||||||
// REMARK: copying process.env so we don't mutate that object or encounter conflicts between tests
|
|
||||||
beforeEach(() => {
|
|
||||||
process.env = { ...env };
|
|
||||||
});
|
|
||||||
|
|
||||||
// REMARK: restoring process.env
|
|
||||||
afterEach(() => {
|
|
||||||
process.env = env;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays the menu items", () => {
|
|
||||||
render(
|
|
||||||
<UserDropdownContent
|
|
||||||
user={MockUser}
|
|
||||||
buildInfo={MockBuildInfo}
|
|
||||||
supportLinks={MockSupportLinks}
|
|
||||||
onSignOut={jest.fn()}
|
|
||||||
onPopoverClose={jest.fn()}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
expect(screen.getByText(Language.accountLabel)).toBeDefined();
|
|
||||||
expect(screen.getByText(Language.signOutLabel)).toBeDefined();
|
|
||||||
expect(screen.getByText(Language.copyrightText)).toBeDefined();
|
|
||||||
expect(screen.getByText(MockSupportLinks[0].name)).toBeDefined();
|
|
||||||
expect(screen.getByText(MockSupportLinks[1].name)).toBeDefined();
|
|
||||||
expect(screen.getByText(MockSupportLinks[2].name)).toBeDefined();
|
|
||||||
expect(
|
|
||||||
screen.getByText(MockSupportLinks[2].name).closest("a"),
|
|
||||||
).toHaveAttribute(
|
|
||||||
"href",
|
|
||||||
"https://github.com/coder/coder/issues/new?labels=needs+grooming&body=Version%3A%20%5B%60v99.999.9999%2Bc9cdf14%60%5D(file%3A%2F%2F%2Fmock-url)",
|
|
||||||
);
|
|
||||||
expect(screen.getByText(MockBuildInfo.version)).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("has the correct link for the account item", () => {
|
|
||||||
render(
|
|
||||||
<UserDropdownContent
|
|
||||||
user={MockUser}
|
|
||||||
onSignOut={jest.fn()}
|
|
||||||
onPopoverClose={jest.fn()}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const link = screen.getByText(Language.accountLabel).closest("a");
|
|
||||||
if (!link) {
|
|
||||||
throw new Error("Anchor tag not found for the account menu item");
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(link.getAttribute("href")).toBe("/settings/account");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("calls the onSignOut function", () => {
|
|
||||||
const onSignOut = jest.fn();
|
|
||||||
render(
|
|
||||||
<UserDropdownContent
|
|
||||||
user={MockUser}
|
|
||||||
onSignOut={onSignOut}
|
|
||||||
onPopoverClose={jest.fn()}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
screen.getByText(Language.signOutLabel).click();
|
|
||||||
expect(onSignOut).toBeCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,24 +1,25 @@
|
|||||||
import { Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { ServiceBannerView, ServiceBannerViewProps } from "./ServiceBannerView";
|
import { ServiceBannerView } from "./ServiceBannerView";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof ServiceBannerView> = {
|
||||||
title: "components/ServiceBannerView",
|
title: "components/ServiceBannerView",
|
||||||
component: ServiceBannerView,
|
component: ServiceBannerView,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<ServiceBannerViewProps> = (args) => (
|
export default meta;
|
||||||
<ServiceBannerView {...args} />
|
type Story = StoryObj<typeof ServiceBannerView>;
|
||||||
);
|
|
||||||
|
|
||||||
export const Production = Template.bind({});
|
export const Production: Story = {
|
||||||
Production.args = {
|
args: {
|
||||||
message: "weeeee",
|
message: "weeeee",
|
||||||
backgroundColor: "#FFFFFF",
|
backgroundColor: "#FFFFFF",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Preview = Template.bind({});
|
export const Preview: Story = {
|
||||||
Preview.args = {
|
args: {
|
||||||
message: "weeeee",
|
message: "weeeee",
|
||||||
backgroundColor: "#000000",
|
backgroundColor: "#000000",
|
||||||
preview: true,
|
preview: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
OptionValue,
|
OptionValue,
|
||||||
} from "components/DeploySettingsLayout/Option";
|
} from "components/DeploySettingsLayout/Option";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { intervalToDuration, formatDuration } from "date-fns";
|
import { optionValue } from "./optionValue";
|
||||||
|
|
||||||
const OptionsTable: FC<{
|
const OptionsTable: FC<{
|
||||||
options: DeploymentOption[];
|
options: DeploymentOption[];
|
||||||
@ -60,29 +60,6 @@ const OptionsTable: FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// optionValue is a helper function to format the value of a specific deployment options
|
|
||||||
export function optionValue(option: DeploymentOption) {
|
|
||||||
switch (option.name) {
|
|
||||||
case "Max Token Lifetime":
|
|
||||||
case "Session Duration":
|
|
||||||
// intervalToDuration takes ms, so convert nanoseconds to ms
|
|
||||||
return formatDuration(
|
|
||||||
intervalToDuration({ start: 0, end: (option.value as number) / 1e6 }),
|
|
||||||
);
|
|
||||||
case "Strict-Transport-Security":
|
|
||||||
if (option.value === 0) {
|
|
||||||
return "Disabled";
|
|
||||||
}
|
|
||||||
return (option.value as number).toString() + "s";
|
|
||||||
case "OIDC Group Mapping":
|
|
||||||
return Object.entries(option.value as Record<string, string>).map(
|
|
||||||
([key, value]) => `"${key}"->"${value}"`,
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return option.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
table: {
|
table: {
|
||||||
"& td": {
|
"& td": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { optionValue } from "./OptionsTable";
|
import { optionValue } from "./optionValue";
|
||||||
import { DeploymentOption } from "api/types";
|
import { DeploymentOption } from "api/types";
|
||||||
|
|
||||||
const defaultOption: DeploymentOption = {
|
const defaultOption: DeploymentOption = {
|
25
site/src/components/DeploySettingsLayout/optionValue.ts
Normal file
25
site/src/components/DeploySettingsLayout/optionValue.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { DeploymentOption } from "api/types";
|
||||||
|
import { intervalToDuration, formatDuration } from "date-fns";
|
||||||
|
|
||||||
|
// optionValue is a helper function to format the value of a specific deployment options
|
||||||
|
export function optionValue(option: DeploymentOption) {
|
||||||
|
switch (option.name) {
|
||||||
|
case "Max Token Lifetime":
|
||||||
|
case "Session Duration":
|
||||||
|
// intervalToDuration takes ms, so convert nanoseconds to ms
|
||||||
|
return formatDuration(
|
||||||
|
intervalToDuration({ start: 0, end: (option.value as number) / 1e6 }),
|
||||||
|
);
|
||||||
|
case "Strict-Transport-Security":
|
||||||
|
if (option.value === 0) {
|
||||||
|
return "Disabled";
|
||||||
|
}
|
||||||
|
return (option.value as number).toString() + "s";
|
||||||
|
case "OIDC Group Mapping":
|
||||||
|
return Object.entries(option.value as Record<string, string>).map(
|
||||||
|
([key, value]) => `"${key}"->"${value}"`,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return option.value;
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +1,57 @@
|
|||||||
import { action } from "@storybook/addon-actions";
|
import { action } from "@storybook/addon-actions";
|
||||||
import { ComponentMeta, Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog";
|
import { ConfirmDialog } from "./ConfirmDialog";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof ConfirmDialog> = {
|
||||||
title: "Components/Dialogs/ConfirmDialog",
|
title: "components/Dialogs/ConfirmDialog",
|
||||||
component: ConfirmDialog,
|
component: ConfirmDialog,
|
||||||
argTypes: {
|
|
||||||
open: {
|
|
||||||
control: "boolean",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
args: {
|
args: {
|
||||||
onClose: action("onClose"),
|
onClose: action("onClose"),
|
||||||
onConfirm: action("onConfirm"),
|
onConfirm: action("onConfirm"),
|
||||||
open: true,
|
open: true,
|
||||||
title: "Confirm Dialog",
|
title: "Confirm Dialog",
|
||||||
},
|
},
|
||||||
} as ComponentMeta<typeof ConfirmDialog>;
|
|
||||||
|
|
||||||
const Template: Story<ConfirmDialogProps> = (args) => (
|
|
||||||
<ConfirmDialog {...args} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const DeleteDialog = Template.bind({});
|
|
||||||
DeleteDialog.args = {
|
|
||||||
description: "Do you really want to delete me?",
|
|
||||||
hideCancel: false,
|
|
||||||
type: "delete",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InfoDialog = Template.bind({});
|
export default meta;
|
||||||
InfoDialog.args = {
|
type Story = StoryObj<typeof ConfirmDialog>;
|
||||||
description: "Information is cool!",
|
|
||||||
hideCancel: true,
|
export const Example: Story = {
|
||||||
type: "info",
|
args: {
|
||||||
|
description: "Do you really want to delete me?",
|
||||||
|
hideCancel: false,
|
||||||
|
type: "delete",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InfoDialogWithCancel = Template.bind({});
|
export const InfoDialog: Story = {
|
||||||
InfoDialogWithCancel.args = {
|
args: {
|
||||||
description: "Information can be cool!",
|
description: "Information is cool!",
|
||||||
hideCancel: false,
|
hideCancel: true,
|
||||||
type: "info",
|
type: "info",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SuccessDialog = Template.bind({});
|
export const InfoDialogWithCancel: Story = {
|
||||||
SuccessDialog.args = {
|
args: {
|
||||||
description: "I am successful.",
|
description: "Information can be cool!",
|
||||||
hideCancel: true,
|
hideCancel: false,
|
||||||
type: "success",
|
type: "info",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SuccessDialogWithCancel = Template.bind({});
|
export const SuccessDialog: Story = {
|
||||||
SuccessDialogWithCancel.args = {
|
args: {
|
||||||
description: "I may be successful.",
|
description: "I am successful.",
|
||||||
hideCancel: false,
|
hideCancel: true,
|
||||||
type: "success",
|
type: "success",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SuccessDialogWithCancel: Story = {
|
||||||
|
args: {
|
||||||
|
description: "I may be successful.",
|
||||||
|
hideCancel: false,
|
||||||
|
type: "success",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,96 +1,8 @@
|
|||||||
import { fireEvent, screen } from "@testing-library/react";
|
import { fireEvent, screen } from "@testing-library/react";
|
||||||
import { ConfirmDialog, ConfirmDialogProps } from "./ConfirmDialog";
|
import { ConfirmDialog } from "./ConfirmDialog";
|
||||||
import { render } from "testHelpers/renderHelpers";
|
import { render } from "testHelpers/renderHelpers";
|
||||||
|
|
||||||
describe("ConfirmDialog", () => {
|
describe("ConfirmDialog", () => {
|
||||||
it("renders", () => {
|
|
||||||
// Given
|
|
||||||
const onCloseMock = jest.fn();
|
|
||||||
const props = {
|
|
||||||
onClose: onCloseMock,
|
|
||||||
open: true,
|
|
||||||
title: "Test",
|
|
||||||
};
|
|
||||||
|
|
||||||
// When
|
|
||||||
render(<ConfirmDialog {...props} />);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
expect(screen.getByRole("dialog")).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not display cancel for info dialogs", () => {
|
|
||||||
// Given (note that info is the default)
|
|
||||||
const onCloseMock = jest.fn();
|
|
||||||
const props = {
|
|
||||||
cancelText: "CANCEL",
|
|
||||||
onClose: onCloseMock,
|
|
||||||
open: true,
|
|
||||||
title: "Test",
|
|
||||||
};
|
|
||||||
|
|
||||||
// When
|
|
||||||
render(<ConfirmDialog {...props} />);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
expect(screen.queryByText("CANCEL")).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can display cancel when normally hidden", () => {
|
|
||||||
// Given
|
|
||||||
const onCloseMock = jest.fn();
|
|
||||||
const props = {
|
|
||||||
cancelText: "CANCEL",
|
|
||||||
onClose: onCloseMock,
|
|
||||||
open: true,
|
|
||||||
title: "Test",
|
|
||||||
hideCancel: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// When
|
|
||||||
render(<ConfirmDialog {...props} />);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
expect(screen.getByText("CANCEL")).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays cancel for delete dialogs", () => {
|
|
||||||
// Given
|
|
||||||
const onCloseMock = jest.fn();
|
|
||||||
const props: ConfirmDialogProps = {
|
|
||||||
cancelText: "CANCEL",
|
|
||||||
onClose: onCloseMock,
|
|
||||||
open: true,
|
|
||||||
title: "Test",
|
|
||||||
type: "delete",
|
|
||||||
};
|
|
||||||
|
|
||||||
// When
|
|
||||||
render(<ConfirmDialog {...props} />);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
expect(screen.getByText("CANCEL")).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("can hide cancel when normally visible", () => {
|
|
||||||
// Given
|
|
||||||
const onCloseMock = jest.fn();
|
|
||||||
const props: ConfirmDialogProps = {
|
|
||||||
cancelText: "CANCEL",
|
|
||||||
onClose: onCloseMock,
|
|
||||||
open: true,
|
|
||||||
title: "Test",
|
|
||||||
hideCancel: true,
|
|
||||||
type: "delete",
|
|
||||||
};
|
|
||||||
|
|
||||||
// When
|
|
||||||
render(<ConfirmDialog {...props} />);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
expect(screen.queryByText("CANCEL")).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("onClose is called when cancelled", () => {
|
it("onClose is called when cancelled", () => {
|
||||||
// Given
|
// Given
|
||||||
const onCloseMock = jest.fn();
|
const onCloseMock = jest.fn();
|
||||||
|
@ -1,28 +1,21 @@
|
|||||||
import { action } from "@storybook/addon-actions";
|
import { action } from "@storybook/addon-actions";
|
||||||
import { ComponentMeta, Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { DeleteDialog, DeleteDialogProps } from "./DeleteDialog";
|
import { DeleteDialog } from "./DeleteDialog";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof DeleteDialog> = {
|
||||||
title: "Components/Dialogs/DeleteDialog",
|
title: "components/Dialogs/DeleteDialog",
|
||||||
component: DeleteDialog,
|
component: DeleteDialog,
|
||||||
argTypes: {
|
|
||||||
open: {
|
|
||||||
control: "boolean",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
args: {
|
args: {
|
||||||
onCancel: action("onClose"),
|
onCancel: action("onClose"),
|
||||||
onConfirm: action("onConfirm"),
|
onConfirm: action("onConfirm"),
|
||||||
open: true,
|
isOpen: true,
|
||||||
entity: "foo",
|
entity: "foo",
|
||||||
name: "MyFoo",
|
name: "MyFoo",
|
||||||
info: "Here's some info about the foo so you know you're deleting the right one.",
|
info: "Here's some info about the foo so you know you're deleting the right one.",
|
||||||
},
|
},
|
||||||
} as ComponentMeta<typeof DeleteDialog>;
|
|
||||||
|
|
||||||
const Template: Story<DeleteDialogProps> = (args) => <DeleteDialog {...args} />;
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
|
||||||
Example.args = {
|
|
||||||
isOpen: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof DeleteDialog>;
|
||||||
|
|
||||||
|
export const Example: Story = {};
|
||||||
|
21
site/src/components/EmptyState/EmptyState.stories.tsx
Normal file
21
site/src/components/EmptyState/EmptyState.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import { EmptyState } from "./EmptyState";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
const meta: Meta<typeof EmptyState> = {
|
||||||
|
title: "components/EmptyState",
|
||||||
|
component: EmptyState,
|
||||||
|
args: {
|
||||||
|
message: "Create your first workspace",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof EmptyState>;
|
||||||
|
|
||||||
|
export const Example: Story = {
|
||||||
|
args: {
|
||||||
|
description: "It is easy, just click the button below",
|
||||||
|
cta: <Button>Create workspace</Button>,
|
||||||
|
},
|
||||||
|
};
|
@ -1,36 +0,0 @@
|
|||||||
import { screen } from "@testing-library/react";
|
|
||||||
import { render } from "../../testHelpers/renderHelpers";
|
|
||||||
import { EmptyState } from "./EmptyState";
|
|
||||||
|
|
||||||
describe("EmptyState", () => {
|
|
||||||
it("renders (smoke test)", async () => {
|
|
||||||
// When
|
|
||||||
render(<EmptyState message="Hello, world" />);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
await screen.findByText("Hello, world");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders description text", async () => {
|
|
||||||
// When
|
|
||||||
render(
|
|
||||||
<EmptyState message="Hello, world" description="Friendly greeting" />,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
await screen.findByText("Hello, world");
|
|
||||||
await screen.findByText("Friendly greeting");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders cta component", async () => {
|
|
||||||
// Given
|
|
||||||
const cta = <button title="Click me" />;
|
|
||||||
|
|
||||||
// When
|
|
||||||
render(<EmptyState message="Hello, world" cta={cta} />);
|
|
||||||
|
|
||||||
// Then
|
|
||||||
await screen.findByText("Hello, world");
|
|
||||||
await screen.findByRole("button");
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, ReactNode, PropsWithChildren } from "react";
|
import { Component, ReactNode, PropsWithChildren } from "react";
|
||||||
import { RuntimeErrorState } from "./RuntimeErrorState/RuntimeErrorState";
|
import { RuntimeErrorState } from "./RuntimeErrorState";
|
||||||
|
|
||||||
type ErrorBoundaryProps = PropsWithChildren<unknown>;
|
type ErrorBoundaryProps = PropsWithChildren<unknown>;
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { RuntimeErrorState } from "./RuntimeErrorState";
|
||||||
|
|
||||||
|
const error = new Error("An error occurred");
|
||||||
|
|
||||||
|
const meta: Meta<typeof RuntimeErrorState> = {
|
||||||
|
title: "components/RuntimeErrorState",
|
||||||
|
component: RuntimeErrorState,
|
||||||
|
args: {
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
// The RuntimeErrorState is noisy for chromatic, because it renders an actual error
|
||||||
|
// along with the stacktrace - and the stacktrace includes the full URL of
|
||||||
|
// scripts in the stack. This is problematic, because every deployment uses
|
||||||
|
// a different URL, causing the validation to fail.
|
||||||
|
chromatic: { disableSnapshot: true },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof RuntimeErrorState>;
|
||||||
|
|
||||||
|
export const Errored: Story = {};
|
@ -9,7 +9,7 @@ import { FullScreenLoader } from "components/Loader/FullScreenLoader";
|
|||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { Helmet } from "react-helmet-async";
|
import { Helmet } from "react-helmet-async";
|
||||||
import { Margins } from "../../Margins/Margins";
|
import { Margins } from "components/Margins/Margins";
|
||||||
|
|
||||||
const fetchDynamicallyImportedModuleError =
|
const fetchDynamicallyImportedModuleError =
|
||||||
"Failed to fetch dynamically imported module";
|
"Failed to fetch dynamically imported module";
|
@ -1,29 +0,0 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { RuntimeErrorState, RuntimeErrorStateProps } from "./RuntimeErrorState";
|
|
||||||
|
|
||||||
const error = new Error("An error occurred");
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "components/RuntimeErrorState",
|
|
||||||
component: RuntimeErrorState,
|
|
||||||
args: {
|
|
||||||
error,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const Template: Story<RuntimeErrorStateProps> = (args) => (
|
|
||||||
<RuntimeErrorState {...args} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Errored = Template.bind({});
|
|
||||||
Errored.parameters = {
|
|
||||||
// The RuntimeErrorState is noisy for chromatic, because it renders an actual error
|
|
||||||
// along with the stacktrace - and the stacktrace includes the full URL of
|
|
||||||
// scripts in the stack. This is problematic, because every deployment uses
|
|
||||||
// a different URL, causing the validation to fail.
|
|
||||||
chromatic: { disableSnapshot: true },
|
|
||||||
};
|
|
||||||
|
|
||||||
Errored.args = {
|
|
||||||
error,
|
|
||||||
};
|
|
@ -1,22 +1,22 @@
|
|||||||
import { Story } from "@storybook/react";
|
import { Expander } from "./Expander";
|
||||||
import { Expander, ExpanderProps } from "./Expander";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof Expander> = {
|
||||||
title: "components/Expander",
|
title: "components/Expander",
|
||||||
component: Expander,
|
component: Expander,
|
||||||
argTypes: {
|
};
|
||||||
setExpanded: { action: "setExpanded" },
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Expander>;
|
||||||
|
|
||||||
|
export const Expanded: Story = {
|
||||||
|
args: {
|
||||||
|
expanded: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<ExpanderProps> = (args) => <Expander {...args} />;
|
export const Collapsed: Story = {
|
||||||
|
args: {
|
||||||
export const Expanded = Template.bind({});
|
expanded: false,
|
||||||
Expanded.args = {
|
},
|
||||||
expanded: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Collapsed = Template.bind({});
|
|
||||||
Collapsed.args = {
|
|
||||||
expanded: false,
|
|
||||||
};
|
};
|
||||||
|
@ -1,28 +1,29 @@
|
|||||||
import { ComponentMeta, Story } from "@storybook/react";
|
import { FormFooter } from "./FormFooter";
|
||||||
import { FormFooter, FormFooterProps } from "./FormFooter";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof FormFooter> = {
|
||||||
title: "components/FormFooter",
|
title: "components/FormFooter",
|
||||||
component: FormFooter,
|
component: FormFooter,
|
||||||
argTypes: {
|
};
|
||||||
onCancel: { action: "cancel" },
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof FormFooter>;
|
||||||
|
|
||||||
|
export const Ready: Story = {
|
||||||
|
args: {
|
||||||
|
isLoading: false,
|
||||||
},
|
},
|
||||||
} as ComponentMeta<typeof FormFooter>;
|
|
||||||
|
|
||||||
const Template: Story<FormFooterProps> = (args) => <FormFooter {...args} />;
|
|
||||||
|
|
||||||
export const Ready = Template.bind({});
|
|
||||||
Ready.args = {
|
|
||||||
isLoading: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Custom = Template.bind({});
|
export const Custom: Story = {
|
||||||
Custom.args = {
|
args: {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
submitLabel: "Create",
|
submitLabel: "Create",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Loading = Template.bind({});
|
export const Loading: Story = {
|
||||||
Loading.args = {
|
args: {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import { action } from "@storybook/addon-actions";
|
import { action } from "@storybook/addon-actions";
|
||||||
import { ComponentMeta, Story } from "@storybook/react";
|
|
||||||
import { FormFooter } from "../FormFooter/FormFooter";
|
import { FormFooter } from "../FormFooter/FormFooter";
|
||||||
import { Stack } from "../Stack/Stack";
|
import { Stack } from "../Stack/Stack";
|
||||||
import { FullPageForm, FullPageFormProps } from "./FullPageForm";
|
import { FullPageForm, FullPageFormProps } from "./FullPageForm";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const Template = (props: FullPageFormProps) => (
|
||||||
title: "components/FullPageForm",
|
<FullPageForm {...props}>
|
||||||
component: FullPageForm,
|
|
||||||
} as ComponentMeta<typeof FullPageForm>;
|
|
||||||
|
|
||||||
const Template: Story<FullPageFormProps> = (args) => (
|
|
||||||
<FullPageForm {...args}>
|
|
||||||
<form
|
<form
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -26,8 +21,17 @@ const Template: Story<FullPageFormProps> = (args) => (
|
|||||||
</FullPageForm>
|
</FullPageForm>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
const meta: Meta<typeof FullPageForm> = {
|
||||||
Example.args = {
|
title: "components/FullPageForm",
|
||||||
title: "My Form",
|
component: Template,
|
||||||
detail: "Lorem ipsum dolor",
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof FullPageForm>;
|
||||||
|
|
||||||
|
export const Example: Story = {
|
||||||
|
args: {
|
||||||
|
title: "My Form",
|
||||||
|
detail: "Lorem ipsum dolor",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { EnterpriseSnackbar } from "./EnterpriseSnackbar";
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
const meta: Meta<typeof EnterpriseSnackbar> = {
|
||||||
|
title: "components/EnterpriseSnackbar",
|
||||||
|
component: EnterpriseSnackbar,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof EnterpriseSnackbar>;
|
||||||
|
|
||||||
|
export const Error: Story = {
|
||||||
|
args: {
|
||||||
|
variant: "error",
|
||||||
|
open: true,
|
||||||
|
message: "Oops, something wrong happened.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Info: Story = {
|
||||||
|
args: {
|
||||||
|
variant: "info",
|
||||||
|
open: true,
|
||||||
|
message: "Hey, something happened.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Success: Story = {
|
||||||
|
args: {
|
||||||
|
variant: "success",
|
||||||
|
open: true,
|
||||||
|
message: "Hey, something good happened.",
|
||||||
|
},
|
||||||
|
};
|
@ -5,7 +5,7 @@ import Snackbar, {
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { combineClasses } from "../../../utils/combineClasses";
|
import { combineClasses } from "utils/combineClasses";
|
||||||
|
|
||||||
type EnterpriseSnackbarVariant = "error" | "info" | "success";
|
type EnterpriseSnackbarVariant = "error" | "info" | "success";
|
||||||
|
|
@ -1,35 +0,0 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import {
|
|
||||||
EnterpriseSnackbar,
|
|
||||||
EnterpriseSnackbarProps,
|
|
||||||
} from "./EnterpriseSnackbar";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "components/EnterpriseSnackbar",
|
|
||||||
component: EnterpriseSnackbar,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Template: Story<EnterpriseSnackbarProps> = (
|
|
||||||
args: EnterpriseSnackbarProps,
|
|
||||||
) => <EnterpriseSnackbar {...args} />;
|
|
||||||
|
|
||||||
export const Error = Template.bind({});
|
|
||||||
Error.args = {
|
|
||||||
variant: "error",
|
|
||||||
open: true,
|
|
||||||
message: "Oops, something wrong happened.",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Info = Template.bind({});
|
|
||||||
Info.args = {
|
|
||||||
variant: "info",
|
|
||||||
open: true,
|
|
||||||
message: "Hey, something happened.",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Success = Template.bind({});
|
|
||||||
Success.args = {
|
|
||||||
variant: "success",
|
|
||||||
open: true,
|
|
||||||
message: "Hey, something good happened.",
|
|
||||||
};
|
|
@ -1,8 +1,8 @@
|
|||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import { useCallback, useState, FC } from "react";
|
import { useCallback, useState, FC } from "react";
|
||||||
import { useCustomEvent } from "../../hooks/events";
|
import { useCustomEvent } from "hooks/events";
|
||||||
import { CustomEventListener } from "../../utils/events";
|
import { CustomEventListener } from "utils/events";
|
||||||
import { EnterpriseSnackbar } from "./EnterpriseSnackbar/EnterpriseSnackbar";
|
import { EnterpriseSnackbar } from "./EnterpriseSnackbar";
|
||||||
import { ErrorIcon } from "../Icons/ErrorIcon";
|
import { ErrorIcon } from "../Icons/ErrorIcon";
|
||||||
import { Typography } from "../Typography/Typography";
|
import { Typography } from "../Typography/Typography";
|
||||||
import {
|
import {
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import { Story } from "@storybook/react";
|
import { GroupAvatar } from "./GroupAvatar";
|
||||||
import { GroupAvatar, GroupAvatarProps } from "./GroupAvatar";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof GroupAvatar> = {
|
||||||
title: "components/GroupAvatar",
|
title: "components/GroupAvatar",
|
||||||
component: GroupAvatar,
|
component: GroupAvatar,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<GroupAvatarProps> = (args) => <GroupAvatar {...args} />;
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof GroupAvatar>;
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const Example: Story = {
|
||||||
Example.args = {
|
args: {
|
||||||
name: "My Group",
|
name: "My Group",
|
||||||
avatarURL: "",
|
avatarURL: "",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,38 +1,36 @@
|
|||||||
import { ComponentMeta, Story } from "@storybook/react";
|
|
||||||
import {
|
import {
|
||||||
HelpTooltip,
|
HelpTooltip,
|
||||||
HelpTooltipLink,
|
HelpTooltipLink,
|
||||||
HelpTooltipLinksGroup,
|
HelpTooltipLinksGroup,
|
||||||
HelpTooltipProps,
|
|
||||||
HelpTooltipText,
|
HelpTooltipText,
|
||||||
HelpTooltipTitle,
|
HelpTooltipTitle,
|
||||||
} from "./HelpTooltip";
|
} from "./HelpTooltip";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof HelpTooltip> = {
|
||||||
title: "components/HelpTooltip",
|
title: "components/HelpTooltip",
|
||||||
component: HelpTooltip,
|
component: HelpTooltip,
|
||||||
} as ComponentMeta<typeof HelpTooltip>;
|
args: {
|
||||||
|
children: (
|
||||||
const Template: Story<HelpTooltipProps> = (args) => (
|
<>
|
||||||
<HelpTooltip {...args}>
|
<HelpTooltipTitle>What is a template?</HelpTooltipTitle>
|
||||||
<HelpTooltipTitle>What is a template?</HelpTooltipTitle>
|
<HelpTooltipText>
|
||||||
<HelpTooltipText>
|
A template is a common configuration for your team's workspaces.
|
||||||
A template is a common configuration for your team's workspaces.
|
</HelpTooltipText>
|
||||||
</HelpTooltipText>
|
<HelpTooltipLinksGroup>
|
||||||
<HelpTooltipLinksGroup>
|
<HelpTooltipLink href="https://github.com/coder/coder/">
|
||||||
<HelpTooltipLink href="https://github.com/coder/coder/">
|
Creating a template
|
||||||
Creating a template
|
</HelpTooltipLink>
|
||||||
</HelpTooltipLink>
|
<HelpTooltipLink href="https://github.com/coder/coder/">
|
||||||
<HelpTooltipLink href="https://github.com/coder/coder/">
|
Updating a template
|
||||||
Updating a template
|
</HelpTooltipLink>
|
||||||
</HelpTooltipLink>
|
</HelpTooltipLinksGroup>
|
||||||
</HelpTooltipLinksGroup>
|
</>
|
||||||
</HelpTooltip>
|
),
|
||||||
);
|
},
|
||||||
|
|
||||||
export const Close = Template.bind({});
|
|
||||||
|
|
||||||
export const Open = Template.bind({});
|
|
||||||
Open.args = {
|
|
||||||
open: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof HelpTooltip>;
|
||||||
|
|
||||||
|
export const Example: Story = {};
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export * from "./HelpTooltip";
|
|
@ -1,7 +1,7 @@
|
|||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import InputAdornment from "@mui/material/InputAdornment";
|
import InputAdornment from "@mui/material/InputAdornment";
|
||||||
import Popover from "@mui/material/Popover";
|
import Popover from "@mui/material/Popover";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField, { TextFieldProps } from "@mui/material/TextField";
|
||||||
import { OpenDropdown } from "components/DropdownArrows/DropdownArrows";
|
import { OpenDropdown } from "components/DropdownArrows/DropdownArrows";
|
||||||
import { useRef, FC, useState } from "react";
|
import { useRef, FC, useState } from "react";
|
||||||
import Picker from "@emoji-mart/react";
|
import Picker from "@emoji-mart/react";
|
||||||
@ -9,9 +9,12 @@ import { makeStyles } from "@mui/styles";
|
|||||||
import { colors } from "theme/colors";
|
import { colors } from "theme/colors";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import data from "@emoji-mart/data/sets/14/twitter.json";
|
import data from "@emoji-mart/data/sets/14/twitter.json";
|
||||||
import { IconFieldProps } from "./types";
|
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
|
|
||||||
|
type IconFieldProps = TextFieldProps & {
|
||||||
|
onPickEmoji: (value: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
|
const IconField: FC<IconFieldProps> = ({ onPickEmoji, ...textFieldProps }) => {
|
||||||
if (
|
if (
|
||||||
typeof textFieldProps.value !== "string" &&
|
typeof textFieldProps.value !== "string" &&
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { lazy, FC, Suspense } from "react";
|
import { lazy, Suspense, ComponentProps } from "react";
|
||||||
import { IconFieldProps } from "./types";
|
|
||||||
|
|
||||||
const IconField = lazy(() => import("./IconField"));
|
const IconField = lazy(() => import("./IconField"));
|
||||||
|
|
||||||
export const LazyIconField: FC<IconFieldProps> = (props) => {
|
export const LazyIconField = (props: ComponentProps<typeof IconField>) => {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<div role="progressbar" data-testid="loader" />}>
|
<Suspense fallback={<div role="progressbar" data-testid="loader" />}>
|
||||||
<IconField {...props} />
|
<IconField {...props} />
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { TextFieldProps } from "@mui/material/TextField";
|
|
||||||
|
|
||||||
export type IconFieldProps = TextFieldProps & {
|
|
||||||
onPickEmoji: (value: string) => void;
|
|
||||||
};
|
|
@ -1,28 +1,25 @@
|
|||||||
import { Story } from "@storybook/react";
|
import { LoadingButton } from "./LoadingButton";
|
||||||
import { LoadingButton, LoadingButtonProps } from "./LoadingButton";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof LoadingButton> = {
|
||||||
title: "components/LoadingButton",
|
title: "components/LoadingButton",
|
||||||
component: LoadingButton,
|
component: LoadingButton,
|
||||||
argTypes: {
|
|
||||||
loading: { control: "boolean" },
|
|
||||||
children: { control: "text" },
|
|
||||||
},
|
|
||||||
args: {
|
args: {
|
||||||
children: "Create workspace",
|
children: "Create workspace",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<LoadingButtonProps> = (args) => (
|
export default meta;
|
||||||
<LoadingButton {...args} />
|
type Story = StoryObj<typeof LoadingButton>;
|
||||||
);
|
|
||||||
|
|
||||||
export const Loading = Template.bind({});
|
export const Loading: Story = {
|
||||||
Loading.args = {
|
args: {
|
||||||
loading: true,
|
loading: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NotLoading = Template.bind({});
|
export const NotLoading: Story = {
|
||||||
NotLoading.args = {
|
args: {
|
||||||
loading: false,
|
loading: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { ComponentMeta, Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { Margins } from "./Margins";
|
import { Margins } from "./Margins";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof Margins> = {
|
||||||
title: "components/Margins",
|
title: "components/Margins",
|
||||||
component: Margins,
|
component: Margins,
|
||||||
} as ComponentMeta<typeof Margins>;
|
};
|
||||||
|
|
||||||
const Template: Story = (args) => (
|
export default meta;
|
||||||
<Margins {...args}>
|
type Story = StoryObj<typeof Margins>;
|
||||||
<div style={{ width: "100%", background: "black" }}>
|
|
||||||
Here is some content that will not get too wide!
|
|
||||||
</div>
|
|
||||||
</Margins>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const Example: Story = {
|
||||||
|
args: {
|
||||||
|
children: (
|
||||||
|
<div style={{ width: "100%", background: "black" }}>
|
||||||
|
Here is some content that will not get too wide!
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
import { ComponentMeta, Story } from "@storybook/react";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
import { Markdown, MarkdownProps } from "./Markdown";
|
import { Markdown } from "./Markdown";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof Markdown> = {
|
||||||
title: "components/Markdown",
|
title: "components/Markdown",
|
||||||
component: Markdown,
|
component: Markdown,
|
||||||
} as ComponentMeta<typeof Markdown>;
|
};
|
||||||
|
|
||||||
const Template: Story<MarkdownProps> = ({ children }) => (
|
export default meta;
|
||||||
<Markdown>{children}</Markdown>
|
type Story = StoryObj<typeof Markdown>;
|
||||||
);
|
|
||||||
|
|
||||||
export const WithCode = Template.bind({});
|
export const WithCode: Story = {
|
||||||
WithCode.args = {
|
args: {
|
||||||
children: `
|
children: `
|
||||||
## Required permissions / policy
|
## Required permissions / policy
|
||||||
|
|
||||||
The following sample policy allows Coder to create EC2 instances and modify instances provisioned by Coder:
|
The following sample policy allows Coder to create EC2 instances and modify instances provisioned by Coder:
|
||||||
@ -64,12 +63,14 @@ WithCode.args = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
\`\`\``,
|
\`\`\``,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WithTable = Template.bind({});
|
export const WithTable: Story = {
|
||||||
WithTable.args = {
|
args: {
|
||||||
children: `
|
children: `
|
||||||
| heading | b | c | d |
|
| heading | b | c | d |
|
||||||
| - | :- | -: | :-: |
|
| - | :- | -: | :-: |
|
||||||
| cell 1 | cell 2 | 3 | 4 | `,
|
| cell 1 | cell 2 | 3 | 4 | `,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,26 +1,29 @@
|
|||||||
import { ComponentMeta, Story } from "@storybook/react";
|
|
||||||
import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "./PageHeader";
|
import { PageHeader, PageHeaderSubtitle, PageHeaderTitle } from "./PageHeader";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof PageHeader> = {
|
||||||
title: "components/PageHeader",
|
title: "components/PageHeader",
|
||||||
component: PageHeader,
|
component: PageHeader,
|
||||||
} as ComponentMeta<typeof PageHeader>;
|
};
|
||||||
|
|
||||||
const WithTitleTemplate: Story = () => (
|
export default meta;
|
||||||
<PageHeader>
|
type Story = StoryObj<typeof PageHeader>;
|
||||||
<PageHeaderTitle>Templates</PageHeaderTitle>
|
|
||||||
</PageHeader>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithTitle = WithTitleTemplate.bind({});
|
export const WithTitle: Story = {
|
||||||
|
args: {
|
||||||
|
children: <PageHeaderTitle>Templates</PageHeaderTitle>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const WithSubtitleTemplate: Story = () => (
|
export const WithSubtitle: Story = {
|
||||||
<PageHeader>
|
args: {
|
||||||
<PageHeaderTitle>Templates</PageHeaderTitle>
|
children: (
|
||||||
<PageHeaderSubtitle>
|
<>
|
||||||
Create a new workspace from a Template
|
<PageHeaderTitle>Templates</PageHeaderTitle>
|
||||||
</PageHeaderSubtitle>
|
<PageHeaderSubtitle>
|
||||||
</PageHeader>
|
Create a new workspace from a Template
|
||||||
);
|
</PageHeaderSubtitle>
|
||||||
|
</>
|
||||||
export const WithSubtitle = WithSubtitleTemplate.bind({});
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Story } from "@storybook/react";
|
import { PaginationWidget } from "./PaginationWidget";
|
||||||
import { PaginationWidget, PaginationWidgetProps } from "./PaginationWidget";
|
|
||||||
import { createPaginationRef } from "./utils";
|
import { createPaginationRef } from "./utils";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof PaginationWidget> = {
|
||||||
title: "components/PaginationWidget",
|
title: "components/PaginationWidget",
|
||||||
component: PaginationWidget,
|
component: PaginationWidget,
|
||||||
args: {
|
args: {
|
||||||
@ -13,28 +13,31 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<PaginationWidgetProps> = (
|
export default meta;
|
||||||
args: PaginationWidgetProps,
|
type Story = StoryObj<typeof PaginationWidget>;
|
||||||
) => <PaginationWidget {...args} />;
|
|
||||||
|
|
||||||
export const LessThan8Pages = Template.bind({});
|
export const MoreThan8Pages: Story = {};
|
||||||
LessThan8Pages.args = {
|
|
||||||
numRecords: 84,
|
export const LessThan8Pages: Story = {
|
||||||
|
args: {
|
||||||
|
numRecords: 84,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MoreThan8Pages = Template.bind({});
|
export const MoreThan7PagesWithActivePageCloseToStart: Story = {
|
||||||
|
args: {
|
||||||
export const MoreThan7PagesWithActivePageCloseToStart = Template.bind({});
|
paginationRef: createPaginationRef({ page: 2, limit: 12 }),
|
||||||
MoreThan7PagesWithActivePageCloseToStart.args = {
|
},
|
||||||
paginationRef: createPaginationRef({ page: 2, limit: 12 }),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MoreThan7PagesWithActivePageFarFromBoundaries = Template.bind({});
|
export const MoreThan7PagesWithActivePageFarFromBoundaries: Story = {
|
||||||
MoreThan7PagesWithActivePageFarFromBoundaries.args = {
|
args: {
|
||||||
paginationRef: createPaginationRef({ page: 4, limit: 12 }),
|
paginationRef: createPaginationRef({ page: 4, limit: 12 }),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MoreThan7PagesWithActivePageCloseToEnd = Template.bind({});
|
export const MoreThan7PagesWithActivePageCloseToEnd: Story = {
|
||||||
MoreThan7PagesWithActivePageCloseToEnd.args = {
|
args: {
|
||||||
paginationRef: createPaginationRef({ page: 17, limit: 12 }),
|
paginationRef: createPaginationRef({ page: 17, limit: 12 }),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,55 +1,9 @@
|
|||||||
import { screen } from "@testing-library/react";
|
import { screen } from "@testing-library/react";
|
||||||
import { render } from "../../testHelpers/renderHelpers";
|
import { render } from "testHelpers/renderHelpers";
|
||||||
import { PaginationWidget } from "./PaginationWidget";
|
import { PaginationWidget } from "./PaginationWidget";
|
||||||
import { createPaginationRef } from "./utils";
|
import { createPaginationRef } from "./utils";
|
||||||
|
|
||||||
describe("PaginatedList", () => {
|
describe("PaginatedList", () => {
|
||||||
it("displays an accessible previous and next button", () => {
|
|
||||||
render(
|
|
||||||
<PaginationWidget
|
|
||||||
prevLabel="Previous"
|
|
||||||
nextLabel="Next"
|
|
||||||
paginationRef={createPaginationRef({ page: 2, limit: 12 })}
|
|
||||||
numRecords={200}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByRole("button", { name: "Previous page" })).toBeEnabled();
|
|
||||||
expect(screen.getByRole("button", { name: "Next page" })).toBeEnabled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays the expected number of pages with one ellipsis tile", () => {
|
|
||||||
const { container } = render(
|
|
||||||
<PaginationWidget
|
|
||||||
prevLabel="Previous"
|
|
||||||
nextLabel="Next"
|
|
||||||
numRecords={200}
|
|
||||||
paginationRef={createPaginationRef({ page: 1, limit: 12 })}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 7 total spaces. 6 are page numbers, one is ellipsis
|
|
||||||
expect(
|
|
||||||
container.querySelectorAll(`button[name="Page button"]`),
|
|
||||||
).toHaveLength(6);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("displays the expected number of pages with two ellipsis tiles", () => {
|
|
||||||
const { container } = render(
|
|
||||||
<PaginationWidget
|
|
||||||
prevLabel="Previous"
|
|
||||||
nextLabel="Next"
|
|
||||||
numRecords={200}
|
|
||||||
paginationRef={createPaginationRef({ page: 6, limit: 12 })}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 7 total spaces. 2 sets of ellipsis on either side of the active page
|
|
||||||
expect(
|
|
||||||
container.querySelectorAll(`button[name="Page button"]`),
|
|
||||||
).toHaveLength(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("disables the previous button on the first page", () => {
|
it("disables the previous button on the first page", () => {
|
||||||
render(
|
render(
|
||||||
<PaginationWidget
|
<PaginationWidget
|
||||||
|
@ -1,57 +1,66 @@
|
|||||||
import { Story } from "@storybook/react";
|
import { Pill } from "./Pill";
|
||||||
import { Pill, PillProps } from "./Pill";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof Pill> = {
|
||||||
title: "components/Pill",
|
title: "components/Pill",
|
||||||
component: Pill,
|
component: Pill,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<PillProps> = (args) => <Pill {...args} />;
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Pill>;
|
||||||
|
|
||||||
export const Primary = Template.bind({});
|
export const Primary: Story = {
|
||||||
Primary.args = {
|
args: {
|
||||||
text: "Primary",
|
text: "Primary",
|
||||||
type: "primary",
|
type: "primary",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Secondary = Template.bind({});
|
export const Secondary: Story = {
|
||||||
Secondary.args = {
|
args: {
|
||||||
text: "Secondary",
|
text: "Secondary",
|
||||||
type: "secondary",
|
type: "secondary",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Success = Template.bind({});
|
export const Success: Story = {
|
||||||
Success.args = {
|
args: {
|
||||||
text: "Success",
|
text: "Success",
|
||||||
type: "success",
|
type: "success",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Info = Template.bind({});
|
export const Info: Story = {
|
||||||
Info.args = {
|
args: {
|
||||||
text: "Information",
|
text: "Information",
|
||||||
type: "info",
|
type: "info",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Warning = Template.bind({});
|
export const Warning: Story = {
|
||||||
Warning.args = {
|
args: {
|
||||||
text: "Warning",
|
text: "Warning",
|
||||||
type: "warning",
|
type: "warning",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Error = Template.bind({});
|
export const Error: Story = {
|
||||||
Error.args = {
|
args: {
|
||||||
text: "Error",
|
text: "Error",
|
||||||
type: "error",
|
type: "error",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
export const Default: Story = {
|
||||||
Default.args = {
|
args: {
|
||||||
text: "Default",
|
text: "Default",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WarningLight = Template.bind({});
|
export const WarningLight: Story = {
|
||||||
WarningLight.args = {
|
args: {
|
||||||
text: "Warning",
|
text: "Warning",
|
||||||
type: "warning",
|
type: "warning",
|
||||||
lightBorder: true,
|
lightBorder: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import {
|
import {
|
||||||
WorkspaceAgentMetadataDescription,
|
WorkspaceAgentMetadataDescription,
|
||||||
WorkspaceAgentMetadataResult,
|
WorkspaceAgentMetadataResult,
|
||||||
} from "api/typesGenerated";
|
} from "api/typesGenerated";
|
||||||
import { AgentMetadataView, AgentMetadataViewProps } from "./AgentMetadata";
|
import { AgentMetadataView } from "./AgentMetadata";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof AgentMetadataView> = {
|
||||||
title: "components/AgentMetadata",
|
title: "components/AgentMetadataView",
|
||||||
component: AgentMetadataView,
|
component: AgentMetadataView,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<AgentMetadataViewProps> = (args) => (
|
export default meta;
|
||||||
<AgentMetadataView {...args} />
|
type Story = StoryObj<typeof AgentMetadataView>;
|
||||||
);
|
|
||||||
|
|
||||||
const resultDefaults: WorkspaceAgentMetadataResult = {
|
const resultDefaults: WorkspaceAgentMetadataResult = {
|
||||||
collected_at: "2021-05-05T00:00:00Z",
|
collected_at: "2021-05-05T00:00:00Z",
|
||||||
@ -29,79 +28,80 @@ const descriptionDefaults: WorkspaceAgentMetadataDescription = {
|
|||||||
script: "some command",
|
script: "some command",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const Example: Story = {
|
||||||
Example.args = {
|
args: {
|
||||||
metadata: [
|
metadata: [
|
||||||
{
|
{
|
||||||
result: {
|
result: {
|
||||||
...resultDefaults,
|
...resultDefaults,
|
||||||
value: "110%",
|
value: "110%",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
...descriptionDefaults,
|
||||||
|
display_name: "CPU",
|
||||||
|
key: "CPU",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
description: {
|
{
|
||||||
...descriptionDefaults,
|
result: {
|
||||||
display_name: "CPU",
|
...resultDefaults,
|
||||||
key: "CPU",
|
value: "50GB",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
...descriptionDefaults,
|
||||||
|
display_name: "Memory",
|
||||||
|
key: "Memory",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
result: {
|
||||||
result: {
|
...resultDefaults,
|
||||||
...resultDefaults,
|
value: "stale value",
|
||||||
value: "50GB",
|
age: 300,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
...descriptionDefaults,
|
||||||
|
interval: 5,
|
||||||
|
display_name: "Stale",
|
||||||
|
key: "stale",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
description: {
|
{
|
||||||
...descriptionDefaults,
|
result: {
|
||||||
display_name: "Memory",
|
...resultDefaults,
|
||||||
key: "Memory",
|
value: "oops",
|
||||||
|
error: "fatal error",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
...descriptionDefaults,
|
||||||
|
display_name: "Error",
|
||||||
|
key: "error",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
result: {
|
||||||
result: {
|
...resultDefaults,
|
||||||
...resultDefaults,
|
value: "",
|
||||||
value: "stale value",
|
collected_at: "0001-01-01T00:00:00Z",
|
||||||
age: 300,
|
age: 1000000,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
...descriptionDefaults,
|
||||||
|
display_name: "Never loads",
|
||||||
|
key: "nloads",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
description: {
|
{
|
||||||
...descriptionDefaults,
|
result: {
|
||||||
interval: 5,
|
...resultDefaults,
|
||||||
display_name: "Stale",
|
value: "r".repeat(1000),
|
||||||
key: "stale",
|
},
|
||||||
|
description: {
|
||||||
|
...descriptionDefaults,
|
||||||
|
display_name: "Really, really big",
|
||||||
|
key: "big",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
{
|
},
|
||||||
result: {
|
|
||||||
...resultDefaults,
|
|
||||||
value: "oops",
|
|
||||||
error: "fatal error",
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
...descriptionDefaults,
|
|
||||||
display_name: "Error",
|
|
||||||
key: "error",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
result: {
|
|
||||||
...resultDefaults,
|
|
||||||
value: "",
|
|
||||||
collected_at: "0001-01-01T00:00:00Z",
|
|
||||||
age: 1000000,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
...descriptionDefaults,
|
|
||||||
display_name: "Never loads",
|
|
||||||
key: "nloads",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
result: {
|
|
||||||
...resultDefaults,
|
|
||||||
value: "r".repeat(1000),
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
...descriptionDefaults,
|
|
||||||
display_name: "Really, really big",
|
|
||||||
key: "big",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import {
|
import {
|
||||||
MockPrimaryWorkspaceProxy,
|
MockPrimaryWorkspaceProxy,
|
||||||
MockWorkspaceProxies,
|
MockWorkspaceProxies,
|
||||||
@ -18,65 +17,9 @@ import {
|
|||||||
MockWorkspaceApp,
|
MockWorkspaceApp,
|
||||||
MockProxyLatencies,
|
MockProxyLatencies,
|
||||||
} from "testHelpers/entities";
|
} from "testHelpers/entities";
|
||||||
import { AgentRow, AgentRowProps } from "./AgentRow";
|
import { AgentRow } from "./AgentRow";
|
||||||
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
|
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
|
||||||
import { Region } from "api/typesGenerated";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "components/AgentRow",
|
|
||||||
component: AgentRow,
|
|
||||||
args: {
|
|
||||||
storybookStartupLogs: [
|
|
||||||
"\x1b[91mCloning Git repository...",
|
|
||||||
"\x1b[2;37;41mStarting Docker Daemon...",
|
|
||||||
"\x1b[1;95mAdding some 🧙magic🧙...",
|
|
||||||
"Starting VS Code...",
|
|
||||||
"\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 1475 0 1475 0 0 4231 0 --:--:-- --:--:-- --:--:-- 4238",
|
|
||||||
].map((line, index) => ({
|
|
||||||
id: index,
|
|
||||||
level: "info",
|
|
||||||
output: line,
|
|
||||||
time: "",
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const Template: Story<AgentRowProps> = (args) => {
|
|
||||||
return TemplateFC(args, [], undefined);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TemplateWithPortForward: Story<AgentRowProps> = (args) => {
|
|
||||||
return TemplateFC(args, MockWorkspaceProxies, MockPrimaryWorkspaceProxy);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TemplateFC = (
|
|
||||||
args: AgentRowProps,
|
|
||||||
proxies: Region[],
|
|
||||||
selectedProxy?: Region,
|
|
||||||
) => {
|
|
||||||
return (
|
|
||||||
<ProxyContext.Provider
|
|
||||||
value={{
|
|
||||||
proxyLatencies: MockProxyLatencies,
|
|
||||||
proxy: getPreferredProxy(proxies, selectedProxy),
|
|
||||||
proxies: proxies,
|
|
||||||
isLoading: false,
|
|
||||||
isFetched: true,
|
|
||||||
setProxy: () => {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
clearProxy: () => {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
refetchProxyLatencies: (): Date => {
|
|
||||||
return new Date();
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AgentRow {...args} />
|
|
||||||
</ProxyContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultAgentMetadata = [
|
const defaultAgentMetadata = [
|
||||||
{
|
{
|
||||||
@ -141,135 +84,205 @@ const defaultAgentMetadata = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
const meta: Meta<typeof AgentRow> = {
|
||||||
Example.args = {
|
title: "components/AgentRow",
|
||||||
agent: {
|
component: AgentRow,
|
||||||
...MockWorkspaceAgent,
|
args: {
|
||||||
startup_script:
|
storybookLogs: [
|
||||||
'set -eux -o pipefail\n\n# install and start code-server\ncurl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.8.3\n/tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &\n\n\nif [ ! -d ~/coder ]; then\n mkdir -p ~/coder\n\n git clone https://github.com/coder/coder ~/coder\nfi\n\nsudo service docker start\nDOTFILES_URI=" "\nrm -f ~/.personalize.log\nif [ -n "${DOTFILES_URI// }" ]; then\n coder dotfiles "$DOTFILES_URI" -y 2>&1 | tee -a ~/.personalize.log\nfi\nif [ -x ~/personalize ]; then\n ~/personalize 2>&1 | tee -a ~/.personalize.log\nelif [ -f ~/personalize ]; then\n echo "~/personalize is not executable, skipping..." | tee -a ~/.personalize.log\nfi\n',
|
"\x1b[91mCloning Git repository...",
|
||||||
|
"\x1b[2;37;41mStarting Docker Daemon...",
|
||||||
|
"\x1b[1;95mAdding some 🧙magic🧙...",
|
||||||
|
"Starting VS Code...",
|
||||||
|
"\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 1475 0 1475 0 0 4231 0 --:--:-- --:--:-- --:--:-- 4238",
|
||||||
|
].map((line, index) => ({
|
||||||
|
id: index,
|
||||||
|
level: "info",
|
||||||
|
output: line,
|
||||||
|
time: "",
|
||||||
|
})),
|
||||||
|
agent: {
|
||||||
|
...MockWorkspaceAgent,
|
||||||
|
startup_script:
|
||||||
|
'set -eux -o pipefail\n\n# install and start code-server\ncurl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.8.3\n/tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &\n\n\nif [ ! -d ~/coder ]; then\n mkdir -p ~/coder\n\n git clone https://github.com/coder/coder ~/coder\nfi\n\nsudo service docker start\nDOTFILES_URI=" "\nrm -f ~/.personalize.log\nif [ -n "${DOTFILES_URI// }" ]; then\n coder dotfiles "$DOTFILES_URI" -y 2>&1 | tee -a ~/.personalize.log\nfi\nif [ -x ~/personalize ]; then\n ~/personalize 2>&1 | tee -a ~/.personalize.log\nelif [ -f ~/personalize ]; then\n echo "~/personalize is not executable, skipping..." | tee -a ~/.personalize.log\nfi\n',
|
||||||
|
},
|
||||||
|
workspace: MockWorkspace,
|
||||||
|
showApps: true,
|
||||||
|
storybookAgentMetadata: defaultAgentMetadata,
|
||||||
},
|
},
|
||||||
workspace: MockWorkspace,
|
decorators: [
|
||||||
showApps: true,
|
(Story) => (
|
||||||
storybookAgentMetadata: defaultAgentMetadata,
|
<ProxyContext.Provider
|
||||||
|
value={{
|
||||||
|
proxyLatencies: MockProxyLatencies,
|
||||||
|
proxy: getPreferredProxy([], undefined),
|
||||||
|
proxies: [],
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
setProxy: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
clearProxy: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
refetchProxyLatencies: (): Date => {
|
||||||
|
return new Date();
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</ProxyContext.Provider>
|
||||||
|
),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HideSSHButton = Template.bind({});
|
export default meta;
|
||||||
HideSSHButton.args = {
|
type Story = StoryObj<typeof AgentRow>;
|
||||||
...Example.args,
|
|
||||||
hideSSHButton: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HideVSCodeDesktopButton = Template.bind({});
|
export const Example: Story = {};
|
||||||
HideVSCodeDesktopButton.args = {
|
|
||||||
...Example.args,
|
|
||||||
hideVSCodeDesktopButton: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NotShowingApps = Template.bind({});
|
export const HideSSHButton: Story = {
|
||||||
NotShowingApps.args = {
|
args: {
|
||||||
...Example.args,
|
hideSSHButton: true,
|
||||||
showApps: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BunchOfApps = Template.bind({});
|
|
||||||
BunchOfApps.args = {
|
|
||||||
...Example.args,
|
|
||||||
agent: {
|
|
||||||
...MockWorkspaceAgent,
|
|
||||||
apps: [
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
workspace: MockWorkspace,
|
|
||||||
showApps: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Connecting = Template.bind({});
|
|
||||||
Connecting.args = {
|
|
||||||
...Example.args,
|
|
||||||
agent: MockWorkspaceAgentConnecting,
|
|
||||||
storybookAgentMetadata: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Timeout = Template.bind({});
|
|
||||||
Timeout.args = {
|
|
||||||
...Example.args,
|
|
||||||
agent: MockWorkspaceAgentTimeout,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Starting = Template.bind({});
|
|
||||||
Starting.args = {
|
|
||||||
...Example.args,
|
|
||||||
agent: MockWorkspaceAgentStarting,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Started = Template.bind({});
|
|
||||||
Started.args = {
|
|
||||||
...Example.args,
|
|
||||||
agent: {
|
|
||||||
...MockWorkspaceAgentReady,
|
|
||||||
logs_length: 1,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StartedNoMetadata = Template.bind({});
|
export const HideVSCodeDesktopButton: Story = {
|
||||||
StartedNoMetadata.args = {
|
args: {
|
||||||
...Started.args,
|
hideVSCodeDesktopButton: true,
|
||||||
storybookAgentMetadata: [],
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StartTimeout = Template.bind({});
|
export const NotShowingApps: Story = {
|
||||||
StartTimeout.args = {
|
args: {
|
||||||
...Example.args,
|
showApps: false,
|
||||||
agent: MockWorkspaceAgentStartTimeout,
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StartError = Template.bind({});
|
export const BunchOfApps: Story = {
|
||||||
StartError.args = {
|
args: {
|
||||||
...Example.args,
|
agent: {
|
||||||
agent: MockWorkspaceAgentStartError,
|
...MockWorkspaceAgent,
|
||||||
|
apps: [
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
workspace: MockWorkspace,
|
||||||
|
showApps: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShuttingDown = Template.bind({});
|
export const Connecting: Story = {
|
||||||
ShuttingDown.args = {
|
args: {
|
||||||
...Example.args,
|
agent: MockWorkspaceAgentConnecting,
|
||||||
agent: MockWorkspaceAgentShuttingDown,
|
storybookAgentMetadata: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShutdownTimeout = Template.bind({});
|
export const Timeout: Story = {
|
||||||
ShutdownTimeout.args = {
|
args: {
|
||||||
...Example.args,
|
agent: MockWorkspaceAgentTimeout,
|
||||||
agent: MockWorkspaceAgentShutdownTimeout,
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShutdownError = Template.bind({});
|
export const Starting: Story = {
|
||||||
ShutdownError.args = {
|
args: {
|
||||||
...Example.args,
|
agent: MockWorkspaceAgentStarting,
|
||||||
agent: MockWorkspaceAgentShutdownError,
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Off = Template.bind({});
|
export const Started: Story = {
|
||||||
Off.args = {
|
args: {
|
||||||
...Example.args,
|
agent: {
|
||||||
agent: MockWorkspaceAgentOff,
|
...MockWorkspaceAgentReady,
|
||||||
|
logs_length: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShowingPortForward = TemplateWithPortForward.bind({});
|
export const StartedNoMetadata: Story = {
|
||||||
ShowingPortForward.args = {
|
args: {
|
||||||
...Example.args,
|
...Started.args,
|
||||||
|
storybookAgentMetadata: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Outdated = Template.bind({});
|
export const StartTimeout: Story = {
|
||||||
Outdated.args = {
|
args: {
|
||||||
...Example.args,
|
agent: MockWorkspaceAgentStartTimeout,
|
||||||
agent: MockWorkspaceAgentOutdated,
|
},
|
||||||
workspace: MockWorkspace,
|
};
|
||||||
serverVersion: "v99.999.9999+c1cdf14",
|
|
||||||
|
export const StartError: Story = {
|
||||||
|
args: {
|
||||||
|
agent: MockWorkspaceAgentStartError,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShuttingDown: Story = {
|
||||||
|
args: {
|
||||||
|
agent: MockWorkspaceAgentShuttingDown,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShutdownTimeout: Story = {
|
||||||
|
args: {
|
||||||
|
agent: MockWorkspaceAgentShutdownTimeout,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShutdownError: Story = {
|
||||||
|
args: {
|
||||||
|
agent: MockWorkspaceAgentShutdownError,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Off: Story = {
|
||||||
|
args: {
|
||||||
|
agent: MockWorkspaceAgentOff,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ShowingPortForward: Story = {
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<ProxyContext.Provider
|
||||||
|
value={{
|
||||||
|
proxyLatencies: MockProxyLatencies,
|
||||||
|
proxy: getPreferredProxy(
|
||||||
|
MockWorkspaceProxies,
|
||||||
|
MockPrimaryWorkspaceProxy,
|
||||||
|
),
|
||||||
|
proxies: MockWorkspaceProxies,
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
setProxy: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
clearProxy: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
refetchProxyLatencies: (): Date => {
|
||||||
|
return new Date();
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</ProxyContext.Provider>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Outdated: Story = {
|
||||||
|
args: {
|
||||||
|
agent: MockWorkspaceAgentOutdated,
|
||||||
|
workspace: MockWorkspace,
|
||||||
|
serverVersion: "v99.999.9999+c1cdf14",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -7,10 +7,7 @@ import {
|
|||||||
CloseDropdown,
|
CloseDropdown,
|
||||||
OpenDropdown,
|
OpenDropdown,
|
||||||
} from "components/DropdownArrows/DropdownArrows";
|
} from "components/DropdownArrows/DropdownArrows";
|
||||||
import {
|
import { LogLine, logLineHeight } from "components/WorkspaceBuildLogs/Logs";
|
||||||
LogLine,
|
|
||||||
logLineHeight,
|
|
||||||
} from "components/WorkspaceBuildLogs/Logs/Logs";
|
|
||||||
import { PortForwardButton } from "./PortForwardButton";
|
import { PortForwardButton } from "./PortForwardButton";
|
||||||
import { VSCodeDesktopButton } from "components/Resources/VSCodeDesktopButton/VSCodeDesktopButton";
|
import { VSCodeDesktopButton } from "components/Resources/VSCodeDesktopButton/VSCodeDesktopButton";
|
||||||
import {
|
import {
|
||||||
|
@ -1,44 +1,43 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { MockWorkspaceAgent, MockWorkspaceApp } from "testHelpers/entities";
|
import { MockWorkspaceAgent, MockWorkspaceApp } from "testHelpers/entities";
|
||||||
import { AgentRowPreview, AgentRowPreviewProps } from "./AgentRowPreview";
|
import { AgentRowPreview } from "./AgentRowPreview";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof AgentRowPreview> = {
|
||||||
title: "components/AgentRowPreview",
|
title: "components/AgentRowPreview",
|
||||||
component: AgentRowPreview,
|
component: AgentRowPreview,
|
||||||
};
|
args: {
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
const Template: Story<AgentRowPreviewProps> = (args) => (
|
|
||||||
<AgentRowPreview {...args} />
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
|
||||||
Example.args = {
|
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const BunchOfApps = Template.bind({});
|
|
||||||
BunchOfApps.args = {
|
|
||||||
...Example.args,
|
|
||||||
agent: {
|
|
||||||
...MockWorkspaceAgent,
|
|
||||||
apps: [
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
MockWorkspaceApp,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoApps = Template.bind({});
|
export default meta;
|
||||||
NoApps.args = {
|
type Story = StoryObj<typeof AgentRowPreview>;
|
||||||
...Example.args,
|
|
||||||
agent: {
|
export const Example: Story = {};
|
||||||
...MockWorkspaceAgent,
|
|
||||||
apps: [],
|
export const BunchOfApps: Story = {
|
||||||
|
args: {
|
||||||
|
agent: {
|
||||||
|
...MockWorkspaceAgent,
|
||||||
|
apps: [
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
MockWorkspaceApp,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NoApps: Story = {
|
||||||
|
args: {
|
||||||
|
agent: {
|
||||||
|
...MockWorkspaceAgent,
|
||||||
|
apps: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import {
|
import {
|
||||||
MockPrimaryWorkspaceProxy,
|
MockPrimaryWorkspaceProxy,
|
||||||
MockWorkspaceProxies,
|
MockWorkspaceProxies,
|
||||||
@ -7,116 +6,132 @@ import {
|
|||||||
MockWorkspaceApp,
|
MockWorkspaceApp,
|
||||||
MockProxyLatencies,
|
MockProxyLatencies,
|
||||||
} from "testHelpers/entities";
|
} from "testHelpers/entities";
|
||||||
import { AppLink, AppLinkProps } from "./AppLink";
|
import { AppLink } from "./AppLink";
|
||||||
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
|
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof AppLink> = {
|
||||||
title: "components/AppLink",
|
title: "components/AppLink",
|
||||||
component: AppLink,
|
component: AppLink,
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<ProxyContext.Provider
|
||||||
|
value={{
|
||||||
|
proxyLatencies: MockProxyLatencies,
|
||||||
|
proxy: getPreferredProxy(
|
||||||
|
MockWorkspaceProxies,
|
||||||
|
MockPrimaryWorkspaceProxy,
|
||||||
|
),
|
||||||
|
proxies: MockWorkspaceProxies,
|
||||||
|
isLoading: false,
|
||||||
|
isFetched: true,
|
||||||
|
setProxy: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
clearProxy: () => {
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
refetchProxyLatencies: (): Date => {
|
||||||
|
return new Date();
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</ProxyContext.Provider>
|
||||||
|
),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<AppLinkProps> = (args) => (
|
export default meta;
|
||||||
<ProxyContext.Provider
|
type Story = StoryObj<typeof AppLink>;
|
||||||
value={{
|
|
||||||
proxyLatencies: MockProxyLatencies,
|
|
||||||
proxy: getPreferredProxy(MockWorkspaceProxies, MockPrimaryWorkspaceProxy),
|
|
||||||
proxies: MockWorkspaceProxies,
|
|
||||||
isLoading: false,
|
|
||||||
isFetched: true,
|
|
||||||
setProxy: () => {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
clearProxy: () => {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
refetchProxyLatencies: (): Date => {
|
|
||||||
return new Date();
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AppLink {...args} />
|
|
||||||
</ProxyContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const WithIcon = Template.bind({});
|
export const WithIcon: Story = {
|
||||||
WithIcon.args = {
|
args: {
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
app: {
|
app: {
|
||||||
...MockWorkspaceApp,
|
...MockWorkspaceApp,
|
||||||
icon: "/icon/code.svg",
|
icon: "/icon/code.svg",
|
||||||
sharing_level: "owner",
|
sharing_level: "owner",
|
||||||
health: "healthy",
|
health: "healthy",
|
||||||
|
},
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
},
|
},
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExternalApp = Template.bind({});
|
export const ExternalApp: Story = {
|
||||||
ExternalApp.args = {
|
args: {
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
app: {
|
app: {
|
||||||
...MockWorkspaceApp,
|
...MockWorkspaceApp,
|
||||||
external: true,
|
external: true,
|
||||||
|
},
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
},
|
},
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SharingLevelOwner = Template.bind({});
|
export const SharingLevelOwner: Story = {
|
||||||
SharingLevelOwner.args = {
|
args: {
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
app: {
|
app: {
|
||||||
...MockWorkspaceApp,
|
...MockWorkspaceApp,
|
||||||
sharing_level: "owner",
|
sharing_level: "owner",
|
||||||
|
},
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
},
|
},
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SharingLevelAuthenticated = Template.bind({});
|
export const SharingLevelAuthenticated: Story = {
|
||||||
SharingLevelAuthenticated.args = {
|
args: {
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
app: {
|
app: {
|
||||||
...MockWorkspaceApp,
|
...MockWorkspaceApp,
|
||||||
sharing_level: "authenticated",
|
sharing_level: "authenticated",
|
||||||
|
},
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
},
|
},
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SharingLevelPublic = Template.bind({});
|
export const SharingLevelPublic: Story = {
|
||||||
SharingLevelPublic.args = {
|
args: {
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
app: {
|
app: {
|
||||||
...MockWorkspaceApp,
|
...MockWorkspaceApp,
|
||||||
sharing_level: "public",
|
sharing_level: "public",
|
||||||
|
},
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
},
|
},
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HealthDisabled = Template.bind({});
|
export const HealthDisabled: Story = {
|
||||||
HealthDisabled.args = {
|
args: {
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
app: {
|
app: {
|
||||||
...MockWorkspaceApp,
|
...MockWorkspaceApp,
|
||||||
sharing_level: "owner",
|
sharing_level: "owner",
|
||||||
health: "disabled",
|
health: "disabled",
|
||||||
|
},
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
},
|
},
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HealthInitializing = Template.bind({});
|
export const HealthInitializing: Story = {
|
||||||
HealthInitializing.args = {
|
args: {
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
app: {
|
app: {
|
||||||
...MockWorkspaceApp,
|
...MockWorkspaceApp,
|
||||||
health: "initializing",
|
health: "initializing",
|
||||||
|
},
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
},
|
},
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HealthUnhealthy = Template.bind({});
|
export const HealthUnhealthy: Story = {
|
||||||
HealthUnhealthy.args = {
|
args: {
|
||||||
workspace: MockWorkspace,
|
workspace: MockWorkspace,
|
||||||
app: {
|
app: {
|
||||||
...MockWorkspaceApp,
|
...MockWorkspaceApp,
|
||||||
health: "unhealthy",
|
health: "unhealthy",
|
||||||
|
},
|
||||||
|
agent: MockWorkspaceAgent,
|
||||||
},
|
},
|
||||||
agent: MockWorkspaceAgent,
|
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
HelpTooltipLinksGroup,
|
HelpTooltipLinksGroup,
|
||||||
HelpTooltipText,
|
HelpTooltipText,
|
||||||
HelpTooltipTitle,
|
HelpTooltipTitle,
|
||||||
} from "components/HelpTooltip";
|
} from "components/HelpTooltip/HelpTooltip";
|
||||||
import { SecondaryAgentButton } from "components/Resources/AgentButton";
|
import { SecondaryAgentButton } from "components/Resources/AgentButton";
|
||||||
import { docs } from "utils/docs";
|
import { docs } from "utils/docs";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
|
@ -1,61 +1,66 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { MockWorkspaceResource } from "testHelpers/entities";
|
import { MockWorkspaceResource } from "testHelpers/entities";
|
||||||
import { ResourceAvatar, ResourceAvatarProps } from "./ResourceAvatar";
|
import { ResourceAvatar } from "./ResourceAvatar";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof ResourceAvatar> = {
|
||||||
title: "components/ResourceAvatar",
|
title: "components/ResourceAvatar",
|
||||||
component: ResourceAvatar,
|
component: ResourceAvatar,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<ResourceAvatarProps> = (args) => (
|
export default meta;
|
||||||
<ResourceAvatar {...args} />
|
type Story = StoryObj<typeof ResourceAvatar>;
|
||||||
);
|
|
||||||
|
|
||||||
export const VolumeResource = Template.bind({});
|
export const VolumeResource: Story = {
|
||||||
VolumeResource.args = {
|
args: {
|
||||||
resource: {
|
resource: {
|
||||||
...MockWorkspaceResource,
|
...MockWorkspaceResource,
|
||||||
type: "docker_volume",
|
type: "docker_volume",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ComputeResource = Template.bind({});
|
export const ComputeResource: Story = {
|
||||||
ComputeResource.args = {
|
args: {
|
||||||
resource: {
|
resource: {
|
||||||
...MockWorkspaceResource,
|
...MockWorkspaceResource,
|
||||||
type: "docker_container",
|
type: "docker_container",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ImageResource = Template.bind({});
|
export const ImageResource: Story = {
|
||||||
ImageResource.args = {
|
args: {
|
||||||
resource: {
|
resource: {
|
||||||
...MockWorkspaceResource,
|
...MockWorkspaceResource,
|
||||||
type: "docker_image",
|
type: "docker_image",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NullResource = Template.bind({});
|
export const NullResource: Story = {
|
||||||
NullResource.args = {
|
args: {
|
||||||
resource: {
|
resource: {
|
||||||
...MockWorkspaceResource,
|
...MockWorkspaceResource,
|
||||||
type: "null_resource",
|
type: "null_resource",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UnknownResource = Template.bind({});
|
export const UnknownResource: Story = {
|
||||||
UnknownResource.args = {
|
args: {
|
||||||
resource: {
|
resource: {
|
||||||
...MockWorkspaceResource,
|
...MockWorkspaceResource,
|
||||||
type: "noexistentvalue",
|
type: "noexistentvalue",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EmptyIcon = Template.bind({});
|
export const EmptyIcon: Story = {
|
||||||
EmptyIcon.args = {
|
args: {
|
||||||
resource: {
|
resource: {
|
||||||
...MockWorkspaceResource,
|
...MockWorkspaceResource,
|
||||||
type: "helm_release",
|
type: "helm_release",
|
||||||
icon: "",
|
icon: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,124 +1,129 @@
|
|||||||
import { action } from "@storybook/addon-actions";
|
import { action } from "@storybook/addon-actions";
|
||||||
import { Story } from "@storybook/react";
|
|
||||||
import {
|
import {
|
||||||
MockProxyLatencies,
|
MockProxyLatencies,
|
||||||
MockWorkspace,
|
MockWorkspace,
|
||||||
MockWorkspaceResource,
|
MockWorkspaceResource,
|
||||||
} from "testHelpers/entities";
|
} from "testHelpers/entities";
|
||||||
import { AgentRow } from "./AgentRow";
|
import { AgentRow } from "./AgentRow";
|
||||||
import { ResourceCard, ResourceCardProps } from "./ResourceCard";
|
import { ResourceCard } from "./ResourceCard";
|
||||||
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
|
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof ResourceCard> = {
|
||||||
title: "components/ResourceCard",
|
title: "components/ResourceCard",
|
||||||
component: ResourceCard,
|
component: ResourceCard,
|
||||||
};
|
args: {
|
||||||
|
resource: MockWorkspaceResource,
|
||||||
const Template: Story<ResourceCardProps> = (args) => <ResourceCard {...args} />;
|
agentRow: (agent) => (
|
||||||
|
<ProxyContext.Provider
|
||||||
export const Example = Template.bind({});
|
value={{
|
||||||
Example.args = {
|
proxyLatencies: MockProxyLatencies,
|
||||||
resource: MockWorkspaceResource,
|
proxy: getPreferredProxy([], undefined),
|
||||||
agentRow: (agent) => (
|
proxies: [],
|
||||||
<ProxyContext.Provider
|
isLoading: false,
|
||||||
value={{
|
isFetched: true,
|
||||||
proxyLatencies: MockProxyLatencies,
|
setProxy: () => {
|
||||||
proxy: getPreferredProxy([], undefined),
|
return;
|
||||||
proxies: [],
|
},
|
||||||
isLoading: false,
|
clearProxy: () => {
|
||||||
isFetched: true,
|
return;
|
||||||
setProxy: () => {
|
},
|
||||||
return;
|
refetchProxyLatencies: (): Date => {
|
||||||
},
|
return new Date();
|
||||||
clearProxy: () => {
|
},
|
||||||
return;
|
}}
|
||||||
},
|
>
|
||||||
refetchProxyLatencies: (): Date => {
|
<AgentRow
|
||||||
return new Date();
|
showApps
|
||||||
},
|
key={agent.id}
|
||||||
}}
|
agent={agent}
|
||||||
>
|
workspace={MockWorkspace}
|
||||||
<AgentRow
|
serverVersion=""
|
||||||
showApps
|
onUpdateAgent={action("updateAgent")}
|
||||||
key={agent.id}
|
/>
|
||||||
agent={agent}
|
</ProxyContext.Provider>
|
||||||
workspace={MockWorkspace}
|
),
|
||||||
serverVersion=""
|
},
|
||||||
onUpdateAgent={action("updateAgent")}
|
};
|
||||||
/>
|
|
||||||
</ProxyContext.Provider>
|
export default meta;
|
||||||
),
|
type Story = StoryObj<typeof ResourceCard>;
|
||||||
};
|
|
||||||
|
export const Example: Story = {};
|
||||||
export const BunchOfMetadata = Template.bind({});
|
|
||||||
BunchOfMetadata.args = {
|
export const BunchOfMetadata: Story = {
|
||||||
...Example.args,
|
args: {
|
||||||
resource: {
|
resource: {
|
||||||
...MockWorkspaceResource,
|
...MockWorkspaceResource,
|
||||||
metadata: [
|
metadata: [
|
||||||
{
|
{
|
||||||
key: "CPU(limits, requests)",
|
key: "CPU(limits, requests)",
|
||||||
value: "2 cores, 500m",
|
value: "2 cores, 500m",
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
},
|
},
|
||||||
{ key: "container image pull policy", value: "Always", sensitive: false },
|
{
|
||||||
{ key: "Disk", value: "10GiB", sensitive: false },
|
key: "container image pull policy",
|
||||||
{
|
value: "Always",
|
||||||
key: "image",
|
sensitive: false,
|
||||||
value: "docker.io/markmilligan/pycharm-community:latest",
|
},
|
||||||
sensitive: false,
|
{ key: "Disk", value: "10GiB", sensitive: false },
|
||||||
},
|
{
|
||||||
{ key: "kubernetes namespace", value: "oss", sensitive: false },
|
key: "image",
|
||||||
{
|
value: "docker.io/markmilligan/pycharm-community:latest",
|
||||||
key: "memory(limits, requests)",
|
sensitive: false,
|
||||||
value: "4GB, 500mi",
|
},
|
||||||
sensitive: false,
|
{ key: "kubernetes namespace", value: "oss", sensitive: false },
|
||||||
},
|
{
|
||||||
{
|
key: "memory(limits, requests)",
|
||||||
key: "security context - container",
|
value: "4GB, 500mi",
|
||||||
value: "run_as_user 1000",
|
sensitive: false,
|
||||||
sensitive: false,
|
},
|
||||||
},
|
{
|
||||||
{
|
key: "security context - container",
|
||||||
key: "security context - pod",
|
value: "run_as_user 1000",
|
||||||
value: "run_as_user 1000 fs_group 1000",
|
sensitive: false,
|
||||||
sensitive: false,
|
},
|
||||||
},
|
{
|
||||||
{ key: "volume", value: "/home/coder", sensitive: false },
|
key: "security context - pod",
|
||||||
{
|
value: "run_as_user 1000 fs_group 1000",
|
||||||
key: "secret",
|
sensitive: false,
|
||||||
value: "3XqfNW0b1bvsGsqud8O6OW6VabH3fwzI",
|
},
|
||||||
sensitive: true,
|
{ key: "volume", value: "/home/coder", sensitive: false },
|
||||||
},
|
{
|
||||||
],
|
key: "secret",
|
||||||
|
value: "3XqfNW0b1bvsGsqud8O6OW6VabH3fwzI",
|
||||||
|
sensitive: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
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: (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>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { MockWorkspace, MockWorkspaceAgent } from "testHelpers/entities";
|
import { MockWorkspace, MockWorkspaceAgent } from "testHelpers/entities";
|
||||||
import { SSHButton, SSHButtonProps } from "./SSHButton";
|
import { SSHButton } from "./SSHButton";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof SSHButton> = {
|
||||||
title: "components/SSHButton",
|
title: "components/SSHButton",
|
||||||
component: SSHButton,
|
component: SSHButton,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<SSHButtonProps> = (args) => <SSHButton {...args} />;
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof SSHButton>;
|
||||||
|
|
||||||
export const Closed = Template.bind({});
|
export const Closed: Story = {
|
||||||
Closed.args = {
|
args: {
|
||||||
workspaceName: MockWorkspace.name,
|
workspaceName: MockWorkspace.name,
|
||||||
agentName: MockWorkspaceAgent.name,
|
agentName: MockWorkspaceAgent.name,
|
||||||
sshPrefix: "coder.",
|
sshPrefix: "coder.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Opened = Template.bind({});
|
export const Opened: Story = {
|
||||||
Opened.args = {
|
args: {
|
||||||
workspaceName: MockWorkspace.name,
|
workspaceName: MockWorkspace.name,
|
||||||
agentName: MockWorkspaceAgent.name,
|
agentName: MockWorkspaceAgent.name,
|
||||||
defaultIsOpen: true,
|
defaultIsOpen: true,
|
||||||
sshPrefix: "coder.",
|
sshPrefix: "coder.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { MockWorkspace } from "testHelpers/entities";
|
import { MockWorkspace } from "testHelpers/entities";
|
||||||
import { TerminalLink, TerminalLinkProps } from "./TerminalLink";
|
import { TerminalLink } from "./TerminalLink";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof TerminalLink> = {
|
||||||
title: "components/TerminalLink",
|
title: "components/TerminalLink",
|
||||||
component: TerminalLink,
|
component: TerminalLink,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<TerminalLinkProps> = (args) => <TerminalLink {...args} />;
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof TerminalLink>;
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const Example: Story = {
|
||||||
Example.args = {
|
args: {
|
||||||
workspaceName: MockWorkspace.name,
|
workspaceName: MockWorkspace.name,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,29 +1,26 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { MockWorkspace, MockWorkspaceAgent } from "testHelpers/entities";
|
import { MockWorkspace, MockWorkspaceAgent } from "testHelpers/entities";
|
||||||
import {
|
import { VSCodeDesktopButton } from "./VSCodeDesktopButton";
|
||||||
VSCodeDesktopButton,
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
VSCodeDesktopButtonProps,
|
|
||||||
} from "./VSCodeDesktopButton";
|
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof VSCodeDesktopButton> = {
|
||||||
title: "components/VSCodeDesktopButton",
|
title: "components/VSCodeDesktopButton",
|
||||||
component: VSCodeDesktopButton,
|
component: VSCodeDesktopButton,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<VSCodeDesktopButtonProps> = (args) => (
|
export default meta;
|
||||||
<VSCodeDesktopButton {...args} />
|
type Story = StoryObj<typeof VSCodeDesktopButton>;
|
||||||
);
|
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
export const Default: Story = {
|
||||||
Default.args = {
|
args: {
|
||||||
userName: MockWorkspace.owner_name,
|
userName: MockWorkspace.owner_name,
|
||||||
workspaceName: MockWorkspace.name,
|
workspaceName: MockWorkspace.name,
|
||||||
agentName: MockWorkspaceAgent.name,
|
agentName: MockWorkspaceAgent.name,
|
||||||
displayApps: [
|
displayApps: [
|
||||||
"vscode",
|
"vscode",
|
||||||
"port_forwarding_helper",
|
"port_forwarding_helper",
|
||||||
"ssh_helper",
|
"ssh_helper",
|
||||||
"vscode_insiders",
|
"vscode_insiders",
|
||||||
"web_terminal",
|
"web_terminal",
|
||||||
],
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { MultiTextField, MultiTextFieldProps } from "./MultiTextField";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "components/MultiTextField",
|
|
||||||
component: MultiTextField,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Template: Story<MultiTextFieldProps> = (args) => {
|
|
||||||
const [values, setValues] = useState(args.values ?? ["foo", "bar"]);
|
|
||||||
return <MultiTextField {...args} values={values} onChange={setValues} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
|
||||||
Example.args = {};
|
|
@ -5,10 +5,10 @@ import { makeStyles } from "@mui/styles";
|
|||||||
import TextField, { TextFieldProps } from "@mui/material/TextField";
|
import TextField, { TextFieldProps } from "@mui/material/TextField";
|
||||||
import { Stack } from "components/Stack/Stack";
|
import { Stack } from "components/Stack/Stack";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { TemplateVersionParameter } from "../../api/typesGenerated";
|
import { TemplateVersionParameter } from "api/typesGenerated";
|
||||||
import { colors } from "theme/colors";
|
import { colors } from "theme/colors";
|
||||||
import { MemoizedMarkdown } from "components/Markdown/Markdown";
|
import { MemoizedMarkdown } from "components/Markdown/Markdown";
|
||||||
import { MultiTextField } from "components/RichParameterInput/MultiTextField/MultiTextField";
|
import { MultiTextField } from "./MultiTextField";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { Theme } from "@mui/material/styles";
|
import { Theme } from "@mui/material/styles";
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import TextField from "@mui/material/TextField";
|
|
||||||
import { Story } from "@storybook/react";
|
|
||||||
import { Stack, StackProps } from "./Stack";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "components/Stack",
|
|
||||||
component: Stack,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Template: Story<StackProps> = (args: StackProps) => (
|
|
||||||
<Stack {...args}>
|
|
||||||
<TextField autoFocus autoComplete="name" fullWidth label="Name" />
|
|
||||||
<TextField autoComplete="email" fullWidth label="Email" />
|
|
||||||
<TextField autoComplete="username" fullWidth label="Username" />
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
|
||||||
Example.args = {
|
|
||||||
spacing: 2,
|
|
||||||
};
|
|
@ -1,26 +1,22 @@
|
|||||||
import { ComponentMeta, Story } from "@storybook/react";
|
import { TableRowMenu } from "./TableRowMenu";
|
||||||
import { TableRowMenu, TableRowMenuProps } from "./TableRowMenu";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof TableRowMenu> = {
|
||||||
title: "components/TableRowMenu",
|
title: "components/TableRowMenu",
|
||||||
component: TableRowMenu,
|
component: TableRowMenu,
|
||||||
} as ComponentMeta<typeof TableRowMenu>;
|
|
||||||
|
|
||||||
type DataType = {
|
|
||||||
id: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<TableRowMenuProps<DataType>> = (args) => (
|
export default meta;
|
||||||
<TableRowMenu {...args} />
|
type Story = StoryObj<typeof TableRowMenu<{ id: string }>>;
|
||||||
);
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const Example: Story = {
|
||||||
Example.args = {
|
args: {
|
||||||
data: { id: "123" },
|
data: { id: "123" },
|
||||||
menuItems: [
|
menuItems: [
|
||||||
{ label: "Suspend", onClick: (data) => alert(data.id), disabled: false },
|
{ label: "Suspend", onClick: (data) => alert(data.id), disabled: false },
|
||||||
{ label: "Update", onClick: (data) => alert(data.id), disabled: false },
|
{ label: "Update", onClick: (data) => alert(data.id), disabled: false },
|
||||||
{ label: "Delete", onClick: (data) => alert(data.id), disabled: false },
|
{ label: "Delete", onClick: (data) => alert(data.id), disabled: false },
|
||||||
{ label: "Explode", onClick: (data) => alert(data.id), disabled: true },
|
{ label: "Explode", onClick: (data) => alert(data.id), disabled: true },
|
||||||
],
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { ComponentMeta, Story } from "@storybook/react";
|
|
||||||
import { MockTemplate, MockTemplateVersion } from "testHelpers/entities";
|
import { MockTemplate, MockTemplateVersion } from "testHelpers/entities";
|
||||||
import {
|
import { TemplatePageHeader } from "./TemplatePageHeader";
|
||||||
TemplatePageHeader,
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
TemplatePageHeaderProps,
|
|
||||||
} from "./TemplatePageHeader";
|
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof TemplatePageHeader> = {
|
||||||
title: "Components/TemplatePageHeader",
|
title: "components/TemplatePageHeader",
|
||||||
component: TemplatePageHeader,
|
component: TemplatePageHeader,
|
||||||
args: {
|
args: {
|
||||||
template: MockTemplate,
|
template: MockTemplate,
|
||||||
@ -15,18 +12,17 @@ export default {
|
|||||||
canUpdateTemplate: true,
|
canUpdateTemplate: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as ComponentMeta<typeof TemplatePageHeader>;
|
};
|
||||||
|
|
||||||
const Template: Story<TemplatePageHeaderProps> = (args) => (
|
export default meta;
|
||||||
<TemplatePageHeader {...args} />
|
type Story = StoryObj<typeof TemplatePageHeader>;
|
||||||
);
|
|
||||||
|
|
||||||
export const CanUpdate = Template.bind({});
|
export const CanUpdate: Story = {};
|
||||||
CanUpdate.args = {};
|
|
||||||
|
|
||||||
export const CanNotUpdate = Template.bind({});
|
export const CanNotUpdate: Story = {
|
||||||
CanNotUpdate.args = {
|
args: {
|
||||||
permissions: {
|
permissions: {
|
||||||
canUpdateTemplate: false,
|
canUpdateTemplate: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,16 @@
|
|||||||
import { Story } from "@storybook/react";
|
import { TemplateVersionWarnings } from "./TemplateVersionWarnings";
|
||||||
import {
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
TemplateVersionWarnings,
|
|
||||||
TemplateVersionWarningsProps,
|
|
||||||
} from "./TemplateVersionWarnings";
|
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof TemplateVersionWarnings> = {
|
||||||
title: "components/TemplateVersionWarnings",
|
title: "components/TemplateVersionWarnings",
|
||||||
component: TemplateVersionWarnings,
|
component: TemplateVersionWarnings,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<TemplateVersionWarningsProps> = (args) => (
|
export default meta;
|
||||||
<TemplateVersionWarnings {...args} />
|
type Story = StoryObj<typeof TemplateVersionWarnings>;
|
||||||
);
|
|
||||||
|
|
||||||
export const UnsupportedWorkspaces = Template.bind({});
|
export const UnsupportedWorkspaces: Story = {
|
||||||
UnsupportedWorkspaces.args = {
|
args: {
|
||||||
warnings: ["UNSUPPORTED_WORKSPACES"],
|
warnings: ["UNSUPPORTED_WORKSPACES"],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
import { Story } from "@storybook/react";
|
import { Typography } from "./Typography";
|
||||||
import { Typography, TypographyProps } from "./Typography";
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof Typography> = {
|
||||||
title: "components/Typography",
|
title: "components/Typography",
|
||||||
component: Typography,
|
component: Typography,
|
||||||
|
args: {
|
||||||
|
children: "Colorless green ideas sleep furiously",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<TypographyProps> = (args: TypographyProps) => (
|
export default meta;
|
||||||
<>
|
type Story = StoryObj<typeof Typography>;
|
||||||
<Typography {...args}>Colorless green ideas sleep furiously</Typography>
|
|
||||||
<Typography {...args}>
|
|
||||||
More people have been to France than I have
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const Short = Template.bind({});
|
export const Short: Story = {
|
||||||
Short.args = {
|
args: {
|
||||||
short: true,
|
short: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
export const Tall = Template.bind({});
|
|
||||||
Tall.args = {
|
export const Tall: Story = {
|
||||||
short: false,
|
args: {
|
||||||
|
short: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import { MockUser } from "testHelpers/entities";
|
import { MockUser } from "testHelpers/entities";
|
||||||
import { UserAutocomplete, UserAutocompleteProps } from "./UserAutocomplete";
|
import { UserAutocomplete } from "./UserAutocomplete";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
export default {
|
const meta: Meta<typeof UserAutocomplete> = {
|
||||||
title: "components/UserAutocomplete",
|
title: "components/UserAutocomplete",
|
||||||
component: UserAutocomplete,
|
component: UserAutocomplete,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<UserAutocompleteProps> = (
|
export default meta;
|
||||||
args: UserAutocompleteProps,
|
type Story = StoryObj<typeof UserAutocomplete>;
|
||||||
) => <UserAutocomplete {...args} />;
|
|
||||||
|
|
||||||
export const Example = Template.bind({});
|
export const Example: Story = {
|
||||||
Example.args = {
|
args: {
|
||||||
value: MockUser,
|
value: MockUser,
|
||||||
label: "User",
|
label: "User",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NoLabel = Template.bind({});
|
export const NoLabel: Story = {
|
||||||
NoLabel.args = {
|
args: {
|
||||||
value: MockUser,
|
value: MockUser,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -2,10 +2,9 @@ import { makeStyles } from "@mui/styles";
|
|||||||
import { LogLevel } from "api/typesGenerated";
|
import { LogLevel } from "api/typesGenerated";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { FC, useMemo } from "react";
|
import { FC, useMemo } from "react";
|
||||||
import { MONOSPACE_FONT_FAMILY } from "../../../theme/constants";
|
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||||
import { combineClasses } from "../../../utils/combineClasses";
|
import { combineClasses } from "utils/combineClasses";
|
||||||
import AnsiToHTML from "ansi-to-html";
|
import AnsiToHTML from "ansi-to-html";
|
||||||
import { Theme } from "@mui/material/styles";
|
|
||||||
|
|
||||||
export interface Line {
|
export interface Line {
|
||||||
time: string;
|
time: string;
|
||||||
@ -16,19 +15,15 @@ export interface Line {
|
|||||||
export interface LogsProps {
|
export interface LogsProps {
|
||||||
lines: Line[];
|
lines: Line[];
|
||||||
hideTimestamps?: boolean;
|
hideTimestamps?: boolean;
|
||||||
lineNumbers?: boolean;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
|
export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
|
||||||
hideTimestamps,
|
hideTimestamps,
|
||||||
lines,
|
lines,
|
||||||
lineNumbers,
|
|
||||||
className = "",
|
className = "",
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles({
|
const styles = useStyles();
|
||||||
lineNumbers: Boolean(lineNumbers),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={combineClasses([className, styles.root])}>
|
<div className={combineClasses([className, styles.root])}>
|
||||||
@ -38,9 +33,7 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
|
|||||||
{!hideTimestamps && (
|
{!hideTimestamps && (
|
||||||
<>
|
<>
|
||||||
<span className={styles.time}>
|
<span className={styles.time}>
|
||||||
{lineNumbers
|
{dayjs(line.time).format(`HH:mm:ss.SSS`)}
|
||||||
? idx + 1
|
|
||||||
: dayjs(line.time).format(`HH:mm:ss.SSS`)}
|
|
||||||
</span>
|
</span>
|
||||||
<span className={styles.space} />
|
<span className={styles.space} />
|
||||||
</>
|
</>
|
||||||
@ -63,9 +56,7 @@ export const LogLine: FC<{
|
|||||||
number?: number;
|
number?: number;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
}> = ({ line, hideTimestamp, number, style }) => {
|
}> = ({ line, hideTimestamp, number, style }) => {
|
||||||
const styles = useStyles({
|
const styles = useStyles();
|
||||||
lineNumbers: Boolean(number),
|
|
||||||
});
|
|
||||||
const output = useMemo(() => {
|
const output = useMemo(() => {
|
||||||
return convert.toHtml(line.output.split(/\r/g).pop() as string);
|
return convert.toHtml(line.output.split(/\r/g).pop() as string);
|
||||||
}, [line.output]);
|
}, [line.output]);
|
||||||
@ -89,12 +80,7 @@ export const LogLine: FC<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStyles = makeStyles<
|
const useStyles = makeStyles((theme) => ({
|
||||||
Theme,
|
|
||||||
{
|
|
||||||
lineNumbers: boolean;
|
|
||||||
}
|
|
||||||
>((theme) => ({
|
|
||||||
root: {
|
root: {
|
||||||
minHeight: 156,
|
minHeight: 156,
|
||||||
padding: theme.spacing(1, 0),
|
padding: theme.spacing(1, 0),
|
||||||
@ -116,7 +102,7 @@ const useStyles = makeStyles<
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
fontFamily: MONOSPACE_FONT_FAMILY,
|
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||||
height: ({ lineNumbers }) => (lineNumbers ? logLineHeight : "auto"),
|
height: "auto",
|
||||||
// Whitespace is significant in terminal output for alignment
|
// Whitespace is significant in terminal output for alignment
|
||||||
whiteSpace: "pre",
|
whiteSpace: "pre",
|
||||||
padding: theme.spacing(0, 4),
|
padding: theme.spacing(0, 4),
|
||||||
@ -141,7 +127,7 @@ const useStyles = makeStyles<
|
|||||||
},
|
},
|
||||||
time: {
|
time: {
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
width: ({ lineNumbers }) => theme.spacing(lineNumbers ? 3.5 : 12.5),
|
width: theme.spacing(12.5),
|
||||||
whiteSpace: "pre",
|
whiteSpace: "pre",
|
||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
@ -1,27 +0,0 @@
|
|||||||
import { ComponentMeta, Story } from "@storybook/react";
|
|
||||||
import { LogLevel } from "api/typesGenerated";
|
|
||||||
import { MockWorkspaceBuildLogs } from "../../../testHelpers/entities";
|
|
||||||
import { Logs, LogsProps } from "./Logs";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: "components/Logs",
|
|
||||||
component: Logs,
|
|
||||||
} as ComponentMeta<typeof Logs>;
|
|
||||||
|
|
||||||
const Template: Story<LogsProps> = (args) => <Logs {...args} />;
|
|
||||||
|
|
||||||
const lines = MockWorkspaceBuildLogs.map((log) => ({
|
|
||||||
time: log.created_at,
|
|
||||||
output: log.output,
|
|
||||||
level: "info" as LogLevel,
|
|
||||||
}));
|
|
||||||
export const Example = Template.bind({});
|
|
||||||
Example.args = {
|
|
||||||
lines,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const WithLineNumbers = Template.bind({});
|
|
||||||
WithLineNumbers.args = {
|
|
||||||
lines,
|
|
||||||
lineNumbers: true,
|
|
||||||
};
|
|
@ -1,37 +0,0 @@
|
|||||||
import { ProvisionerJobLog } from "api/typesGenerated";
|
|
||||||
import { groupLogsByStage } from "./WorkspaceBuildLogs";
|
|
||||||
|
|
||||||
describe("groupLogsByStage", () => {
|
|
||||||
it("should group them by stage", () => {
|
|
||||||
const input: ProvisionerJobLog[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
created_at: "oct 13",
|
|
||||||
log_source: "provisioner",
|
|
||||||
log_level: "debug",
|
|
||||||
stage: "build",
|
|
||||||
output: "test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
created_at: "oct 13",
|
|
||||||
log_source: "provisioner",
|
|
||||||
log_level: "debug",
|
|
||||||
stage: "cleanup",
|
|
||||||
output: "test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
created_at: "oct 13",
|
|
||||||
log_source: "provisioner",
|
|
||||||
log_level: "debug",
|
|
||||||
stage: "cleanup",
|
|
||||||
output: "done",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const actual = groupLogsByStage(input);
|
|
||||||
|
|
||||||
expect(actual["cleanup"].length).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
@ -3,7 +3,7 @@ import dayjs from "dayjs";
|
|||||||
import { ComponentProps, FC, Fragment } from "react";
|
import { ComponentProps, FC, Fragment } from "react";
|
||||||
import { ProvisionerJobLog } from "../../api/typesGenerated";
|
import { ProvisionerJobLog } from "../../api/typesGenerated";
|
||||||
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants";
|
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants";
|
||||||
import { Logs } from "./Logs/Logs";
|
import { Logs } from "./Logs";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import { combineClasses } from "utils/combineClasses";
|
import { combineClasses } from "utils/combineClasses";
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Story } from "@storybook/react";
|
|
||||||
import {
|
import {
|
||||||
MockCanceledWorkspace,
|
MockCanceledWorkspace,
|
||||||
MockCancelingWorkspace,
|
MockCancelingWorkspace,
|
||||||
@ -15,16 +14,9 @@ import {
|
|||||||
MockExperiments,
|
MockExperiments,
|
||||||
MockAppearance,
|
MockAppearance,
|
||||||
} from "testHelpers/entities";
|
} from "testHelpers/entities";
|
||||||
import {
|
import { WorkspaceStatusBadge } from "./WorkspaceStatusBadge";
|
||||||
WorkspaceStatusBadge,
|
|
||||||
WorkspaceStatusBadgeProps,
|
|
||||||
} from "./WorkspaceStatusBadge";
|
|
||||||
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
|
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
export default {
|
|
||||||
title: "components/WorkspaceStatusBadge",
|
|
||||||
component: WorkspaceStatusBadge,
|
|
||||||
};
|
|
||||||
|
|
||||||
const MockedAppearance = {
|
const MockedAppearance = {
|
||||||
config: MockAppearance,
|
config: MockAppearance,
|
||||||
@ -33,65 +25,84 @@ const MockedAppearance = {
|
|||||||
save: () => null,
|
save: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Template: Story<WorkspaceStatusBadgeProps> = (args) => (
|
const meta: Meta<typeof WorkspaceStatusBadge> = {
|
||||||
<DashboardProviderContext.Provider
|
title: "components/WorkspaceStatusBadge",
|
||||||
value={{
|
component: WorkspaceStatusBadge,
|
||||||
buildInfo: MockBuildInfo,
|
decorators: [
|
||||||
entitlements: MockEntitlementsWithScheduling,
|
(Story) => (
|
||||||
experiments: MockExperiments,
|
<DashboardProviderContext.Provider
|
||||||
appearance: MockedAppearance,
|
value={{
|
||||||
}}
|
buildInfo: MockBuildInfo,
|
||||||
>
|
entitlements: MockEntitlementsWithScheduling,
|
||||||
<WorkspaceStatusBadge {...args} />
|
experiments: MockExperiments,
|
||||||
</DashboardProviderContext.Provider>
|
appearance: MockedAppearance,
|
||||||
);
|
}}
|
||||||
|
>
|
||||||
export const Running = Template.bind({});
|
<Story />
|
||||||
Running.args = {
|
</DashboardProviderContext.Provider>
|
||||||
workspace: MockWorkspace,
|
),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Starting = Template.bind({});
|
export default meta;
|
||||||
Starting.args = {
|
type Story = StoryObj<typeof WorkspaceStatusBadge>;
|
||||||
workspace: MockStartingWorkspace,
|
|
||||||
|
export const Running: Story = {
|
||||||
|
args: {
|
||||||
|
workspace: MockWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Stopped = Template.bind({});
|
export const Starting: Story = {
|
||||||
Stopped.args = {
|
args: {
|
||||||
workspace: MockStoppedWorkspace,
|
workspace: MockStartingWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Stopping = Template.bind({});
|
export const Stopped: Story = {
|
||||||
Stopping.args = {
|
args: {
|
||||||
workspace: MockStoppingWorkspace,
|
workspace: MockStoppedWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Deleting = Template.bind({});
|
export const Stopping: Story = {
|
||||||
Deleting.args = {
|
args: {
|
||||||
workspace: MockDeletingWorkspace,
|
workspace: MockStoppingWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Deleted = Template.bind({});
|
export const Deleting: Story = {
|
||||||
Deleted.args = {
|
args: {
|
||||||
workspace: MockDeletedWorkspace,
|
workspace: MockDeletingWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Canceling = Template.bind({});
|
export const Deleted: Story = {
|
||||||
Canceling.args = {
|
args: {
|
||||||
workspace: MockCancelingWorkspace,
|
workspace: MockDeletedWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Canceled = Template.bind({});
|
export const Canceling: Story = {
|
||||||
Canceled.args = {
|
args: {
|
||||||
workspace: MockCanceledWorkspace,
|
workspace: MockCancelingWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Failed = Template.bind({});
|
export const Canceled: Story = {
|
||||||
Failed.args = {
|
args: {
|
||||||
workspace: MockFailedWorkspace,
|
workspace: MockCanceledWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Pending = Template.bind({});
|
export const Failed: Story = {
|
||||||
Pending.args = {
|
args: {
|
||||||
workspace: MockPendingWorkspace,
|
workspace: MockFailedWorkspace,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Pending: Story = {
|
||||||
|
args: {
|
||||||
|
workspace: MockPendingWorkspace,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as API from "api/api";
|
import * as API from "api/api";
|
||||||
import { createMachine, assign } from "xstate";
|
import { createMachine, assign } from "xstate";
|
||||||
import { Line } from "components/WorkspaceBuildLogs/Logs/Logs";
|
import { Line } from "components/WorkspaceBuildLogs/Logs";
|
||||||
|
|
||||||
// Logs are stored as the Line interface to make rendering
|
// Logs are stored as the Line interface to make rendering
|
||||||
// much more efficient. Instead of mapping objects each time, we're
|
// much more efficient. Instead of mapping objects each time, we're
|
||||||
|
Reference in New Issue
Block a user