mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat: Redesign build logs (#4734)
This commit is contained in:
71
site/src/components/BuildsTable/BuildAvatar.tsx
Normal file
71
site/src/components/BuildsTable/BuildAvatar.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import Avatar from "@material-ui/core/Avatar"
|
||||||
|
import Badge from "@material-ui/core/Badge"
|
||||||
|
import { Theme, useTheme, withStyles } from "@material-ui/core/styles"
|
||||||
|
import { FC } from "react"
|
||||||
|
import PlayArrowOutlined from "@material-ui/icons/PlayArrowOutlined"
|
||||||
|
import PauseOutlined from "@material-ui/icons/PauseOutlined"
|
||||||
|
import DeleteOutlined from "@material-ui/icons/DeleteOutlined"
|
||||||
|
import { WorkspaceBuild, WorkspaceTransition } from "api/typesGenerated"
|
||||||
|
import { getDisplayWorkspaceBuildStatus } from "util/workspace"
|
||||||
|
import { PaletteIndex } from "theme/palettes"
|
||||||
|
|
||||||
|
interface StylesBadgeProps {
|
||||||
|
type: PaletteIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledBadge = withStyles((theme) => ({
|
||||||
|
badge: {
|
||||||
|
backgroundColor: ({ type }: StylesBadgeProps) => theme.palette[type].light,
|
||||||
|
borderRadius: "100%",
|
||||||
|
width: 8,
|
||||||
|
minWidth: 8,
|
||||||
|
height: 8,
|
||||||
|
display: "block",
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
}))(Badge)
|
||||||
|
|
||||||
|
const StyledAvatar = withStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
background: theme.palette.background.paperLight,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
border: `2px solid ${theme.palette.divider}`,
|
||||||
|
|
||||||
|
"& svg": {
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))(Avatar)
|
||||||
|
|
||||||
|
export type BuildAvatarProps = {
|
||||||
|
build: WorkspaceBuild
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconByTransition: Record<WorkspaceTransition, JSX.Element> = {
|
||||||
|
start: <PlayArrowOutlined />,
|
||||||
|
stop: <PauseOutlined />,
|
||||||
|
delete: <DeleteOutlined />,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BuildAvatar: FC<BuildAvatarProps> = ({ build }) => {
|
||||||
|
const theme = useTheme<Theme>()
|
||||||
|
const displayBuildStatus = getDisplayWorkspaceBuildStatus(theme, build)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledBadge
|
||||||
|
role="status"
|
||||||
|
type={displayBuildStatus.type}
|
||||||
|
arial-label={displayBuildStatus.status}
|
||||||
|
title={displayBuildStatus.status}
|
||||||
|
overlap="circular"
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "bottom",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
badgeContent={<div></div>}
|
||||||
|
>
|
||||||
|
<StyledAvatar>{iconByTransition[build.transition]}</StyledAvatar>
|
||||||
|
</StyledBadge>
|
||||||
|
)
|
||||||
|
}
|
46
site/src/components/BuildsTable/BuildDateRow.tsx
Normal file
46
site/src/components/BuildsTable/BuildDateRow.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
|
import TableCell from "@material-ui/core/TableCell"
|
||||||
|
import TableRow from "@material-ui/core/TableRow"
|
||||||
|
import formatRelative from "date-fns/formatRelative"
|
||||||
|
import { FC } from "react"
|
||||||
|
|
||||||
|
export interface BuildDateRow {
|
||||||
|
date: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BuildDateRow: FC<BuildDateRow> = ({ date }) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
// We only want the message related to the date since the time is displayed
|
||||||
|
// inside of the build row
|
||||||
|
const displayDate = formatRelative(date, new Date()).split("at")[0]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow className={styles.buildDateRow}>
|
||||||
|
<TableCell
|
||||||
|
className={styles.buildDateCell}
|
||||||
|
title={date.toLocaleDateString()}
|
||||||
|
>
|
||||||
|
{displayDate}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
buildDateRow: {
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
|
||||||
|
"&:not(:first-child) td": {
|
||||||
|
borderTop: `1px solid ${theme.palette.divider}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
buildDateCell: {
|
||||||
|
padding: `${theme.spacing(1, 4)} !important`,
|
||||||
|
background: `${theme.palette.background.paperLight} !important`,
|
||||||
|
fontSize: 12,
|
||||||
|
position: "relative",
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
textTransform: "capitalize",
|
||||||
|
},
|
||||||
|
}))
|
144
site/src/components/BuildsTable/BuildRow.tsx
Normal file
144
site/src/components/BuildsTable/BuildRow.tsx
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { makeStyles } from "@material-ui/core/styles"
|
||||||
|
import TableCell from "@material-ui/core/TableCell"
|
||||||
|
import TableRow from "@material-ui/core/TableRow"
|
||||||
|
import { WorkspaceBuild } from "api/typesGenerated"
|
||||||
|
import { Stack } from "components/Stack/Stack"
|
||||||
|
import { useClickable } from "hooks/useClickable"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { MONOSPACE_FONT_FAMILY } from "theme/constants"
|
||||||
|
import {
|
||||||
|
displayWorkspaceBuildDuration,
|
||||||
|
getDisplayWorkspaceBuildInitiatedBy,
|
||||||
|
} from "util/workspace"
|
||||||
|
import { BuildAvatar } from "./BuildAvatar"
|
||||||
|
|
||||||
|
export interface BuildRowProps {
|
||||||
|
build: WorkspaceBuild
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
|
||||||
|
const styles = useStyles()
|
||||||
|
const { t } = useTranslation("workspacePage")
|
||||||
|
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(build)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const clickableProps = useClickable(() =>
|
||||||
|
navigate(`builds/${build.build_number}`),
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
hover
|
||||||
|
data-testid={`build-${build.id}`}
|
||||||
|
className={styles.buildRow}
|
||||||
|
{...clickableProps}
|
||||||
|
>
|
||||||
|
<TableCell className={styles.buildCell}>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
className={styles.buildWrapper}
|
||||||
|
>
|
||||||
|
<Stack direction="row" alignItems="center">
|
||||||
|
<BuildAvatar build={build} />
|
||||||
|
<div>
|
||||||
|
<Stack
|
||||||
|
className={styles.buildResume}
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
spacing={1}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<strong>{initiatedBy}</strong>{" "}
|
||||||
|
{build.reason !== "initiator"
|
||||||
|
? t("buildMessage.automatically")
|
||||||
|
: ""}
|
||||||
|
<strong>{t(`buildMessage.${build.transition}`)}</strong>{" "}
|
||||||
|
{t("buildMessage.theWorkspace")}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={styles.buildTime}>
|
||||||
|
{new Date(build.created_at).toLocaleTimeString()}
|
||||||
|
</span>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack direction="row" spacing={1}>
|
||||||
|
<span className={styles.buildInfo}>
|
||||||
|
{t("buildData.reason")}: <strong>{build.reason}</strong>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className={styles.buildInfo}>
|
||||||
|
{t("buildData.duration")}:{" "}
|
||||||
|
<strong>{displayWorkspaceBuildDuration(build)}</strong>
|
||||||
|
</span>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
buildRow: {
|
||||||
|
cursor: "pointer",
|
||||||
|
|
||||||
|
"&:focus": {
|
||||||
|
outlineStyle: "solid",
|
||||||
|
outlineOffset: -1,
|
||||||
|
outlineWidth: 2,
|
||||||
|
outlineColor: theme.palette.secondary.dark,
|
||||||
|
},
|
||||||
|
|
||||||
|
"&:not(:last-child) td:before": {
|
||||||
|
position: "absolute",
|
||||||
|
top: 20,
|
||||||
|
left: 50,
|
||||||
|
display: "block",
|
||||||
|
content: "''",
|
||||||
|
height: "100%",
|
||||||
|
width: 2,
|
||||||
|
background: theme.palette.divider,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
buildWrapper: {
|
||||||
|
padding: theme.spacing(2, 4),
|
||||||
|
},
|
||||||
|
|
||||||
|
buildCell: {
|
||||||
|
padding: "0 !important",
|
||||||
|
position: "relative",
|
||||||
|
borderBottom: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
buildResume: {
|
||||||
|
...theme.typography.body1,
|
||||||
|
fontFamily: "inherit",
|
||||||
|
},
|
||||||
|
|
||||||
|
buildInfo: {
|
||||||
|
...theme.typography.body2,
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "inherit",
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
display: "block",
|
||||||
|
},
|
||||||
|
|
||||||
|
buildTime: {
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
|
||||||
|
buildRight: {
|
||||||
|
width: "auto",
|
||||||
|
},
|
||||||
|
|
||||||
|
buildExtraInfo: {
|
||||||
|
...theme.typography.body2,
|
||||||
|
fontFamily: MONOSPACE_FONT_FAMILY,
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
},
|
||||||
|
}))
|
@ -1,23 +1,15 @@
|
|||||||
import Box from "@material-ui/core/Box"
|
import Box from "@material-ui/core/Box"
|
||||||
import { makeStyles, Theme } from "@material-ui/core/styles"
|
|
||||||
import Table from "@material-ui/core/Table"
|
import Table from "@material-ui/core/Table"
|
||||||
import TableBody from "@material-ui/core/TableBody"
|
import TableBody from "@material-ui/core/TableBody"
|
||||||
import TableCell from "@material-ui/core/TableCell"
|
import TableCell from "@material-ui/core/TableCell"
|
||||||
import TableContainer from "@material-ui/core/TableContainer"
|
import TableContainer from "@material-ui/core/TableContainer"
|
||||||
import TableHead from "@material-ui/core/TableHead"
|
|
||||||
import TableRow from "@material-ui/core/TableRow"
|
import TableRow from "@material-ui/core/TableRow"
|
||||||
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
|
import { FC, Fragment } from "react"
|
||||||
import useTheme from "@material-ui/styles/useTheme"
|
|
||||||
import { FC } from "react"
|
|
||||||
import { useNavigate, useParams } from "react-router-dom"
|
|
||||||
import * as TypesGen from "../../api/typesGenerated"
|
import * as TypesGen from "../../api/typesGenerated"
|
||||||
import {
|
|
||||||
displayWorkspaceBuildDuration,
|
|
||||||
getDisplayWorkspaceBuildStatus,
|
|
||||||
} from "../../util/workspace"
|
|
||||||
import { EmptyState } from "../EmptyState/EmptyState"
|
import { EmptyState } from "../EmptyState/EmptyState"
|
||||||
import { TableCellLink } from "../TableCellLink/TableCellLink"
|
|
||||||
import { TableLoader } from "../TableLoader/TableLoader"
|
import { TableLoader } from "../TableLoader/TableLoader"
|
||||||
|
import { BuildDateRow } from "./BuildDateRow"
|
||||||
|
import { BuildRow } from "./BuildRow"
|
||||||
|
|
||||||
export const Language = {
|
export const Language = {
|
||||||
emptyMessage: "No builds found",
|
emptyMessage: "No builds found",
|
||||||
@ -33,75 +25,51 @@ export interface BuildsTableProps {
|
|||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupBuildsByDate = (builds?: TypesGen.WorkspaceBuild[]) => {
|
||||||
|
const buildsByDate: Record<string, TypesGen.WorkspaceBuild[]> = {}
|
||||||
|
|
||||||
|
if (!builds) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
builds.forEach((build) => {
|
||||||
|
const dateKey = new Date(build.created_at).toDateString()
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (buildsByDate[dateKey]) {
|
||||||
|
buildsByDate[dateKey].push(build)
|
||||||
|
} else {
|
||||||
|
buildsByDate[dateKey] = [build]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return buildsByDate
|
||||||
|
}
|
||||||
|
|
||||||
export const BuildsTable: FC<React.PropsWithChildren<BuildsTableProps>> = ({
|
export const BuildsTable: FC<React.PropsWithChildren<BuildsTableProps>> = ({
|
||||||
builds,
|
builds,
|
||||||
className,
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const { username, workspace: workspaceName } = useParams()
|
|
||||||
const isLoading = !builds
|
const isLoading = !builds
|
||||||
const theme: Theme = useTheme()
|
const buildsByDate = groupBuildsByDate(builds)
|
||||||
const navigate = useNavigate()
|
|
||||||
const styles = useStyles()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer className={className}>
|
<TableContainer className={className}>
|
||||||
<Table data-testid="builds-table" aria-describedby="builds table">
|
<Table data-testid="builds-table" aria-describedby="builds table">
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell width="20%">{Language.actionLabel}</TableCell>
|
|
||||||
<TableCell width="20%">{Language.durationLabel}</TableCell>
|
|
||||||
<TableCell width="40%">{Language.startedAtLabel}</TableCell>
|
|
||||||
<TableCell width="20%">{Language.statusLabel}</TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{isLoading && <TableLoader />}
|
{isLoading && <TableLoader />}
|
||||||
{builds &&
|
|
||||||
builds.map((build) => {
|
{buildsByDate &&
|
||||||
const status = getDisplayWorkspaceBuildStatus(theme, build)
|
Object.keys(buildsByDate).map((dateStr) => {
|
||||||
const buildPageLink = `/@${username}/${workspaceName}/builds/${build.build_number}`
|
const builds = buildsByDate[dateStr]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<Fragment key={dateStr}>
|
||||||
hover
|
<BuildDateRow date={new Date(dateStr)} />
|
||||||
key={build.id}
|
{builds.map((build) => (
|
||||||
data-testid={`build-${build.id}`}
|
<BuildRow key={build.id} build={build} />
|
||||||
tabIndex={0}
|
))}
|
||||||
onKeyDown={(event) => {
|
</Fragment>
|
||||||
if (event.key === "Enter") {
|
|
||||||
navigate(buildPageLink)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={styles.clickableTableRow}
|
|
||||||
>
|
|
||||||
<TableCellLink to={buildPageLink}>
|
|
||||||
{build.transition}
|
|
||||||
</TableCellLink>
|
|
||||||
<TableCellLink to={buildPageLink}>
|
|
||||||
<span style={{ color: theme.palette.text.secondary }}>
|
|
||||||
{displayWorkspaceBuildDuration(build)}
|
|
||||||
</span>
|
|
||||||
</TableCellLink>
|
|
||||||
<TableCellLink to={buildPageLink}>
|
|
||||||
<span style={{ color: theme.palette.text.secondary }}>
|
|
||||||
{new Date(build.created_at).toLocaleString()}
|
|
||||||
</span>
|
|
||||||
</TableCellLink>
|
|
||||||
<TableCellLink to={buildPageLink}>
|
|
||||||
<span
|
|
||||||
style={{ color: status.color }}
|
|
||||||
className={styles.status}
|
|
||||||
>
|
|
||||||
{status.status}
|
|
||||||
</span>
|
|
||||||
</TableCellLink>
|
|
||||||
<TableCellLink to={buildPageLink}>
|
|
||||||
<div className={styles.arrowCell}>
|
|
||||||
<KeyboardArrowRight className={styles.arrowRight} />
|
|
||||||
</div>
|
|
||||||
</TableCellLink>
|
|
||||||
</TableRow>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
@ -119,30 +87,3 @@ export const BuildsTable: FC<React.PropsWithChildren<BuildsTableProps>> = ({
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
clickableTableRow: {
|
|
||||||
"&:hover td": {
|
|
||||||
backgroundColor: theme.palette.action.hover,
|
|
||||||
},
|
|
||||||
|
|
||||||
"&:focus": {
|
|
||||||
outline: `1px solid ${theme.palette.secondary.dark}`,
|
|
||||||
},
|
|
||||||
|
|
||||||
"& .MuiTableCell-root:last-child": {
|
|
||||||
paddingRight: theme.spacing(2),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
arrowRight: {
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
},
|
|
||||||
arrowCell: {
|
|
||||||
display: "flex",
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
@ -93,11 +93,12 @@ export const Resources: FC<React.PropsWithChildren<ResourcesProps>> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
buttonWrapper: {
|
buttonWrapper: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
},
|
},
|
||||||
|
|
||||||
showMoreButton: {
|
showMoreButton: {
|
||||||
|
@ -179,7 +179,7 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
|||||||
<Stack
|
<Stack
|
||||||
direction="column"
|
direction="column"
|
||||||
className={styles.firstColumnSpacer}
|
className={styles.firstColumnSpacer}
|
||||||
spacing={2.5}
|
spacing={6}
|
||||||
>
|
>
|
||||||
{buildError}
|
{buildError}
|
||||||
{cancellationError}
|
{cancellationError}
|
||||||
@ -220,7 +220,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<WorkspaceSection
|
<WorkspaceSection
|
||||||
title="Logs"
|
|
||||||
contentsProps={{ className: styles.timelineContents }}
|
contentsProps={{ className: styles.timelineContents }}
|
||||||
>
|
>
|
||||||
{workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? (
|
{workspaceErrors[WorkspaceErrors.GET_BUILDS_ERROR] ? (
|
||||||
|
@ -38,5 +38,16 @@
|
|||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
"connecting": "Connecting...",
|
"connecting": "Connecting...",
|
||||||
"disconnected": "Disconnected"
|
"disconnected": "Disconnected"
|
||||||
|
},
|
||||||
|
"buildMessage": {
|
||||||
|
"start": "started",
|
||||||
|
"stop": "stopped",
|
||||||
|
"delete": "deleted",
|
||||||
|
"theWorkspace": "the workspace",
|
||||||
|
"automatically": "automatically "
|
||||||
|
},
|
||||||
|
"buildData": {
|
||||||
|
"reason": "Reason",
|
||||||
|
"duration": "Duration"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,16 +92,6 @@ afterAll(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("WorkspacePage", () => {
|
describe("WorkspacePage", () => {
|
||||||
it("shows a workspace", async () => {
|
|
||||||
await renderWorkspacePage()
|
|
||||||
const workspaceName = await screen.findByText(MockWorkspace.name)
|
|
||||||
expect(workspaceName).toBeDefined()
|
|
||||||
const header = screen.getByTestId("header")
|
|
||||||
const status = await within(header).findByRole("status")
|
|
||||||
expect(status).toHaveTextContent("Running")
|
|
||||||
// wait for workspace page to finish loading
|
|
||||||
await screen.findByText("stop")
|
|
||||||
})
|
|
||||||
it("requests a stop job when the user presses Stop", async () => {
|
it("requests a stop job when the user presses Stop", async () => {
|
||||||
const stopWorkspaceMock = jest
|
const stopWorkspaceMock = jest
|
||||||
.spyOn(api, "stopWorkspace")
|
.spyOn(api, "stopWorkspace")
|
||||||
@ -288,7 +278,8 @@ describe("WorkspacePage", () => {
|
|||||||
// Wait for the results to be loaded
|
// Wait for the results to be loaded
|
||||||
await waitFor(async () => {
|
await waitFor(async () => {
|
||||||
const rows = table.querySelectorAll("tbody > tr")
|
const rows = table.querySelectorAll("tbody > tr")
|
||||||
expect(rows).toHaveLength(MockBuilds.length)
|
// Added +1 because of the date row
|
||||||
|
expect(rows).toHaveLength(MockBuilds.length + 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -82,14 +82,14 @@ describe("util > workspace", () => {
|
|||||||
...Mocks.MockWorkspaceBuild,
|
...Mocks.MockWorkspaceBuild,
|
||||||
reason: "autostart",
|
reason: "autostart",
|
||||||
},
|
},
|
||||||
"system/autostart",
|
"Coder",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
...Mocks.MockWorkspaceBuild,
|
...Mocks.MockWorkspaceBuild,
|
||||||
reason: "autostop",
|
reason: "autostop",
|
||||||
},
|
},
|
||||||
"system/autostop",
|
"Coder",
|
||||||
],
|
],
|
||||||
])(
|
])(
|
||||||
`getDisplayWorkspaceBuildInitiatedBy(%p) returns %p`,
|
`getDisplayWorkspaceBuildInitiatedBy(%p) returns %p`,
|
||||||
|
@ -4,6 +4,7 @@ import duration from "dayjs/plugin/duration"
|
|||||||
import minMax from "dayjs/plugin/minMax"
|
import minMax from "dayjs/plugin/minMax"
|
||||||
import utc from "dayjs/plugin/utc"
|
import utc from "dayjs/plugin/utc"
|
||||||
import semver from "semver"
|
import semver from "semver"
|
||||||
|
import { PaletteIndex } from "theme/palettes"
|
||||||
import * as TypesGen from "../api/typesGenerated"
|
import * as TypesGen from "../api/typesGenerated"
|
||||||
|
|
||||||
dayjs.extend(duration)
|
dayjs.extend(duration)
|
||||||
@ -29,46 +30,48 @@ export const getDisplayWorkspaceBuildStatus = (
|
|||||||
): {
|
): {
|
||||||
color: string
|
color: string
|
||||||
status: string
|
status: string
|
||||||
|
type: PaletteIndex
|
||||||
} => {
|
} => {
|
||||||
switch (build.job.status) {
|
switch (build.job.status) {
|
||||||
case "succeeded":
|
case "succeeded":
|
||||||
return {
|
return {
|
||||||
|
type: "success",
|
||||||
color: theme.palette.success.main,
|
color: theme.palette.success.main,
|
||||||
status: `⦿ ${DisplayWorkspaceBuildStatusLanguage.succeeded}`,
|
status: DisplayWorkspaceBuildStatusLanguage.succeeded,
|
||||||
}
|
}
|
||||||
case "pending":
|
case "pending":
|
||||||
return {
|
return {
|
||||||
|
type: "secondary",
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
status: `⦿ ${DisplayWorkspaceBuildStatusLanguage.pending}`,
|
status: DisplayWorkspaceBuildStatusLanguage.pending,
|
||||||
}
|
}
|
||||||
case "running":
|
case "running":
|
||||||
return {
|
return {
|
||||||
|
type: "info",
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
status: `⦿ ${DisplayWorkspaceBuildStatusLanguage.running}`,
|
status: DisplayWorkspaceBuildStatusLanguage.running,
|
||||||
}
|
}
|
||||||
case "failed":
|
case "failed":
|
||||||
return {
|
return {
|
||||||
|
type: "error",
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
status: `⦸ ${DisplayWorkspaceBuildStatusLanguage.failed}`,
|
status: DisplayWorkspaceBuildStatusLanguage.failed,
|
||||||
}
|
}
|
||||||
case "canceling":
|
case "canceling":
|
||||||
return {
|
return {
|
||||||
|
type: "warning",
|
||||||
color: theme.palette.warning.light,
|
color: theme.palette.warning.light,
|
||||||
status: `◍ ${DisplayWorkspaceBuildStatusLanguage.canceling}`,
|
status: DisplayWorkspaceBuildStatusLanguage.canceling,
|
||||||
}
|
}
|
||||||
case "canceled":
|
case "canceled":
|
||||||
return {
|
return {
|
||||||
|
type: "secondary",
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
status: `◍ ${DisplayWorkspaceBuildStatusLanguage.canceled}`,
|
status: DisplayWorkspaceBuildStatusLanguage.canceled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DisplayWorkspaceBuildInitiatedByLanguage = {
|
|
||||||
autostart: "system/autostart",
|
|
||||||
autostop: "system/autostop",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDisplayWorkspaceBuildInitiatedBy = (
|
export const getDisplayWorkspaceBuildInitiatedBy = (
|
||||||
build: TypesGen.WorkspaceBuild,
|
build: TypesGen.WorkspaceBuild,
|
||||||
): string => {
|
): string => {
|
||||||
@ -76,9 +79,8 @@ export const getDisplayWorkspaceBuildInitiatedBy = (
|
|||||||
case "initiator":
|
case "initiator":
|
||||||
return build.initiator_name
|
return build.initiator_name
|
||||||
case "autostart":
|
case "autostart":
|
||||||
return DisplayWorkspaceBuildInitiatedByLanguage.autostart
|
|
||||||
case "autostop":
|
case "autostop":
|
||||||
return DisplayWorkspaceBuildInitiatedByLanguage.autostop
|
return "Coder"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user