refactor: Make workspace status more visible (#3130)

This commit is contained in:
Bruno Quaresma 2022-07-22 16:18:52 -03:00 committed by GitHub
parent 1b19a09a37
commit ca93614c3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 275 additions and 85 deletions

View File

@ -168,7 +168,7 @@ const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`,
},
errorRoot: {
color: theme.palette.error.dark,
color: theme.palette.error.main,
},
inputStyles: {
height: "100%",

View File

@ -1,4 +1,5 @@
import { makeStyles } from "@material-ui/core/styles"
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
import React, { FC } from "react"
import { useNavigate } from "react-router-dom"
import * as TypesGen from "../../api/typesGenerated"
@ -77,6 +78,7 @@ export const Workspace: FC<WorkspaceProps> = ({
</Stack>
}
>
<WorkspaceStatusBadge build={workspace.latest_build} className={styles.statusBadge} />
<PageHeaderTitle>{workspace.name}</PageHeaderTitle>
<PageHeaderSubtitle>{workspace.owner_name}</PageHeaderSubtitle>
</PageHeader>
@ -118,6 +120,9 @@ const spacerWidth = 300
export const useStyles = makeStyles((theme) => {
return {
statusBadge: {
marginBottom: theme.spacing(3),
},
firstColumnSpacer: {
flex: 2,
},

View File

@ -5,7 +5,7 @@ import { FC } from "react"
import { Link as RouterLink } from "react-router-dom"
import { combineClasses } from "util/combineClasses"
import { createDayString } from "util/createDayString"
import { getDisplayStatus, getDisplayWorkspaceBuildInitiatedBy } from "util/workspace"
import { getDisplayWorkspaceBuildInitiatedBy } from "util/workspace"
import { Workspace } from "../../api/typesGenerated"
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
@ -28,7 +28,6 @@ export interface WorkspaceStatsProps {
export const WorkspaceStats: FC<WorkspaceStatsProps> = ({ workspace, handleUpdate }) => {
const styles = useStyles()
const theme = useTheme()
const status = getDisplayStatus(theme, workspace.latest_build)
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(workspace.latest_build)
return (
@ -69,15 +68,6 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({ workspace, handleUpdat
<span className={styles.statsLabel}>{Language.byLabel}</span>
<span className={styles.statsValue}>{initiatedBy}</span>
</div>
<div className={styles.statsDivider} />
<div className={styles.statItem}>
<span className={styles.statsLabel}>{Language.statusLabel}</span>
<span className={styles.statsValue}>
<span style={{ color: status.color }} role="status">
{status.status}
</span>
</span>
</div>
</div>
)
}

View File

@ -0,0 +1,71 @@
import { Story } from "@storybook/react"
import {
MockCanceledWorkspace,
MockCancelingWorkspace,
MockDeletedWorkspace,
MockDeletingWorkspace,
MockFailedWorkspace,
MockQueuedWorkspace,
MockStartingWorkspace,
MockStoppedWorkspace,
MockStoppingWorkspace,
MockWorkspace,
} from "testHelpers/renderHelpers"
import { WorkspaceStatusBadge, WorkspaceStatusBadgeProps } from "./WorkspaceStatusBadge"
export default {
title: "components/WorkspaceStatusBadge",
component: WorkspaceStatusBadge,
}
const Template: Story<WorkspaceStatusBadgeProps> = (args) => <WorkspaceStatusBadge {...args} />
export const Running = Template.bind({})
Running.args = {
build: MockWorkspace.latest_build,
}
export const Starting = Template.bind({})
Starting.args = {
build: MockStartingWorkspace.latest_build,
}
export const Stopped = Template.bind({})
Stopped.args = {
build: MockStoppedWorkspace.latest_build,
}
export const Stopping = Template.bind({})
Stopping.args = {
build: MockStoppingWorkspace.latest_build,
}
export const Deleting = Template.bind({})
Deleting.args = {
build: MockDeletingWorkspace.latest_build,
}
export const Deleted = Template.bind({})
Deleted.args = {
build: MockDeletedWorkspace.latest_build,
}
export const Canceling = Template.bind({})
Canceling.args = {
build: MockCancelingWorkspace.latest_build,
}
export const Canceled = Template.bind({})
Canceled.args = {
build: MockCanceledWorkspace.latest_build,
}
export const Failed = Template.bind({})
Failed.args = {
build: MockFailedWorkspace.latest_build,
}
export const Queued = Template.bind({})
Queued.args = {
build: MockQueuedWorkspace.latest_build,
}

View File

@ -0,0 +1,174 @@
import CircularProgress from "@material-ui/core/CircularProgress"
import { makeStyles, Theme, useTheme } from "@material-ui/core/styles"
import ErrorIcon from "@material-ui/icons/ErrorOutline"
import PlayIcon from "@material-ui/icons/PlayArrowOutlined"
import StopIcon from "@material-ui/icons/StopOutlined"
import { WorkspaceBuild } from "api/typesGenerated"
import React from "react"
import { MONOSPACE_FONT_FAMILY } from "theme/constants"
import { combineClasses } from "util/combineClasses"
import { getWorkspaceStatus } from "util/workspace"
const StatusLanguage = {
loading: "Loading",
started: "Running",
starting: "Starting",
stopping: "Stopping",
stopped: "Stopped",
deleting: "Deleting",
deleted: "Deleted",
canceling: "Canceling action",
canceled: "Canceled action",
failed: "Failed",
queued: "Queued",
}
const LoadingIcon: React.FC = () => {
return <CircularProgress size={10} style={{ color: "#FFF" }} />
}
export const getStatus = (
theme: Theme,
build: WorkspaceBuild,
): {
borderColor: string
backgroundColor: string
text: string
icon: React.ReactNode
} => {
const status = getWorkspaceStatus(build)
switch (status) {
case undefined:
return {
borderColor: theme.palette.text.secondary,
backgroundColor: theme.palette.text.secondary,
text: StatusLanguage.loading,
icon: <LoadingIcon />,
}
case "started":
return {
borderColor: theme.palette.success.main,
backgroundColor: theme.palette.success.dark,
text: StatusLanguage.started,
icon: <PlayIcon />,
}
case "starting":
return {
borderColor: theme.palette.success.main,
backgroundColor: theme.palette.success.dark,
text: StatusLanguage.starting,
icon: <LoadingIcon />,
}
case "stopping":
return {
borderColor: theme.palette.warning.main,
backgroundColor: theme.palette.warning.dark,
text: StatusLanguage.stopping,
icon: <LoadingIcon />,
}
case "stopped":
return {
borderColor: theme.palette.warning.main,
backgroundColor: theme.palette.warning.dark,
text: StatusLanguage.stopped,
icon: <StopIcon />,
}
case "deleting":
return {
borderColor: theme.palette.warning.main,
backgroundColor: theme.palette.warning.dark,
text: StatusLanguage.deleting,
icon: <LoadingIcon />,
}
case "deleted":
return {
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.dark,
text: StatusLanguage.deleted,
icon: <ErrorIcon />,
}
case "canceling":
return {
borderColor: theme.palette.warning.main,
backgroundColor: theme.palette.warning.dark,
text: StatusLanguage.canceling,
icon: <LoadingIcon />,
}
case "canceled":
return {
borderColor: theme.palette.warning.main,
backgroundColor: theme.palette.warning.dark,
text: StatusLanguage.canceled,
icon: <ErrorIcon />,
}
case "error":
return {
borderColor: theme.palette.error.main,
backgroundColor: theme.palette.error.dark,
text: StatusLanguage.failed,
icon: <ErrorIcon />,
}
case "queued":
return {
borderColor: theme.palette.info.main,
backgroundColor: theme.palette.info.dark,
text: StatusLanguage.queued,
icon: <LoadingIcon />,
}
}
throw new Error("unknown text " + status)
}
export type WorkspaceStatusBadgeProps = {
build: WorkspaceBuild
className?: string
}
export const WorkspaceStatusBadge: React.FC<WorkspaceStatusBadgeProps> = ({ build, className }) => {
const styles = useStyles()
const theme = useTheme()
const { text, icon, ...colorStyles } = getStatus(theme, build)
return (
<div
className={combineClasses([styles.wrapper, className])}
style={{ ...colorStyles }}
role="status"
>
<div className={styles.iconWrapper}>{icon}</div>
{text}
</div>
)
}
const useStyles = makeStyles((theme) => ({
wrapper: {
fontFamily: MONOSPACE_FONT_FAMILY,
display: "inline-flex",
alignItems: "center",
borderWidth: 1,
borderStyle: "solid",
borderRadius: 99999,
fontSize: 14,
fontWeight: 500,
color: "#FFF",
height: theme.spacing(3),
paddingLeft: theme.spacing(0.75),
paddingRight: theme.spacing(1.5),
whiteSpace: "nowrap",
},
iconWrapper: {
marginRight: theme.spacing(0.5),
width: theme.spacing(2),
height: theme.spacing(2),
lineHeight: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
"& > svg": {
width: theme.spacing(2),
height: theme.spacing(2),
},
},
}))

View File

@ -3,10 +3,11 @@ import TableRow from "@material-ui/core/TableRow"
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
import useTheme from "@material-ui/styles/useTheme"
import { useActor } from "@xstate/react"
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
import { FC } from "react"
import { useNavigate } from "react-router-dom"
import { createDayString } from "util/createDayString"
import { getDisplayStatus, getDisplayWorkspaceBuildInitiatedBy } from "../../util/workspace"
import { getDisplayWorkspaceBuildInitiatedBy } from "util/workspace"
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
import { AvatarData } from "../AvatarData/AvatarData"
import {
@ -28,7 +29,6 @@ export const WorkspacesRow: FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ w
const theme: Theme = useTheme()
const [workspaceState, send] = useActor(workspaceRef)
const { data: workspace } = workspaceState.context
const status = getDisplayStatus(theme, workspace.latest_build)
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(workspace.latest_build)
const workspacePageLink = `/@${workspace.owner_name}/${workspace.name}`
@ -74,7 +74,7 @@ export const WorkspacesRow: FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({ w
</TableCellLink>
<TableCellLink to={workspacePageLink}>
<span style={{ color: status.color }}>{status.status}</span>
<WorkspaceStatusBadge build={workspace.latest_build} />
</TableCellLink>
<TableCellLink to={workspacePageLink}>
<div className={styles.arrowCell}>

View File

@ -98,7 +98,10 @@ export const MockRunningProvisionerJob: TypesGen.ProvisionerJob = {
...MockProvisionerJob,
status: "running",
}
export const MockPendingProvisionerJob: TypesGen.ProvisionerJob = {
...MockProvisionerJob,
status: "pending",
}
export const MockTemplateVersion: TypesGen.TemplateVersion = {
id: "test-template-version",
created_at: "2022-05-17T17:39:01.382927298Z",
@ -236,6 +239,15 @@ export const MockDeletedWorkspace: TypesGen.Workspace = {
export const MockOutdatedWorkspace: TypesGen.Workspace = { ...MockFailedWorkspace, outdated: true }
export const MockQueuedWorkspace: TypesGen.Workspace = {
...MockWorkspace,
latest_build: {
...MockWorkspaceBuild,
job: MockPendingProvisionerJob,
transition: "start",
},
}
export const MockWorkspaceApp: TypesGen.WorkspaceApp = {
id: "test-app",
name: "test-app",

View File

@ -24,11 +24,18 @@ export const darkPalette: PaletteOptions = {
divider: "hsl(221, 32%, 26%)",
warning: {
main: "hsl(20, 79%, 53%)",
dark: "#57250C",
},
success: {
main: "hsl(142, 58%, 41%)",
dark: "#205027",
},
info: {
main: "hsl(219, 67%, 54%)",
dark: "#0C2551",
},
error: {
main: "#D8666C",
dark: "#511112",
},
}

View File

@ -67,75 +67,6 @@ export const DisplayStatusLanguage = {
queued: "Queued",
}
// Localize workspace status and provide corresponding color from theme
export const getDisplayStatus = (
theme: Theme,
build: TypesGen.WorkspaceBuild,
): {
color: string
status: string
} => {
const status = getWorkspaceStatus(build)
switch (status) {
case undefined:
return {
color: theme.palette.text.secondary,
status: DisplayStatusLanguage.loading,
}
case "started":
return {
color: theme.palette.success.main,
status: `⦿ ${DisplayStatusLanguage.started}`,
}
case "starting":
return {
color: theme.palette.primary.main,
status: `⦿ ${DisplayStatusLanguage.starting}`,
}
case "stopping":
return {
color: theme.palette.primary.main,
status: `${DisplayStatusLanguage.stopping}`,
}
case "stopped":
return {
color: theme.palette.text.secondary,
status: `${DisplayStatusLanguage.stopped}`,
}
case "deleting":
return {
color: theme.palette.text.secondary,
status: `${DisplayStatusLanguage.deleting}`,
}
case "deleted":
return {
color: theme.palette.text.secondary,
status: `${DisplayStatusLanguage.deleted}`,
}
case "canceling":
return {
color: theme.palette.warning.light,
status: `${DisplayStatusLanguage.canceling}`,
}
case "canceled":
return {
color: theme.palette.text.secondary,
status: `${DisplayStatusLanguage.canceled}`,
}
case "error":
return {
color: theme.palette.error.main,
status: `${DisplayStatusLanguage.failed}`,
}
case "queued":
return {
color: theme.palette.text.secondary,
status: `${DisplayStatusLanguage.queued}`,
}
}
throw new Error("unknown status " + status)
}
export const DisplayWorkspaceBuildStatusLanguage = {
succeeded: "Succeeded",
pending: "Pending",