Skip to content

Commit

Permalink
[Dashboard] Update Serve System UI (ray-project#36787)
Browse files Browse the repository at this point in the history
Add logs to the main serve page
Put serve system details on a separate tab, but show a preview on the main Serve page

Signed-off-by: e428265 <arvind.chandramouli@lmco.com>
  • Loading branch information
alanwguo authored and arvind-chandra committed Aug 31, 2023
1 parent f8f80ec commit e44f9b2
Show file tree
Hide file tree
Showing 15 changed files with 543 additions and 82 deletions.
43 changes: 33 additions & 10 deletions dashboard/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ import {
ServeApplicationDetailPage,
} from "./pages/serve/ServeApplicationDetailPage";
import { ServeApplicationsListPage } from "./pages/serve/ServeApplicationsListPage";
import { ServeLayout } from "./pages/serve/ServeLayout";
import { ServeLayout, ServeSideTabLayout } from "./pages/serve/ServeLayout";
import { ServeReplicaDetailPage } from "./pages/serve/ServeReplicaDetailPage";
import {
ServeControllerDetailPage,
ServeHttpProxyDetailPage,
} from "./pages/serve/ServeSystemActorDetailPage";
import {
ServeSystemDetailLayout,
ServeSystemDetailPage,
} from "./pages/serve/ServeSystemDetailPage";
import { TaskPage } from "./pages/task/TaskPage";
import { getNodeList } from "./service/node";
import { lightTheme } from "./theme";
Expand Down Expand Up @@ -225,15 +229,34 @@ const App = () => {
</Route>
<Route element={<Metrics />} path="metrics" />
<Route element={<ServeLayout />} path="serve">
<Route element={<ServeApplicationsListPage />} path="" />
<Route
element={<ServeControllerDetailPage />}
path="controller"
/>
<Route
element={<ServeHttpProxyDetailPage />}
path="httpProxies/:httpProxyId"
/>
<Route element={<ServeSideTabLayout />} path="">
<Route
element={
<SideTabPage tabId="system">
<ServeSystemDetailPage />
</SideTabPage>
}
path="system"
/>
<Route
element={
<SideTabPage tabId="applications">
<ServeApplicationsListPage />
</SideTabPage>
}
path=""
/>
</Route>
<Route element={<ServeSystemDetailLayout />} path="system">
<Route
element={<ServeControllerDetailPage />}
path="controller"
/>
<Route
element={<ServeHttpProxyDetailPage />}
path="httpProxies/:httpProxyId"
/>
</Route>
<Route
element={<ServeApplicationDetailLayout />}
path="applications/:applicationName"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,16 @@ const MetadataList: React.FC<{
export const MetadataSection = ({
header,
metadataList,
footer,
}: {
header?: string;
metadataList: Metadata[];
footer?: JSX.Element;
}) => {
return (
<Section title={header} marginTop={1} marginBottom={4}>
<MetadataList metadataList={metadataList} />
<Box marginTop={1}>{footer}</Box>
</Section>
);
};
17 changes: 7 additions & 10 deletions dashboard/client/src/components/StatusChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const colorMap = {
[ServeApplicationStatus.RUNNING]: green,
[ServeApplicationStatus.DEPLOY_FAILED]: red,
[ServeApplicationStatus.DELETING]: orange,
[ServeApplicationStatus.UNHEALTHY]: red,
},
serveDeployment: {
[ServeDeploymentStatus.UPDATING]: orange,
Expand Down Expand Up @@ -106,7 +107,6 @@ const useStyles = makeStyles((theme) =>
border: "solid 1px",
borderRadius: 4,
fontSize: 12,
margin: 2,
display: "inline-flex",
alignItems: "center",
},
Expand All @@ -116,17 +116,14 @@ const useStyles = makeStyles((theme) =>
}),
);

export const StatusChip = ({
type,
status,
suffix,
icon,
}: {
type: string;
export type StatusChipProps = {
type: keyof typeof colorMap;
status: string | ActorEnum | ReactNode;
suffix?: string;
suffix?: ReactNode;
icon?: ReactNode;
}) => {
};

export const StatusChip = ({ type, status, suffix, icon }: StatusChipProps) => {
const classes = useStyles();
let color: Color | string = blueGrey;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const mockGetServeApplications = jest.mocked(getServeApplications);

describe("ServeApplicationDetailPage", () => {
it("renders page with deployments and replicas", async () => {
expect.assertions(19);
expect.assertions(22);

mockUseParams.mockReturnValue({
applicationName: "home",
Expand Down Expand Up @@ -146,18 +146,23 @@ describe("ServeApplicationDetailPage", () => {
expect(screen.getByText(/test-config: 1/)).toBeVisible();
expect(screen.getByText(/autoscaling-value: 2/)).toBeVisible();

// Expand the first deployment
await user.click(screen.getAllByTitle("Expand")[0]);
await screen.findByText("test-replica-1");
// All deployments are already expanded
expect(screen.getByText("test-replica-1")).toBeVisible();
expect(screen.getByText("test-replica-2")).toBeVisible();
expect(screen.queryByText("test-replica-3")).toBeNull();
expect(screen.getByText("test-replica-3")).toBeVisible();

// Collapse the first deployment
await user.click(screen.getByTitle("Collapse"));
await user.click(screen.getAllByTitle("Collapse")[0]);
await waitFor(() => screen.queryByText("test-replica-1") === null);
expect(screen.queryByText("test-replica-1")).toBeNull();
expect(screen.queryByText("test-replica-2")).toBeNull();
expect(screen.queryByText("test-replica-3")).toBeNull();
expect(screen.getByText("test-replica-3")).toBeVisible();

// Expand the first deployment again
await user.click(screen.getByTitle("Expand"));
await screen.findByText("test-replica-1");
expect(screen.getByText("test-replica-1")).toBeVisible();
expect(screen.getByText("test-replica-2")).toBeVisible();
expect(screen.getByText("test-replica-3")).toBeVisible();
});
});
15 changes: 14 additions & 1 deletion dashboard/client/src/pages/serve/ServeApplicationDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import { ServeDeploymentRow } from "./ServeDeploymentRow";

const useStyles = makeStyles((theme) =>
createStyles({
root: {
padding: theme.spacing(3),
},
table: {
tableLayout: "fixed",
},
Expand Down Expand Up @@ -76,8 +79,17 @@ export const ServeApplicationDetailPage = () => {
}

const appName = application.name ? application.name : "-";
// Expand all deployments if there is only 1 deployment or
// there are less than 10 replicas across all deployments.
const deploymentsStartExpanded =
Object.keys(application.deployments).length === 1 ||
Object.values(application.deployments).reduce(
(acc, deployment) => acc + deployment.replicas.length,
0,
) < 10;

return (
<div>
<div className={classes.root}>
<MetadataSection
metadataList={[
{
Expand Down Expand Up @@ -245,6 +257,7 @@ export const ServeApplicationDetailPage = () => {
key={deployment.name}
deployment={deployment}
application={application}
startExpanded={deploymentsStartExpanded}
/>
))}
</TableBody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const mockGetActor = jest.mocked(getActor);

describe("ServeApplicationsListPage", () => {
it("renders list", async () => {
expect.assertions(14);
expect.assertions(5);

// Mock ServeController actor fetch
mockGetActor.mockResolvedValue({
Expand Down Expand Up @@ -79,29 +79,15 @@ describe("ServeApplicationsListPage", () => {
} as any);

render(<ServeApplicationsListPage />, { wrapper: TEST_APP_WRAPPER });

await screen.findByText("System");
expect(screen.getByText("System")).toBeVisible();
expect(screen.getByText("1.2.3.4")).toBeVisible();
expect(screen.getByText("8000")).toBeVisible();

// HTTP Proxy row
expect(screen.getByText("HTTPProxyActor:node:12345")).toBeVisible();
expect(screen.getByText("STARTING")).toBeVisible();

// Serve Controller row
expect(screen.getByText("Serve Controller")).toBeVisible();
expect(screen.getByText("HEALTHY")).toBeVisible();
await screen.findByText("Application status");

// First row
expect(screen.getByText("home")).toBeVisible();
expect(screen.getByText("/")).toBeVisible();
expect(screen.getByText("RUNNING")).toBeVisible();

// Second row
expect(screen.getByText("second-app")).toBeVisible();
expect(screen.getByText("/second-app")).toBeVisible();
expect(screen.getByText("DEPLOYING")).toBeVisible();

expect(screen.getByText("Metrics")).toBeVisible();
});
Expand Down
77 changes: 65 additions & 12 deletions dashboard/client/src/pages/serve/ServeApplicationsListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@ import {
import { Alert, Autocomplete, Pagination } from "@material-ui/lab";
import React, { ReactElement } from "react";
import { CollapsibleSection } from "../../common/CollapsibleSection";
import {
MultiTabLogViewer,
MultiTabLogViewerTabDetails,
} from "../../common/MultiTabLogViewer";
import { Section } from "../../common/Section";
import Loading from "../../components/Loading";
import { HelpInfo } from "../../components/Tooltip";
import { ServeSystemActor } from "../../type/serve";
import { useFetchActor } from "../actor/hook/useActorDetail";
import { useServeApplications } from "./hook/useServeApplications";
import { ServeApplicationRow } from "./ServeApplicationRow";
import { ServeMetricsSection } from "./ServeMetricsSection";
import { ServeSystemDetails } from "./ServeSystemDetails";
import { ServeSystemPreview } from "./ServeSystemDetails";

const useStyles = makeStyles((theme) =>
createStyles({
root: {
padding: theme.spacing(3),
},
table: {
tableLayout: "fixed",
},
Expand All @@ -37,7 +47,7 @@ const useStyles = makeStyles((theme) =>
applicationsSection: {
marginTop: theme.spacing(4),
},
metricsSection: {
section: {
marginTop: theme.spacing(4),
},
}),
Expand All @@ -59,13 +69,11 @@ export const ServeApplicationsListPage = () => {
const {
serveDetails,
filteredServeApplications,
httpProxies,
error,
allServeApplications,
page,
setPage,
httpProxiesPage,
setHttpProxiesPage,
httpProxies,
changeFilter,
} = useServeApplications();

Expand All @@ -78,21 +86,20 @@ export const ServeApplicationsListPage = () => {
}

return (
<div>
<div className={classes.root}>
{serveDetails.http_options === undefined ? (
<Alert className={classes.serveInstanceWarning} severity="warning">
Serve not started. Please deploy a serve application first.
</Alert>
) : (
<React.Fragment>
<ServeSystemDetails
serveDetails={serveDetails}
<ServeSystemPreview
allApplications={allServeApplications}
httpProxies={httpProxies}
page={httpProxiesPage}
setPage={setHttpProxiesPage}
serveDetails={serveDetails}
/>
<CollapsibleSection
title="Serve applications"
title="Applications"
startExpanded
className={classes.applicationsSection}
>
Expand Down Expand Up @@ -192,9 +199,55 @@ export const ServeApplicationsListPage = () => {
</Table>
</TableContainer>
</CollapsibleSection>
<CollapsibleSection
title="Logs"
startExpanded
className={classes.section}
>
<Section noTopPadding>
<ServeControllerLogs controller={serveDetails.controller_info} />
</Section>
</CollapsibleSection>
</React.Fragment>
)}
<ServeMetricsSection className={classes.metricsSection} />
<ServeMetricsSection className={classes.section} />
</div>
);
};

type ServeControllerLogsProps = {
controller: ServeSystemActor;
};

const ServeControllerLogs = ({
controller: { actor_id, log_file_path },
}: ServeControllerLogsProps) => {
const { data: fetchedActor } = useFetchActor(actor_id);

if (!fetchedActor || !log_file_path) {
return <Loading loading={true} />;
}

const tabs: MultiTabLogViewerTabDetails[] = [
{
title: "Controller logs",
nodeId: fetchedActor.address.rayletId,
filename: log_file_path.startsWith("/")
? log_file_path.substring(1)
: log_file_path,
},
{
title: "Other logs",
contents:
"Replica logs contain the application logs emitted by each Serve Replica.\n" +
"To view replica logs, please click into a Serve application from " +
"the table above to enter the Application details page.\nThen, click " +
"into a Serve Replica in the Deployments table.\n\n" +
"HTTP Proxy logs contains HTTP access logs for each HTTP Proxy.\n" +
"To view HTTP Proxy logs, click into a HTTP Proxy from the Serve System " +
"Details page.\nThis page can be accessed via the left tab menu or by " +
'clicking "View system status and configuration" link above.',
},
];
return <MultiTabLogViewer tabs={tabs} />;
};
4 changes: 3 additions & 1 deletion dashboard/client/src/pages/serve/ServeDeploymentRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,19 @@ const useStyles = makeStyles((theme) =>
export type ServeDeployentRowProps = {
deployment: ServeDeployment;
application: ServeApplication;
startExpanded?: boolean;
};

export const ServeDeploymentRow = ({
deployment,
application: { last_deployed_time_s },
startExpanded = false,
}: ServeDeployentRowProps) => {
const { name, status, message, deployment_config, replicas } = deployment;

const classes = useStyles();

const [expanded, setExpanded] = useState(false);
const [expanded, setExpanded] = useState(startExpanded);
const metricsUrl = useViewServeDeploymentMetricsButtonUrl(name);

return (
Expand Down
Loading

0 comments on commit e44f9b2

Please sign in to comment.