Skip to content

feat: add warning if workspace page becomes stale #4375

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions site/src/components/WarningAlert/WarningAlert.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Story } from "@storybook/react"
import { WarningAlert, WarningAlertProps } from "./WarningAlert"
import Button from "@material-ui/core/Button"

export default {
title: "components/WarningAlert",
component: WarningAlert,
}

const Template: Story<WarningAlertProps> = (args) => <WarningAlert {...args} />

export const ExampleWithDismiss = Template.bind({})
ExampleWithDismiss.args = {
text: "This is a warning",
dismissible: true,
}

const ExampleAction = (
<Button onClick={() => null} size="small">
Button
</Button>
)

export const ExampleWithAction = Template.bind({})
ExampleWithAction.args = {
text: "This is a warning",
actions: [ExampleAction],
}

export const ExampleWithActionAndDismiss = Template.bind({})
ExampleWithActionAndDismiss.args = {
text: "This is a warning",
actions: [ExampleAction],
dismissible: true,
}
61 changes: 61 additions & 0 deletions site/src/components/WarningAlert/WarningAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useState, FC, ReactElement } from "react"
import Collapse from "@material-ui/core/Collapse"
import { Stack } from "components/Stack/Stack"
import { makeStyles, Theme } from "@material-ui/core/styles"
import { colors } from "theme/colors"
import ReportProblemOutlinedIcon from "@material-ui/icons/ReportProblemOutlined"
import Button from "@material-ui/core/Button"
import { useTranslation } from "react-i18next"

export interface WarningAlertProps {
text: string
dismissible?: boolean
actions?: ReactElement[]
}

export const WarningAlert: FC<WarningAlertProps> = ({
text,
dismissible = false,
actions = [],
}) => {
const { t } = useTranslation("common")
const [open, setOpen] = useState(true)
const classes = useStyles()

return (
<Collapse in={open}>
<Stack
className={classes.alertContainer}
direction="row"
alignItems="center"
spacing={0}
justifyContent="space-between"
>
<Stack direction="row" spacing={1}>
<ReportProblemOutlinedIcon fontSize="small" className={classes.alertIcon} />
{text}
</Stack>
<Stack direction="row">
{actions.length > 0 && actions.map((action) => <div key={String(action)}>{action}</div>)}
{dismissible && (
<Button size="small" onClick={() => setOpen(false)} variant="outlined">
{t("ctas.dismissCta")}
</Button>
)}
</Stack>
</Stack>
</Collapse>
)
}

const useStyles = makeStyles<Theme>((theme) => ({
alertContainer: {
border: `1px solid ${colors.orange[7]}`,
borderRadius: theme.shape.borderRadius,
padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
backgroundColor: `${colors.gray[16]}`,
},
alertIcon: {
color: `${colors.orange[7]}`,
},
}))
17 changes: 11 additions & 6 deletions site/src/components/Workspace/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { WorkspaceScheduleBanner } from "../WorkspaceScheduleBanner/WorkspaceSch
import { WorkspaceScheduleButton } from "../WorkspaceScheduleButton/WorkspaceScheduleButton"
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
import { WorkspaceStats } from "../WorkspaceStats/WorkspaceStats"
import { WarningAlert } from "../WarningAlert/WarningAlert"
import { useTranslation } from "react-i18next"

export enum WorkspaceErrors {
GET_RESOURCES_ERROR = "getResourcesError",
Expand Down Expand Up @@ -71,19 +73,21 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
buildInfo,
applicationsHost,
}) => {
const { t } = useTranslation("workspacePage")
const styles = useStyles()
const navigate = useNavigate()
const hasTemplateIcon = workspace.template_icon && workspace.template_icon !== ""

const buildError = workspaceErrors[WorkspaceErrors.BUILD_ERROR] ? (
const buildError = Boolean(workspaceErrors[WorkspaceErrors.BUILD_ERROR]) && (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.BUILD_ERROR]} dismissible />
) : (
<></>
)
const cancellationError = workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR] ? (

const cancellationError = Boolean(workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]) && (
<ErrorSummary error={workspaceErrors[WorkspaceErrors.CANCELLATION_ERROR]} dismissible />
) : (
<></>
)

const workspaceRefreshWarning = Boolean(workspaceErrors[WorkspaceErrors.GET_RESOURCES_ERROR]) && (
<WarningAlert text={t("warningsAndErrors.workspaceRefreshWarning")} dismissible />
)

return (
Expand Down Expand Up @@ -127,6 +131,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
<Stack direction="column" className={styles.firstColumnSpacer} spacing={2.5}>
{buildError}
{cancellationError}
{workspaceRefreshWarning}

<WorkspaceScheduleBanner
isLoading={bannerProps.isLoading}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import Button from "@material-ui/core/Button"
import { makeStyles } from "@material-ui/core/styles"
import Alert from "@material-ui/lab/Alert"
import AlertTitle from "@material-ui/lab/AlertTitle"
import { Maybe } from "components/Conditionals/Maybe"
import { FC } from "react"
import * as TypesGen from "../../api/typesGenerated"

const Language = {
bannerTitle: "This workspace has been deleted and cannot be edited.",
createWorkspaceCta: "Create new workspace",
}
import { WarningAlert } from "components/WarningAlert/WarningAlert"
import { useTranslation } from "react-i18next"
import { Maybe } from "components/Conditionals/Maybe"

export interface WorkspaceDeletedBannerProps {
workspace: TypesGen.Workspace
Expand All @@ -20,32 +14,19 @@ export const WorkspaceDeletedBanner: FC<React.PropsWithChildren<WorkspaceDeleted
workspace,
handleClick,
}) => {
const styles = useStyles()
const { t } = useTranslation("workspacePage")
const NewWorkspaceButton = (
<Button onClick={handleClick} size="small">
{t("ctas.createWorkspaceCta")}
</Button>
)

return (
<Maybe condition={workspace.latest_build.status === "deleted"}>
<Alert
className={styles.root}
action={
<Button color="inherit" onClick={handleClick} size="small">
{Language.createWorkspaceCta}
</Button>
}
severity="warning"
>
<AlertTitle>{Language.bannerTitle}</AlertTitle>
</Alert>
<WarningAlert
text={t("warningsAndErrors.workspaceDeletedWarning")}
actions={[NewWorkspaceButton]}
/>
</Maybe>
)
}

export const useStyles = makeStyles(() => {
return {
root: {
alignItems: "center",
"& .MuiAlertTitle-root": {
marginBottom: "0px",
},
},
}
})
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import Button from "@material-ui/core/Button"
import Alert from "@material-ui/lab/Alert"
import AlertTitle from "@material-ui/lab/AlertTitle"
import dayjs from "dayjs"
import isSameOrBefore from "dayjs/plugin/isSameOrBefore"
import utc from "dayjs/plugin/utc"
import { FC } from "react"
import * as TypesGen from "../../api/typesGenerated"
import { isWorkspaceOn } from "../../util/workspace"
import * as TypesGen from "api/typesGenerated"
import { isWorkspaceOn } from "util/workspace"
import { WarningAlert } from "components/WarningAlert/WarningAlert"
import { useTranslation } from "react-i18next"

dayjs.extend(utc)
dayjs.extend(isSameOrBefore)

export const Language = {
bannerAction: "Extend",
bannerTitle: "Your workspace is scheduled to automatically shut down soon.",
}

export interface WorkspaceScheduleBannerProps {
isLoading?: boolean
onExtend: () => void
Expand All @@ -36,26 +31,22 @@ export const WorkspaceScheduleBanner: FC<React.PropsWithChildren<WorkspaceSchedu
onExtend,
workspace,
}) => {
const { t } = useTranslation("workspacePage")

if (!shouldDisplay(workspace)) {
return null
} else {
return (
<Alert
action={
<Button
variant="outlined"
color="inherit"
disabled={isLoading}
onClick={onExtend}
size="small"
>
{Language.bannerAction}
</Button>
}
severity="warning"
>
<AlertTitle>{Language.bannerTitle}</AlertTitle>
</Alert>
)
}

const ScheduleButton = (
<Button variant="outlined" disabled={isLoading} onClick={onExtend} size="small">
{t("ctas.extendScheduleCta")}
</Button>
)

return (
<WarningAlert
text={t("warningsAndErrors.workspaceShutdownWarning")}
actions={[ScheduleButton]}
/>
)
}
3 changes: 3 additions & 0 deletions site/src/i18n/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@
"confirm": "Are you sure you want to proceed? Type the name of this {{entity}} below to confirm.",
"confirmLabel": "Name of {{entity}} to delete",
"incorrectName": "Incorrect {{entity}} name."
},
"ctas": {
"dismissCta": "Dismiss"
}
}
9 changes: 9 additions & 0 deletions site/src/i18n/en/workspacePage.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
"editDeadlineMinus": "Subtract one hour",
"editDeadlinePlus": "Add one hour"
},
"ctas": {
"createWorkspaceCta": "Create new workspace",
"extendScheduleCta": "Extend"
},
"warningsAndErrors": {
"workspaceRefreshWarning": "We're having difficulty fetching the latest workspace state. Refresh the page to see the newest changes.",
"workspaceDeletedWarning": "This workspace has been deleted and cannot be edited.",
"workspaceShutdownWarning": "Your workspace is scheduled to automatically shut down soon."
},
"actionButton": {
"start": "Start",
"stop": "Stop",
Expand Down