mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
refactor: Show template version in the workspace page (#5194)
This commit is contained in:
@ -34,9 +34,18 @@ export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
|
||||
alignItems="center"
|
||||
className={styles.buildWrapper}
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
className={styles.fullWidth}
|
||||
>
|
||||
<BuildAvatar build={build} />
|
||||
<div>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
className={styles.fullWidth}
|
||||
>
|
||||
<Stack
|
||||
className={styles.buildSummary}
|
||||
direction="row"
|
||||
@ -66,8 +75,13 @@ export const BuildRow: React.FC<BuildRowProps> = ({ build }) => {
|
||||
{t("buildData.duration")}:{" "}
|
||||
<strong>{displayWorkspaceBuildDuration(build)}</strong>
|
||||
</span>
|
||||
|
||||
<span className={styles.buildInfo}>
|
||||
{t("buildData.version")}:{" "}
|
||||
<strong>{build.template_version_name}</strong>
|
||||
</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</TableCell>
|
||||
@ -114,4 +128,8 @@ const useStyles = makeStyles((theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
|
||||
fullWidth: {
|
||||
width: "100%",
|
||||
},
|
||||
}))
|
||||
|
@ -30,17 +30,24 @@ const useStyles = makeStyles((theme) => ({
|
||||
alignItems: "center",
|
||||
color: theme.palette.text.secondary,
|
||||
margin: "0px",
|
||||
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "block",
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
|
||||
statItem: {
|
||||
padding: theme.spacing(2),
|
||||
paddingTop: theme.spacing(1.75),
|
||||
padding: theme.spacing(1.75),
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
gap: theme.spacing(1),
|
||||
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
},
|
||||
|
||||
statsLabel: {
|
||||
@ -50,9 +57,10 @@ const useStyles = makeStyles((theme) => ({
|
||||
|
||||
statsValue: {
|
||||
marginTop: theme.spacing(0.25),
|
||||
display: "block",
|
||||
display: "flex",
|
||||
wordWrap: "break-word",
|
||||
color: theme.palette.text.primary,
|
||||
alignItems: "center",
|
||||
|
||||
"& a": {
|
||||
color: theme.palette.text.primary,
|
||||
|
@ -4,6 +4,7 @@ import { makeStyles } from "@material-ui/core/styles"
|
||||
import HelpIcon from "@material-ui/icons/HelpOutline"
|
||||
import OpenInNewIcon from "@material-ui/icons/OpenInNew"
|
||||
import React, { createContext, useContext, useRef, useState } from "react"
|
||||
import { combineClasses } from "util/combineClasses"
|
||||
import { Stack } from "../../Stack/Stack"
|
||||
|
||||
type Icon = typeof HelpIcon
|
||||
@ -13,6 +14,9 @@ export interface HelpTooltipProps {
|
||||
// Useful to test on storybook
|
||||
open?: boolean
|
||||
size?: Size
|
||||
icon?: Icon
|
||||
iconClassName?: string
|
||||
buttonClassName?: string
|
||||
}
|
||||
|
||||
export const Language = {
|
||||
@ -66,7 +70,14 @@ export const HelpPopover: React.FC<
|
||||
|
||||
export const HelpTooltip: React.FC<
|
||||
React.PropsWithChildren<HelpTooltipProps>
|
||||
> = ({ children, open, size = "medium" }) => {
|
||||
> = ({
|
||||
children,
|
||||
open,
|
||||
size = "medium",
|
||||
icon: Icon = HelpIcon,
|
||||
iconClassName,
|
||||
buttonClassName,
|
||||
}) => {
|
||||
const styles = useStyles({ size })
|
||||
const anchorRef = useRef<HTMLButtonElement>(null)
|
||||
const [isOpen, setIsOpen] = useState(Boolean(open))
|
||||
@ -81,7 +92,7 @@ export const HelpTooltip: React.FC<
|
||||
<button
|
||||
ref={anchorRef}
|
||||
aria-describedby={id}
|
||||
className={styles.button}
|
||||
className={combineClasses([styles.button, buttonClassName])}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
setIsOpen(true)
|
||||
@ -94,7 +105,7 @@ export const HelpTooltip: React.FC<
|
||||
}}
|
||||
aria-label={Language.ariaLabel}
|
||||
>
|
||||
<HelpIcon className={styles.icon} />
|
||||
<Icon className={combineClasses([styles.icon, iconClassName])} />
|
||||
</button>
|
||||
<HelpPopover
|
||||
id={id}
|
||||
|
@ -7,6 +7,9 @@ import {
|
||||
HelpTooltipText,
|
||||
HelpTooltipTitle,
|
||||
} from "./HelpTooltip"
|
||||
import InfoIcon from "@material-ui/icons/InfoOutlined"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { colors } from "theme/colors"
|
||||
|
||||
export const Language = {
|
||||
outdatedLabel: "Outdated",
|
||||
@ -24,8 +27,15 @@ export const OutdatedHelpTooltip: FC<React.PropsWithChildren<TooltipProps>> = ({
|
||||
onUpdateVersion,
|
||||
ariaLabel,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<HelpTooltip size="small">
|
||||
<HelpTooltip
|
||||
size="small"
|
||||
icon={InfoIcon}
|
||||
iconClassName={styles.icon}
|
||||
buttonClassName={styles.button}
|
||||
>
|
||||
<HelpTooltipTitle>{Language.outdatedLabel}</HelpTooltipTitle>
|
||||
<HelpTooltipText>{Language.versionTooltipText}</HelpTooltipText>
|
||||
<HelpTooltipLinksGroup>
|
||||
@ -40,3 +50,17 @@ export const OutdatedHelpTooltip: FC<React.PropsWithChildren<TooltipProps>> = ({
|
||||
</HelpTooltip>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
icon: {
|
||||
color: colors.yellow[5],
|
||||
},
|
||||
|
||||
button: {
|
||||
opacity: 1,
|
||||
|
||||
"&:hover": {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
@ -1,15 +1,14 @@
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { OutdatedHelpTooltip } from "components/Tooltips"
|
||||
import { FC } from "react"
|
||||
import { Link as RouterLink } from "react-router-dom"
|
||||
import { combineClasses } from "util/combineClasses"
|
||||
import { createDayString } from "util/createDayString"
|
||||
import {
|
||||
getDisplayWorkspaceBuildInitiatedBy,
|
||||
getDisplayWorkspaceTemplateName,
|
||||
} from "util/workspace"
|
||||
import { Workspace } from "../../api/typesGenerated"
|
||||
import { Stats, StatsItem } from "components/Stats/Stats"
|
||||
|
||||
const Language = {
|
||||
workspaceDetails: "Workspace Details",
|
||||
@ -34,110 +33,57 @@ export const WorkspaceStats: FC<WorkspaceStatsProps> = ({
|
||||
quota_budget,
|
||||
handleUpdate,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy(
|
||||
workspace.latest_build,
|
||||
)
|
||||
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace)
|
||||
|
||||
return (
|
||||
<div className={styles.stats} aria-label={Language.workspaceDetails}>
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>{Language.templateLabel}:</span>
|
||||
<Stats aria-label={Language.workspaceDetails}>
|
||||
<StatsItem
|
||||
label={Language.templateLabel}
|
||||
value={
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/templates/${workspace.template_name}`}
|
||||
className={combineClasses([styles.statsValue, styles.link])}
|
||||
>
|
||||
{displayTemplateName}
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>{Language.versionLabel}:</span>
|
||||
<span className={styles.statsValue}>
|
||||
{workspace.outdated ? (
|
||||
<span className={styles.outdatedLabel}>
|
||||
{Language.outdated}
|
||||
}
|
||||
/>
|
||||
<StatsItem
|
||||
label={Language.versionLabel}
|
||||
value={
|
||||
<>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/templates/${workspace.template_name}/versions/${workspace.latest_build.template_version_name}`}
|
||||
>
|
||||
{workspace.latest_build.template_version_name}
|
||||
</Link>
|
||||
|
||||
{workspace.outdated && (
|
||||
<OutdatedHelpTooltip
|
||||
onUpdateVersion={handleUpdate}
|
||||
ariaLabel="update version"
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
Language.upToDate
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>{Language.lastBuiltLabel}:</span>
|
||||
<span className={styles.statsValue} data-chromatic="ignore">
|
||||
{createDayString(workspace.latest_build.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>{Language.byLabel}:</span>
|
||||
<span className={styles.statsValue}>{initiatedBy}</span>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<StatsItem
|
||||
label={Language.lastBuiltLabel}
|
||||
value={createDayString(workspace.latest_build.created_at)}
|
||||
/>
|
||||
<StatsItem label={Language.byLabel} value={initiatedBy} />
|
||||
{workspace.latest_build.daily_cost > 0 && (
|
||||
<div className={styles.statItem}>
|
||||
<span className={styles.statsLabel}>{Language.costLabel}:</span>
|
||||
<span className={styles.statsValue}>
|
||||
{workspace.latest_build.daily_cost} / {quota_budget}
|
||||
</span>
|
||||
</div>
|
||||
<StatsItem
|
||||
label={Language.costLabel}
|
||||
value={`${workspace.latest_build.daily_cost} ${
|
||||
quota_budget ? `/ ${quota_budget}` : ""
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Stats>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
stats: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: theme.palette.text.secondary,
|
||||
margin: "0px",
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
display: "block",
|
||||
},
|
||||
},
|
||||
|
||||
statItem: {
|
||||
padding: theme.spacing(2),
|
||||
paddingTop: theme.spacing(1.75),
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
gap: theme.spacing(1),
|
||||
},
|
||||
|
||||
statsLabel: {
|
||||
display: "block",
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
|
||||
statsValue: {
|
||||
marginTop: theme.spacing(0.25),
|
||||
display: "block",
|
||||
wordWrap: "break-word",
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
|
||||
capitalize: {
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
|
||||
link: {
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 600,
|
||||
},
|
||||
|
||||
outdatedLabel: {
|
||||
color: theme.palette.error.main,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(0.5),
|
||||
},
|
||||
}))
|
||||
|
@ -1,53 +1,41 @@
|
||||
import { makeStyles, Theme } from "@material-ui/core/styles"
|
||||
import TableCell from "@material-ui/core/TableCell"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
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 { AvatarData } from "components/AvatarData/AvatarData"
|
||||
import { WorkspaceStatusBadge } from "components/WorkspaceStatusBadge/WorkspaceStatusBadge"
|
||||
import { useClickable } from "hooks/useClickable"
|
||||
import { FC } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { getDisplayWorkspaceTemplateName } from "util/workspace"
|
||||
import { WorkspaceItemMachineRef } from "../../xServices/workspaces/workspacesXService"
|
||||
import { LastUsed } from "../LastUsed/LastUsed"
|
||||
import {
|
||||
TableCellData,
|
||||
TableCellDataPrimary,
|
||||
} from "../TableCellData/TableCellData"
|
||||
import { TableCellLink } from "../TableCellLink/TableCellLink"
|
||||
import { OutdatedHelpTooltip } from "../Tooltips"
|
||||
|
||||
const Language = {
|
||||
upToDateLabel: "Up to date",
|
||||
outdatedLabel: "Outdated",
|
||||
}
|
||||
|
||||
export const WorkspacesRow: FC<
|
||||
React.PropsWithChildren<{ workspaceRef: WorkspaceItemMachineRef }>
|
||||
> = ({ workspaceRef }) => {
|
||||
export const WorkspacesRow: FC<{ workspaceRef: WorkspaceItemMachineRef }> = ({
|
||||
workspaceRef,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const navigate = useNavigate()
|
||||
const theme: Theme = useTheme()
|
||||
const [workspaceState, send] = useActor(workspaceRef)
|
||||
const { data: workspace } = workspaceState.context
|
||||
const workspacePageLink = `/@${workspace.owner_name}/${workspace.name}`
|
||||
const hasTemplateIcon =
|
||||
workspace.template_icon && workspace.template_icon !== ""
|
||||
const displayTemplateName = getDisplayWorkspaceTemplateName(workspace)
|
||||
const clickable = useClickable(() => {
|
||||
navigate(workspacePageLink)
|
||||
})
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
className={styles.row}
|
||||
hover
|
||||
data-testid={`workspace-${workspace.id}`}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
navigate(workspacePageLink)
|
||||
}
|
||||
}}
|
||||
className={styles.clickableTableRow}
|
||||
{...clickable}
|
||||
>
|
||||
<TableCellLink to={workspacePageLink}>
|
||||
<TableCell>
|
||||
<AvatarData
|
||||
highlightTitle
|
||||
title={workspace.name}
|
||||
@ -60,78 +48,61 @@ export const WorkspacesRow: FC<
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</TableCellLink>
|
||||
</TableCell>
|
||||
|
||||
<TableCellLink to={workspacePageLink}>
|
||||
<TableCellDataPrimary>{displayTemplateName}</TableCellDataPrimary>
|
||||
</TableCellLink>
|
||||
<TableCellLink to={workspacePageLink}>
|
||||
<TableCellData>
|
||||
<LastUsed lastUsedAt={workspace.last_used_at} />
|
||||
</TableCellData>
|
||||
</TableCellLink>
|
||||
<TableCell>{displayTemplateName}</TableCell>
|
||||
|
||||
<TableCellLink to={workspacePageLink}>
|
||||
{workspace.outdated ? (
|
||||
<span className={styles.outdatedLabel}>
|
||||
{Language.outdatedLabel}
|
||||
<TableCell>
|
||||
<div className={styles.version}>
|
||||
{workspace.latest_build.template_version_name}
|
||||
{workspace.outdated && (
|
||||
<OutdatedHelpTooltip
|
||||
onUpdateVersion={() => {
|
||||
send("UPDATE_VERSION")
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{Language.upToDateLabel}
|
||||
</span>
|
||||
)}
|
||||
</TableCellLink>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCellLink to={workspacePageLink}>
|
||||
<TableCell>
|
||||
<LastUsed lastUsedAt={workspace.last_used_at} />
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<WorkspaceStatusBadge build={workspace.latest_build} />
|
||||
</TableCellLink>
|
||||
<TableCellLink to={workspacePageLink}>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<div className={styles.arrowCell}>
|
||||
<KeyboardArrowRight className={styles.arrowRight} />
|
||||
</div>
|
||||
</TableCellLink>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
clickableTableRow: {
|
||||
"&:hover td": {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
row: {
|
||||
cursor: "pointer",
|
||||
|
||||
"&:focus": {
|
||||
outline: `1px solid ${theme.palette.secondary.dark}`,
|
||||
outlineOffset: -1,
|
||||
},
|
||||
},
|
||||
|
||||
"& .MuiTableCell-root:last-child": {
|
||||
paddingRight: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
arrowRight: {
|
||||
color: theme.palette.text.secondary,
|
||||
width: 20,
|
||||
height: 20,
|
||||
},
|
||||
|
||||
arrowCell: {
|
||||
display: "flex",
|
||||
paddingLeft: theme.spacing(2),
|
||||
},
|
||||
outdatedLabel: {
|
||||
color: theme.palette.error.main,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(0.5),
|
||||
},
|
||||
buildTime: {
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 12,
|
||||
},
|
||||
|
||||
templateIconWrapper: {
|
||||
// Same size then the avatar component
|
||||
width: 36,
|
||||
@ -142,4 +113,8 @@ const useStyles = makeStyles((theme) => ({
|
||||
width: "100%",
|
||||
},
|
||||
},
|
||||
|
||||
version: {
|
||||
display: "flex",
|
||||
},
|
||||
}))
|
||||
|
@ -32,10 +32,10 @@ export const WorkspacesTable: FC<
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="25%">{Language.name}</TableCell>
|
||||
<TableCell width="35%">{Language.template}</TableCell>
|
||||
<TableCell width="30%">{Language.name}</TableCell>
|
||||
<TableCell width="25%">{Language.template}</TableCell>
|
||||
<TableCell width="25%">{Language.version}</TableCell>
|
||||
<TableCell width="20%">{Language.lastUsed}</TableCell>
|
||||
<TableCell width="20%">{Language.version}</TableCell>
|
||||
<TableCell width="20%">{Language.status}</TableCell>
|
||||
<TableCell width="1%"></TableCell>
|
||||
</TableRow>
|
||||
|
@ -50,6 +50,7 @@
|
||||
},
|
||||
"buildData": {
|
||||
"reason": "Reason",
|
||||
"duration": "Duration"
|
||||
"duration": "Duration",
|
||||
"version": "Version"
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user