import { makeStyles } from "@mui/styles" import dayjs from "dayjs" import { FC, Fragment } from "react" import { ProvisionerJobLog } from "../../api/typesGenerated" import { MONOSPACE_FONT_FAMILY } from "../../theme/constants" import { Logs } from "../Logs/Logs" import { Theme } from "@mui/material/styles" const Language = { seconds: "seconds", } type Stage = ProvisionerJobLog["stage"] type LogsGroupedByStage = Record type GroupLogsByStageFn = (logs: ProvisionerJobLog[]) => LogsGroupedByStage export const groupLogsByStage: GroupLogsByStageFn = (logs) => { const logsByStage: LogsGroupedByStage = {} for (const log of logs) { if (log.stage in logsByStage) { logsByStage[log.stage].push(log) } else { logsByStage[log.stage] = [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[] hideTimestamps?: boolean // If true, render different styles that fit the template editor pane // a bit better. templateEditorPane?: boolean } export const WorkspaceBuildLogs: FC = ({ hideTimestamps, logs, templateEditorPane, }) => { const groupedLogsByStage = groupLogsByStage(logs) const stages = Object.keys(groupedLogsByStage) const styles = useStyles({ templateEditorPane: Boolean(templateEditorPane) }) return (
{stages.map((stage) => { const logs = groupedLogsByStage[stage] const isEmpty = logs.every((log) => log.output === "") const lines = logs.map((log) => ({ time: log.created_at, output: log.output, level: log.log_level, })) const duration = getStageDurationInSeconds(logs) const shouldDisplayDuration = duration !== undefined return (
{stage}
{shouldDisplayDuration && (
{duration} {Language.seconds}
)}
{!isEmpty && }
) })}
) } const useStyles = makeStyles< Theme, { templateEditorPane: boolean } >((theme) => ({ logs: { border: `1px solid ${theme.palette.divider}`, borderRadius: (props) => props.templateEditorPane ? "0px" : theme.shape.borderRadius, fontFamily: MONOSPACE_FONT_FAMILY, }, header: { fontSize: 14, padding: theme.spacing(2), paddingLeft: theme.spacing(3), paddingRight: theme.spacing(3), borderTop: `1px solid ${theme.palette.divider}`, borderBottom: `1px solid ${theme.palette.divider}`, backgroundColor: theme.palette.background.paper, display: "flex", alignItems: "center", fontFamily: "Inter", "&:first-of-type": { borderTopLeftRadius: theme.shape.borderRadius, borderTopRightRadius: theme.shape.borderRadius, borderTop: 0, }, "&:last-child": { borderBottom: 0, borderTop: `1px solid ${theme.palette.divider}`, borderBottomLeftRadius: theme.shape.borderRadius, borderBottomRightRadius: theme.shape.borderRadius, }, "& + $header": { borderTop: 0, }, }, duration: { marginLeft: "auto", color: theme.palette.text.secondary, fontSize: theme.typography.body2.fontSize, }, }))