Skip to content

Commit

Permalink
Lookout: Support state rejected and job errors (#3706)
Browse files Browse the repository at this point in the history
* Support state rejected and job errors

Signed-off-by: Robert Smith <[email protected]>

* fix test

Signed-off-by: Robert Smith <[email protected]>

---------

Signed-off-by: Robert Smith <[email protected]>
Signed-off-by: Chris Martin <[email protected]>
Co-authored-by: Chris Martin <[email protected]>
  • Loading branch information
robertdavidsmith and d80tb7 authored Jun 24, 2024
1 parent 3a9207f commit efa679c
Show file tree
Hide file tree
Showing 19 changed files with 159 additions and 65 deletions.
4 changes: 2 additions & 2 deletions internal/lookout/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import NavBar from "./components/NavBar"
import JobSetsContainer from "./containers/JobSetsContainer"
import { UserManagerContext, useUserManager } from "./oidc"
import { ICordonService } from "./services/lookoutV2/CordonService"
import { IGetJobSpecService } from "./services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "./services/lookoutV2/GetJobInfoService"
import { IGetRunInfoService } from "./services/lookoutV2/GetRunInfoService"
import { ILogService } from "./services/lookoutV2/LogService"
import { CommandSpec } from "./utils"
Expand Down Expand Up @@ -69,7 +69,7 @@ type AppProps = {
v2GetJobsService: IGetJobsService
v2GroupJobsService: IGroupJobsService
v2RunInfoService: IGetRunInfoService
v2JobSpecService: IGetJobSpecService
v2JobSpecService: IGetJobInfoService
v2LogService: ILogService
v2UpdateJobsService: UpdateJobsService
v2UpdateJobSetsService: UpdateJobSetsService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { KeyValuePairTable } from "./KeyValuePairTable"
import { useCustomSnackbar } from "../../../hooks/useCustomSnackbar"
import { useJobSpec } from "../../../hooks/useJobSpec"
import { Job } from "../../../models/lookoutV2Models"
import { IGetJobSpecService } from "../../../services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "../../../services/lookoutV2/GetJobInfoService"

export interface ContainerData {
name: string
Expand All @@ -19,7 +19,7 @@ export interface ContainerData {

interface ContainerDetailsProps {
job: Job
jobSpecService: IGetJobSpecService
jobSpecService: IGetJobInfoService
}

const getContainerData = (container: any): ContainerData => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { makeTestJob } from "utils/fakeJobsUtils"

import { Sidebar } from "./Sidebar"
import { FakeCordonService } from "../../../services/lookoutV2/mocks/FakeCordonService"
import FakeGetJobSpecService from "../../../services/lookoutV2/mocks/FakeGetJobSpecService"
import FakeGetJobInfoService from "../../../services/lookoutV2/mocks/FakeGetJobInfoService"
import { FakeGetRunInfoService } from "../../../services/lookoutV2/mocks/FakeGetRunInfoService"
import { FakeLogService } from "../../../services/lookoutV2/mocks/FakeLogService"

Expand Down Expand Up @@ -44,7 +44,7 @@ describe("Sidebar", () => {
<Sidebar
job={job}
runInfoService={new FakeGetRunInfoService()}
jobSpecService={new FakeGetJobSpecService()}
jobSpecService={new FakeGetJobInfoService()}
logService={new FakeLogService()}
cordonService={new FakeCordonService()}
sidebarWidth={600}
Expand All @@ -71,7 +71,7 @@ describe("Sidebar", () => {
const run = job.runs[0]

// Switch to runs tab
await userEvent.click(getByRole("tab", { name: /Runs/ }))
await userEvent.click(getByRole("tab", { name: /Result/ }))

// First run should already be expanded
within(getByRole("row", { name: /Run ID/ })).getByText(run.runId)
Expand All @@ -84,7 +84,7 @@ describe("Sidebar", () => {
run.exitCode = 137

// Switch to runs tab
await userEvent.click(getByRole("tab", { name: /Runs/ }))
await userEvent.click(getByRole("tab", { name: /Result/ }))

// First run should already be expanded
within(getByRole("row", { name: /Run ID/ })).getByText(run.runId)
Expand All @@ -96,7 +96,7 @@ describe("Sidebar", () => {
const { getByRole, getByText } = renderComponent()

// Switch to runs tab
await userEvent.click(getByRole("tab", { name: /Runs/ }))
await userEvent.click(getByRole("tab", { name: /Result/ }))

getByText("This job has not run.")
})
Expand Down
19 changes: 12 additions & 7 deletions internal/lookout/ui/src/components/lookoutV2/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ import { SidebarHeader } from "./SidebarHeader"
import { SidebarTabJobCommands } from "./SidebarTabJobCommands"
import { SidebarTabJobDetails } from "./SidebarTabJobDetails"
import { SidebarTabJobLogs } from "./SidebarTabJobLogs"
import { SidebarTabJobRuns } from "./SidebarTabJobRuns"
import { SidebarTabJobResult } from "./SidebarTabJobResult"
import { SidebarTabJobYaml } from "./SidebarTabJobYaml"
import { ICordonService } from "../../../services/lookoutV2/CordonService"
import { IGetJobSpecService } from "../../../services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "../../../services/lookoutV2/GetJobInfoService"
import { IGetRunInfoService } from "../../../services/lookoutV2/GetRunInfoService"
import { ILogService } from "../../../services/lookoutV2/LogService"
import { CommandSpec } from "../../../utils"

enum SidebarTab {
JobDetails = "JobDetails",
JobRuns = "JobRuns",
JobResult = "JobResult",
Yaml = "Yaml",
Logs = "Logs",
Commands = "Commands",
Expand All @@ -34,7 +34,7 @@ type ResizeState = {
export interface SidebarProps {
job: Job
runInfoService: IGetRunInfoService
jobSpecService: IGetJobSpecService
jobSpecService: IGetJobInfoService
logService: ILogService
cordonService: ICordonService
sidebarWidth: number
Expand Down Expand Up @@ -165,7 +165,7 @@ export const Sidebar = memo(
<TabContext value={openTab}>
<Tabs value={openTab} onChange={handleTabChange} className={styles.sidebarTabs}>
<Tab label="Details" value={SidebarTab.JobDetails} sx={{ minWidth: "50px" }}></Tab>
<Tab label="Runs" value={SidebarTab.JobRuns} sx={{ minWidth: "50px" }}></Tab>
<Tab label="Result" value={SidebarTab.JobResult} sx={{ minWidth: "50px" }}></Tab>
<Tab label="Yaml" value={SidebarTab.Yaml} sx={{ minWidth: "50px" }}></Tab>
<Tab
label="Logs"
Expand All @@ -185,8 +185,13 @@ export const Sidebar = memo(
<SidebarTabJobDetails job={job} jobSpecService={jobSpecService} />
</TabPanel>

<TabPanel value={SidebarTab.JobRuns} className={styles.sidebarTabPanel}>
<SidebarTabJobRuns job={job} runInfoService={runInfoService} cordonService={cordonService} />
<TabPanel value={SidebarTab.JobResult} className={styles.sidebarTabPanel}>
<SidebarTabJobResult
job={job}
jobInfoService={jobSpecService}
runInfoService={runInfoService}
cordonService={cordonService}
/>
</TabPanel>

<TabPanel value={SidebarTab.Yaml} className={styles.sidebarTabPanel}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { Job } from "models/lookoutV2Models"

import { ContainerDetails } from "./ContainerDetails"
import { KeyValuePairTable } from "./KeyValuePairTable"
import { IGetJobSpecService } from "../../../services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "../../../services/lookoutV2/GetJobInfoService"
import { formatBytes, formatCpu } from "../../../utils/resourceUtils"

export interface SidebarTabJobDetailsProps {
job: Job
jobSpecService: IGetJobSpecService
jobSpecService: IGetJobInfoService
}

export const SidebarTabJobDetails = ({ job, jobSpecService }: SidebarTabJobDetailsProps) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import styles from "./SidebarTabJobLogs.module.css"
import { useCustomSnackbar } from "../../../hooks/useCustomSnackbar"
import { useJobSpec } from "../../../hooks/useJobSpec"
import { getAccessToken, useUserManager } from "../../../oidc"
import { IGetJobSpecService } from "../../../services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "../../../services/lookoutV2/GetJobInfoService"
import { ILogService, LogLine } from "../../../services/lookoutV2/LogService"
import { getErrorMessage, RequestStatus } from "../../../utils"

export interface SidebarTabJobLogsProps {
job: Job
jobSpecService: IGetJobSpecService
jobSpecService: IGetJobInfoService
logService: ILogService
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,49 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { ExpandMore } from "@mui/icons-material"
import {
Accordion,
AccordionSummary,
Typography,
AccordionDetails,
AccordionSummary,
Button,
CircularProgress,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
DialogContent,
DialogTitle,
Tooltip,
Typography,
} from "@mui/material"
import { Button, Tooltip } from "@mui/material"
import { Job, JobRun } from "models/lookoutV2Models"
import { Job, JobRun, JobState } from "models/lookoutV2Models"
import { formatJobRunState, formatTimeSince, formatUtcDate } from "utils/jobsTableFormatters"

import { CodeBlock } from "./CodeBlock"
import { KeyValuePairTable } from "./KeyValuePairTable"
import styles from "./SidebarTabJobRuns.module.css"
import styles from "./SidebarTabJobResult.module.css"
import { useCustomSnackbar } from "../../../hooks/useCustomSnackbar"
import { getAccessToken, useUserManager } from "../../../oidc"
import { ICordonService } from "../../../services/lookoutV2/CordonService"
import { IGetJobInfoService } from "../../../services/lookoutV2/GetJobInfoService"
import { IGetRunInfoService } from "../../../services/lookoutV2/GetRunInfoService"
import { getErrorMessage } from "../../../utils"

export interface SidebarTabJobRunsProps {
export interface SidebarTabJobResultProps {
job: Job
runInfoService: IGetRunInfoService
jobInfoService: IGetJobInfoService
cordonService: ICordonService
}

type LoadState = "Idle" | "Loading"

export const SidebarTabJobRuns = ({ job, runInfoService, cordonService }: SidebarTabJobRunsProps) => {
export const SidebarTabJobResult = ({
job,
jobInfoService,
runInfoService,
cordonService,
}: SidebarTabJobResultProps) => {
const mounted = useRef(false)
const openSnackbar = useCustomSnackbar()
const runsNewestFirst = useMemo(() => [...job.runs].reverse(), [job])
const [jobError, setJobError] = useState<string>("")
const [runErrorMap, setRunErrorMap] = useState<Map<string, string>>(new Map<string, string>())
const [runErrorLoadingMap, setRunErrorLoadingMap] = useState<Map<string, LoadState>>(new Map<string, LoadState>())
const [runDebugMessageMap, setRunDebugMessageMap] = useState<Map<string, string>>(new Map<string, string>())
Expand All @@ -45,6 +54,29 @@ export const SidebarTabJobRuns = ({ job, runInfoService, cordonService }: Sideba
)
const [open, setOpen] = useState(false)

const fetchJobError = useCallback(async () => {
if (job.state != JobState.Failed && job.state != JobState.Rejected) {
setJobError("")
return
}
const getJobErrorResultPromise = jobInfoService.getJobError(job.jobId)
getJobErrorResultPromise
.then((errorString) => {
if (!mounted.current) {
return
}
setJobError(errorString)
})
.catch(async (e) => {
const errMsg = await getErrorMessage(e)
console.error(errMsg)
if (!mounted.current) {
return
}
openSnackbar("Failed to retrieve Job error for Job with ID: " + job.jobId + ": " + errMsg, "error")
})
}, [job])

const fetchRunErrors = useCallback(async () => {
const newRunErrorLoadingMap = new Map<string, LoadState>()
for (const run of job.runs) {
Expand Down Expand Up @@ -134,9 +166,25 @@ export const SidebarTabJobRuns = ({ job, runInfoService, cordonService }: Sideba
}
}, [job])

let topLevelError = ""
let topLevelErrorTitle = ""
if (jobError != "") {
topLevelError = jobError
topLevelErrorTitle = "Job Error"
} else {
for (const run of job.runs) {
const runErr = runErrorMap.get(run.runId) ?? ""
if (runErr != "") {
topLevelError = runErr
topLevelErrorTitle = "Last Job Run Error"
}
}
}

useEffect(() => {
mounted.current = true
fetchRunErrors()
fetchJobError()
fetchRunDebugMessages()
return () => {
mounted.current = false
Expand Down Expand Up @@ -167,9 +215,15 @@ export const SidebarTabJobRuns = ({ job, runInfoService, cordonService }: Sideba
openSnackbar("Failed to cordon node " + node + ": " + errMsg, "error")
}
}

return (
<div style={{ width: "100%", height: "100%" }}>
{topLevelError !== "" ? (
<>
<Typography variant="subtitle2">{topLevelErrorTitle}:</Typography>
<CodeBlock text={topLevelError} />
</>
) : null}
<Typography variant="subtitle2">Runs:</Typography>
{runsNewestFirst.map((run, i) => {
return (
<Accordion key={run.runId} defaultExpanded={i === 0}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { Job } from "models/lookoutV2Models"
import styles from "./SidebarTabJobYaml.module.css"
import { useCustomSnackbar } from "../../../hooks/useCustomSnackbar"
import { useJobSpec } from "../../../hooks/useJobSpec"
import { IGetJobSpecService } from "../../../services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "../../../services/lookoutV2/GetJobInfoService"

export interface SidebarTabJobYamlProps {
job: Job
jobSpecService: IGetJobSpecService
jobSpecService: IGetJobInfoService
}

function toJobSubmissionYaml(jobSpec: Record<string, any>): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import FakeGroupJobsService from "services/lookoutV2/mocks/FakeGroupJobsService"
import { v4 as uuidv4 } from "uuid"

import { JobsTableContainer } from "./JobsTableContainer"
import { IGetJobSpecService } from "../../services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "../../services/lookoutV2/GetJobInfoService"
import { IGetRunInfoService } from "../../services/lookoutV2/GetRunInfoService"
import { ILogService } from "../../services/lookoutV2/LogService"
import { FakeCordonService } from "../../services/lookoutV2/mocks/FakeCordonService"
import FakeGetJobSpecService from "../../services/lookoutV2/mocks/FakeGetJobSpecService"
import FakeGetJobInfoService from "../../services/lookoutV2/mocks/FakeGetJobInfoService"
import { FakeGetRunInfoService } from "../../services/lookoutV2/mocks/FakeGetRunInfoService"
import { FakeLogService } from "../../services/lookoutV2/mocks/FakeLogService"

Expand Down Expand Up @@ -57,7 +57,7 @@ describe("JobsTableContainer", () => {
let getJobsService: IGetJobsService,
groupJobsService: IGroupJobsService,
runErrorService: IGetRunInfoService,
jobSpecService: IGetJobSpecService,
jobSpecService: IGetJobInfoService,
logService: ILogService,
updateJobsService: UpdateJobsService

Expand All @@ -69,7 +69,7 @@ describe("JobsTableContainer", () => {
beforeEach(() => {
setUp([])
runErrorService = new FakeGetRunInfoService(false)
jobSpecService = new FakeGetJobSpecService(false)
jobSpecService = new FakeGetJobInfoService(false)
logService = new FakeLogService()
localStorage.clear()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import styles from "./JobsTableContainer.module.css"
import { useCustomSnackbar } from "../../hooks/useCustomSnackbar"
import { ICordonService } from "../../services/lookoutV2/CordonService"
import { CustomViewsService } from "../../services/lookoutV2/CustomViewsService"
import { IGetJobSpecService } from "../../services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "../../services/lookoutV2/GetJobInfoService"
import { ILogService } from "../../services/lookoutV2/LogService"
import { getErrorMessage, waitMillis, CommandSpec } from "../../utils"
import { EmptyInputError, ParseError } from "../../utils/resourceUtils"
Expand All @@ -83,7 +83,7 @@ interface JobsTableContainerProps {
groupJobsService: IGroupJobsService
updateJobsService: UpdateJobsService
runInfoService: IGetRunInfoService
jobSpecService: IGetJobSpecService
jobSpecService: IGetJobInfoService
logService: ILogService
cordonService: ICordonService
debug: boolean
Expand Down
4 changes: 2 additions & 2 deletions internal/lookout/ui/src/hooks/useJobSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"

import { OpenSnackbarFn } from "./useCustomSnackbar"
import { Job } from "../models/lookoutV2Models"
import { IGetJobSpecService } from "../services/lookoutV2/GetJobSpecService"
import { IGetJobInfoService } from "../services/lookoutV2/GetJobInfoService"
import { getErrorMessage, RequestStatus } from "../utils"

export type JobSpecState = {
Expand All @@ -12,7 +12,7 @@ export type JobSpecState = {

export const useJobSpec = (
job: Job,
jobSpecService: IGetJobSpecService,
jobSpecService: IGetJobInfoService,
openSnackbar: OpenSnackbarFn,
): JobSpecState => {
const [jobSpecState, setJobSpecState] = useState<JobSpecState>({
Expand Down
Loading

0 comments on commit efa679c

Please sign in to comment.