mirror of https://github.com/coder/coder.git
117 lines
3.3 KiB
TypeScript
117 lines
3.3 KiB
TypeScript
import CircularProgress from "@material-ui/core/CircularProgress"
|
|
import { makeStyles } from "@material-ui/core/styles"
|
|
import dayjs from "dayjs"
|
|
import { FC } from "react"
|
|
import { ProvisionerJobLog } from "../../api/typesGenerated"
|
|
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
|
|
import { Logs } from "../Logs/Logs"
|
|
|
|
const Language = {
|
|
seconds: "seconds",
|
|
}
|
|
|
|
type Stage = ProvisionerJobLog["stage"]
|
|
|
|
const groupLogsByStage = (logs: ProvisionerJobLog[]) => {
|
|
const logsByStage: Record<Stage, ProvisionerJobLog[]> = {}
|
|
|
|
for (const log of logs) {
|
|
// If there is no log in the stage record, add an empty array
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
if (logsByStage[log.stage] === undefined) {
|
|
logsByStage[log.stage] = []
|
|
}
|
|
|
|
logsByStage[log.stage].push(log)
|
|
}
|
|
|
|
return logsByStage
|
|
}
|
|
|
|
const getStageDurationInSeconds = (logs: ProvisionerJobLog[]) => {
|
|
if (logs.length < 2) {
|
|
return
|
|
}
|
|
|
|
const startedAt = dayjs(logs[0].created_at)
|
|
const completedAt = dayjs(logs[logs.length - 1].created_at)
|
|
return completedAt.diff(startedAt, "seconds")
|
|
}
|
|
|
|
export interface WorkspaceBuildLogsProps {
|
|
logs: ProvisionerJobLog[]
|
|
isWaitingForLogs: boolean
|
|
}
|
|
|
|
export const WorkspaceBuildLogs: FC<WorkspaceBuildLogsProps> = ({ logs, isWaitingForLogs }) => {
|
|
const groupedLogsByStage = groupLogsByStage(logs)
|
|
const stages = Object.keys(groupedLogsByStage)
|
|
const styles = useStyles()
|
|
|
|
return (
|
|
<div className={styles.logs}>
|
|
{stages.map((stage, stageIndex) => {
|
|
const logs = groupedLogsByStage[stage]
|
|
const isEmpty = logs.every((log) => log.output === "")
|
|
const lines = logs.map((log) => ({
|
|
time: log.created_at,
|
|
output: log.output,
|
|
}))
|
|
const duration = getStageDurationInSeconds(logs)
|
|
const isLastStage = stageIndex === stages.length - 1
|
|
const shouldDisplaySpinner = isWaitingForLogs && isLastStage
|
|
const shouldDisplayDuration = !isWaitingForLogs && duration
|
|
|
|
return (
|
|
<div key={stage}>
|
|
<div className={styles.header}>
|
|
<div>{stage}</div>
|
|
{shouldDisplaySpinner && <CircularProgress size={14} className={styles.spinner} />}
|
|
{shouldDisplayDuration && (
|
|
<div className={styles.duration}>
|
|
{duration} {Language.seconds}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{!isEmpty && <Logs lines={lines} className={styles.codeBlock} />}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const useStyles = makeStyles((theme) => ({
|
|
logs: {
|
|
border: `1px solid ${theme.palette.divider}`,
|
|
borderRadius: 2,
|
|
fontFamily: MONOSPACE_FONT_FAMILY,
|
|
},
|
|
|
|
header: {
|
|
fontSize: theme.typography.body1.fontSize,
|
|
padding: theme.spacing(2),
|
|
paddingLeft: theme.spacing(4),
|
|
paddingRight: theme.spacing(4),
|
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
backgroundColor: theme.palette.background.paper,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
},
|
|
|
|
duration: {
|
|
marginLeft: "auto",
|
|
color: theme.palette.text.secondary,
|
|
fontSize: theme.typography.body2.fontSize,
|
|
},
|
|
|
|
codeBlock: {
|
|
padding: theme.spacing(2),
|
|
paddingLeft: theme.spacing(4),
|
|
},
|
|
|
|
spinner: {
|
|
marginLeft: "auto",
|
|
},
|
|
}))
|