-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* First pass at adding page that lists jobs of each status * Add small updates to jobs sidevar icon and content displayed in job status tables * Small fix to job model: client_info to clients_info to reflect the actual field name * Checking if this works * Adding nextjs-lint to precommit rules * Adding prettier * Adding js unit tests * Adding js unit tests instructions * Changing prettier indentation to 4 spaces * Adding autofix and unit tsts on commit hook * Adding nextjs-unit to the skip list of autofix bot * Update .pre-commit-config.yaml * test adding eslintignore * testing something * Adding more stuff to ignore * Reverting the changes to min.js files, adding them to prettier and eslint ignore * Last change, I promise * Address CR by Marcelo and add typescript typing * Add initial unit tests for list jobs page UI * Add tests to check table contents * Add better mocking for tests * Try to fix error with pre-commit on server * attempt to fix pre-commit ci errors * Attempt to get prettier to run for pre-commit ci * remove invalid hook type and add additional dependencies key with yarn * add prettier dependency * Change to camel case * Address CR by Marcelo * Attempt to resolve pip audit issue with tqdm --------- Co-authored-by: Marcelo Lotif <[email protected]> Co-authored-by: Marcelo Lotif <[email protected]>
- Loading branch information
1 parent
5252cb3
commit ef82de7
Showing
11 changed files
with
303 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import useSWR from "swr"; | ||
import { fetcher } from "../client_imports"; | ||
|
||
export default function useGetJobsByJobStatus(status: string) { | ||
const endpoint = "/api/server/job/".concat(status); | ||
const { data, error, isLoading } = useSWR(endpoint, fetcher, { | ||
refresh_interval: 1000, | ||
}); | ||
return { data, error, isLoading }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
"use client"; | ||
import { ReactElement } from "react/React"; | ||
import useGetJobsByStatus from "./hooks"; | ||
import useGetJobsByJobStatus from "./hooks"; | ||
|
||
export const validStatuses = { | ||
NOT_STARTED: "Not Started", | ||
IN_PROGRESS: "In Progress", | ||
FINISHED_WITH_ERROR: "Finished with Error", | ||
FINISHED_SUCCESSFULLY: "Finished Successfully", | ||
}; | ||
|
||
interface JobData { | ||
status: string; | ||
model: string; | ||
server_address: string; | ||
server_info: string; | ||
redis_host: string; | ||
redis_port: string; | ||
clients_info: Array<ClientInfo>; | ||
} | ||
|
||
interface ClientInfo { | ||
client: string; | ||
service_address: string; | ||
data_path: string; | ||
redis_host: string; | ||
redis_port: string; | ||
} | ||
|
||
interface StatusProp { | ||
status: string; | ||
} | ||
|
||
export default function Page(): ReactElement { | ||
const statusComponents = Object.keys(validStatuses).map((key, i) => ( | ||
<Status key={key} status={key} /> | ||
)); | ||
return ( | ||
<div className="mx-4"> | ||
<h1> Job Status </h1> | ||
{statusComponents} | ||
</div> | ||
); | ||
} | ||
|
||
export function Status({ status }: StatusProp): ReactElement { | ||
const { data, error, isLoading } = useGetJobsByJobStatus(status); | ||
if (error) return <span> Help1</span>; | ||
if (isLoading) return <span> Help2 </span>; | ||
|
||
return ( | ||
<div> | ||
<h4 data-testid={`status-header-${status}`}> | ||
{validStatuses[status]} | ||
</h4> | ||
<StatusTable data={data} status={status} /> | ||
</div> | ||
); | ||
} | ||
|
||
export function StatusTable({ | ||
data, | ||
status, | ||
}: { | ||
data: Array<JobData>; | ||
status: StatusProp; | ||
}): ReactElement { | ||
if (data.length > 0) { | ||
return ( | ||
<table data-testid={`status-table-${status}`} className="table"> | ||
<thead> | ||
<tr> | ||
<th style={{ width: "25%" }}>Model</th> | ||
<th style={{ width: "25%" }}>Server Address</th> | ||
<th style={{ width: "50%" }}> | ||
Client Service Addresses{" "} | ||
</th> | ||
</tr> | ||
</thead> | ||
<TableRows data={data} /> | ||
</table> | ||
); | ||
} else { | ||
return ( | ||
<div> | ||
<span data-testid={`status-no-jobs-${status}`}> | ||
{" "} | ||
No jobs to display.{" "} | ||
</span> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export function TableRows({ data }: { data: Array<JobData> }): ReactElement { | ||
const tableRows = data.map((d, i) => ( | ||
<TableRow | ||
key={i} | ||
model={d.model} | ||
serverAddress={d.server_address} | ||
clientsInfo={d.clients_info} | ||
/> | ||
)); | ||
|
||
return <tbody>{tableRows}</tbody>; | ||
} | ||
|
||
export function TableRow({ | ||
model, | ||
serverAddress, | ||
clientsInfo, | ||
}: { | ||
model: string; | ||
serverAddress: string; | ||
clientsInfo: Array<ClientInfo>; | ||
}): ReactElement { | ||
return ( | ||
<tr> | ||
<td>{model}</td> | ||
<td>{serverAddress}</td> | ||
<ClientListTableData clientsInfo={clientsInfo} /> | ||
</tr> | ||
); | ||
} | ||
|
||
export function ClientListTableData({ | ||
clientsInfo, | ||
}: { | ||
clientsInfo: Array<ClientInfo>; | ||
}): ReactElement { | ||
const clientServiceAddressesString = clientsInfo | ||
.map((c) => c.service_address) | ||
.join(", "); | ||
return <td> {clientServiceAddressesString} </td>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import "@testing-library/jest-dom"; | ||
import { getByText, render, cleanup } from "@testing-library/react"; | ||
import { describe, afterEach, it, expect } from "@jest/globals"; | ||
|
||
import Page, { validStatuses } from "../../../../app/jobs/page"; | ||
import useGetJobsByStatus from "../../../../app/jobs/hooks"; | ||
|
||
jest.mock("../../../../app/jobs/hooks"); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
cleanup(); | ||
}); | ||
|
||
function mockJobData( | ||
model: string, | ||
serverAddress: string, | ||
clientServicesAddresses: Array<string>, | ||
) { | ||
const data = { | ||
model: model, | ||
server_address: serverAddress, | ||
clients_info: clientServicesAddresses.map((clientServicesAddress) => ({ | ||
service_address: clientServicesAddress, | ||
})), | ||
}; | ||
return data; | ||
} | ||
|
||
function setupMock( | ||
validStatuses: Array<string>, | ||
data: Array<object>, | ||
error: boolean, | ||
isLoading: boolean, | ||
) { | ||
useGetJobsByStatus.mockImplementation((status: string) => { | ||
if (validStatuses.includes(status)) { | ||
return { | ||
data, | ||
error, | ||
isLoading, | ||
}; | ||
} else { | ||
return { | ||
data: [], | ||
error: false, | ||
isLoading: false, | ||
}; | ||
} | ||
}); | ||
} | ||
|
||
describe("List Jobs Page", () => { | ||
it("Renders Page Title correct", () => { | ||
setupMock([], [], false, false); | ||
const { container } = render(<Page />); | ||
const h1 = container.querySelector("h1"); | ||
expect(h1).toBeInTheDocument(); | ||
expect(h1).toHaveTextContent("Job Status"); | ||
}); | ||
|
||
it("Renders Status Components Headers", () => { | ||
const data = [ | ||
mockJobData("MNIST", "localhost:8080", ["localhost:7080"]), | ||
]; | ||
const validStatusesKeys = Object.keys(validStatuses); | ||
|
||
setupMock(validStatusesKeys, data, false, false); | ||
const { getByTestId } = render(<Page />); | ||
|
||
for (const status of validStatusesKeys) { | ||
const element = getByTestId(`status-header-${status}`); | ||
expect(element).toBeInTheDocument(); | ||
expect(element).toHaveTextContent(validStatuses[status]); | ||
} | ||
}); | ||
|
||
it("Renders Status Table With Table with Data", () => { | ||
const data = [ | ||
mockJobData("MNIST", "localhost:8080", ["localhost:7080"]), | ||
]; | ||
const validStatusesKeys = Object.keys(validStatuses); | ||
|
||
setupMock(validStatusesKeys, data, false, false); | ||
|
||
const { getByTestId } = render(<Page />); | ||
|
||
for (const status of validStatusesKeys) { | ||
const element = getByTestId(`status-table-${status}`); | ||
expect(getByText(element, "Model")).toBeInTheDocument(); | ||
expect(getByText(element, "Server Address")).toBeInTheDocument(); | ||
expect( | ||
getByText(element, "Client Service Addresses"), | ||
).toBeInTheDocument(); | ||
expect(getByText(element, "MNIST")).toBeInTheDocument(); | ||
expect(getByText(element, "localhost:8080")).toBeInTheDocument(); | ||
expect(getByText(element, "localhost:7080")).toBeInTheDocument(); | ||
} | ||
}); | ||
|
||
it("Renders Status Table With Table without Data", () => { | ||
setupMock([], [], false, false); | ||
const { getByTestId } = render(<Page />); | ||
|
||
for (const status of Object.keys(validStatuses)) { | ||
const element = getByTestId(`status-no-jobs-${status}`); | ||
expect( | ||
getByText(element, "No jobs to display."), | ||
).toBeInTheDocument(); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.