feat: show version messages in version lists (#9708)

This commit is contained in:
Kayla Washburn
2023-09-18 11:17:07 -06:00
committed by GitHub
parent 622442203d
commit f85aa443dd
5 changed files with 193 additions and 108 deletions
@@ -0,0 +1,23 @@
import { InfoTooltip } from "./InfoTooltip";
import type { Meta, StoryObj } from "@storybook/react";
const meta: Meta<typeof InfoTooltip> = {
title: "components/InfoTooltip",
component: InfoTooltip,
args: {
type: "info",
title: "Hello, friend!",
message: "Today is a lovely day :^)",
},
};
export default meta;
type Story = StoryObj<typeof InfoTooltip>;
export const Example: Story = {};
export const Warning: Story = {
args: {
type: "warning",
message: "Unfortunately, there's a radio connected to my brain",
},
};
@@ -0,0 +1,47 @@
import { type FC, type ReactNode } from "react";
import {
HelpTooltip,
HelpTooltipText,
HelpTooltipTitle,
} from "components/HelpTooltip/HelpTooltip";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { makeStyles } from "@mui/styles";
import { colors } from "theme/colors";
interface InfoTooltipProps {
type?: "warning" | "info";
title: ReactNode;
message: ReactNode;
}
export const InfoTooltip: FC<InfoTooltipProps> = (props) => {
const { title, message, type = "info" } = props;
const styles = useStyles({ type });
return (
<HelpTooltip
size="small"
icon={InfoIcon}
iconClassName={styles.icon}
buttonClassName={styles.button}
>
<HelpTooltipTitle>{title}</HelpTooltipTitle>
<HelpTooltipText>{message}</HelpTooltipText>
</HelpTooltip>
);
};
const useStyles = makeStyles<unknown, Pick<InfoTooltipProps, "type">>(() => ({
icon: ({ type }) => ({
color: type === "info" ? colors.blue[5] : colors.yellow[5],
}),
button: {
opacity: 1,
"&:hover": {
opacity: 1,
},
},
}));
@@ -6,6 +6,7 @@ import { Pill } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
import { useClickableTableRow } from "hooks/useClickableTableRow";
import { useNavigate } from "react-router-dom";
import { colors } from "theme/colors";
@@ -63,6 +64,10 @@ export const VersionRow: React.FC<VersionRowProps> = ({
version <strong>{version.name}</strong>
</span>
{version.message && (
<InfoTooltip title="Message" message={version.message} />
)}
<span className={styles.versionTime}>
{new Date(version.created_at).toLocaleTimeString()}
</span>
@@ -2,6 +2,7 @@ import { DialogProps } from "components/Dialogs/Dialog";
import { FC, useRef, useState } from "react";
import { FormFields } from "components/Form/Form";
import TextField from "@mui/material/TextField";
import { makeStyles } from "@mui/styles";
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { Stack } from "components/Stack/Stack";
import { Template, TemplateVersion } from "api/typesGenerated";
@@ -13,6 +14,9 @@ import { Pill } from "components/Pill/Pill";
import { Avatar } from "components/Avatar/Avatar";
import CircularProgress from "@mui/material/CircularProgress";
import Box from "@mui/material/Box";
import { Alert, AlertDetail } from "components/Alert/Alert";
import AlertTitle from "@mui/material/AlertTitle";
import InfoIcon from "@mui/icons-material/InfoOutlined";
export type ChangeVersionDialogProps = DialogProps & {
template: Template | undefined;
@@ -32,6 +36,9 @@ export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
}) => {
const [isAutocompleteOpen, setIsAutocompleteOpen] = useState(false);
const selectedTemplateVersion = useRef<TemplateVersion | undefined>();
const version = selectedTemplateVersion.current;
const styles = useStyles();
return (
<ConfirmDialog
@@ -51,74 +58,103 @@ export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
<Stack>
<p>You are about to change the version of this workspace.</p>
{templateVersions ? (
<FormFields>
<Autocomplete
disableClearable
options={templateVersions}
defaultValue={defaultTemplateVersion}
id="template-version-autocomplete"
open={isAutocompleteOpen}
onChange={(_, newTemplateVersion) => {
selectedTemplateVersion.current =
newTemplateVersion ?? undefined;
}}
onOpen={() => {
setIsAutocompleteOpen(true);
}}
onClose={() => {
setIsAutocompleteOpen(false);
}}
isOptionEqualToValue={(
option: TemplateVersion,
value: TemplateVersion,
) => option.id === value.id}
getOptionLabel={(option) => option.name}
renderOption={(props, option: TemplateVersion) => (
<Box component="li" {...props}>
<AvatarData
avatar={
<Avatar src={option.created_by.avatar_url}>
{option.name}
</Avatar>
}
title={
<Stack
direction="row"
justifyContent="space-between"
style={{ width: "100%" }}
>
{option.name}
{template?.active_version_id === option.id && (
<Pill text="Active" type="success" />
)}
</Stack>
}
subtitle={createDayString(option.created_at)}
/>
</Box>
)}
renderInput={(params) => (
<>
<TextField
{...params}
fullWidth
placeholder="Template version name"
InputProps={{
...params.InputProps,
endAdornment: (
<>
{!templateVersions ? (
<CircularProgress size={16} />
) : null}
{params.InputProps.endAdornment}
</>
),
}}
/>
</>
)}
/>
</FormFields>
<>
<FormFields>
<Autocomplete
disableClearable
options={templateVersions}
defaultValue={defaultTemplateVersion}
id="template-version-autocomplete"
open={isAutocompleteOpen}
onChange={(_, newTemplateVersion) => {
selectedTemplateVersion.current =
newTemplateVersion ?? undefined;
}}
onOpen={() => {
setIsAutocompleteOpen(true);
}}
onClose={() => {
setIsAutocompleteOpen(false);
}}
isOptionEqualToValue={(
option: TemplateVersion,
value: TemplateVersion,
) => option.id === value.id}
getOptionLabel={(option) => option.name}
renderOption={(props, option: TemplateVersion) => (
<Box component="li" {...props}>
<AvatarData
avatar={
<Avatar src={option.created_by.avatar_url}>
{option.name}
</Avatar>
}
title={
<Stack
direction="row"
justifyContent="space-between"
style={{ width: "100%" }}
>
<Stack
direction="row"
alignItems="center"
spacing={1}
>
{option.name}
{option.message && (
<InfoIcon
sx={(theme) => ({
width: theme.spacing(1.5),
height: theme.spacing(1.5),
})}
/>
)}
</Stack>
{template?.active_version_id === option.id && (
<Pill text="Active" type="success" />
)}
</Stack>
}
subtitle={createDayString(option.created_at)}
/>
</Box>
)}
renderInput={(params) => (
<>
<TextField
{...params}
fullWidth
placeholder="Template version name"
InputProps={{
...params.InputProps,
endAdornment: (
<>
{!templateVersions ? (
<CircularProgress size={16} />
) : null}
{params.InputProps.endAdornment}
</>
),
classes: {
root: styles.inputRoot,
},
}}
/>
</>
)}
/>
</FormFields>
{version && (
<Alert severity="info">
<AlertTitle>
Published by {version.created_by.username}
</AlertTitle>
{version.message && (
<AlertDetail>{version.message}</AlertDetail>
)}
</Alert>
)}
</>
) : (
<Loader />
)}
@@ -127,3 +163,9 @@ export const ChangeVersionDialog: FC<ChangeVersionDialogProps> = ({
/>
);
};
export const useStyles = makeStyles((theme) => ({
inputRoot: {
paddingLeft: `${theme.spacing(1.75)} !important`, // Same padding left as input
},
}));
@@ -16,13 +16,6 @@ import Button from "@mui/material/Button";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import { Link as RouterLink, useNavigate } from "react-router-dom";
import { makeStyles } from "@mui/styles";
import {
HelpTooltip,
HelpTooltipText,
HelpTooltipTitle,
} from "components/HelpTooltip/HelpTooltip";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import { colors } from "theme/colors";
import { useClickableTableRow } from "hooks/useClickableTableRow";
import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight";
import Box from "@mui/material/Box";
@@ -36,6 +29,7 @@ import { getDisplayWorkspaceTemplateName } from "utils/workspace";
import Checkbox from "@mui/material/Checkbox";
import { AvatarDataSkeleton } from "components/AvatarData/AvatarDataSkeleton";
import Skeleton from "@mui/material/Skeleton";
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip";
export interface WorkspacesTableProps {
workspaces?: Workspace[];
@@ -215,7 +209,13 @@ export const WorkspacesTable: FC<WorkspacesTableProps> = ({
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<WorkspaceStatusBadge workspace={workspace} />
{workspace.latest_build.status === "running" &&
!workspace.health.healthy && <UnhealthyTooltip />}
!workspace.health.healthy && (
<InfoTooltip
type="warning"
title="Workspace is unhealthy"
message="Your workspace is running but some agents are unhealthy."
/>
)}
</Box>
</TableCell>
@@ -269,24 +269,6 @@ const WorkspacesRow: FC<{
);
};
export const UnhealthyTooltip = () => {
const styles = useUnhealthyTooltipStyles();
return (
<HelpTooltip
size="small"
icon={InfoIcon}
iconClassName={styles.unhealthyIcon}
buttonClassName={styles.unhealthyButton}
>
<HelpTooltipTitle>Workspace is unhealthy</HelpTooltipTitle>
<HelpTooltipText>
Your workspace is running but some agents are unhealthy.
</HelpTooltipText>
</HelpTooltip>
);
};
const TableLoader = ({
canCheckWorkspaces,
}: {
@@ -324,20 +306,6 @@ const cantBeChecked = (workspace: Workspace) => {
return ["deleting", "pending"].includes(workspace.latest_build.status);
};
const useUnhealthyTooltipStyles = makeStyles(() => ({
unhealthyIcon: {
color: colors.yellow[5],
},
unhealthyButton: {
opacity: 1,
"&:hover": {
opacity: 1,
},
},
}));
const useStyles = makeStyles((theme) => ({
withImage: {
paddingBottom: 0,