Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dashboard] Add Replicas to Serve Dashboard UI #33503

Merged
merged 10 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions dashboard/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ import { ClusterDetailInfoPage } from "./pages/node/ClusterDetailInfoPage";
import { ClusterLayout } from "./pages/node/ClusterLayout";
import NodeDetailPage from "./pages/node/NodeDetail";
import { OverviewPage } from "./pages/overview/OverviewPage";
import { ServeApplicationDetailPage } from "./pages/serve/ServeApplicationDetailPage";
import {
ServeApplicationDetailLayout,
ServeApplicationDetailPage,
} from "./pages/serve/ServeApplicationDetailPage";
import { ServeApplicationsListPage } from "./pages/serve/ServeApplicationsListPage";
import { ServeLayout } from "./pages/serve/ServeLayout";
import { ServeReplicaDetailPage } from "./pages/serve/ServeReplicaDetailPage";
import { getNodeList } from "./service/node";
import { lightTheme } from "./theme";

Expand Down Expand Up @@ -191,9 +195,15 @@ const App = () => {
<Route element={<ServeLayout />} path="serve">
<Route element={<ServeApplicationsListPage />} path="" />
<Route
element={<ServeApplicationDetailPage />}
path="applications/:name"
/>
element={<ServeApplicationDetailLayout />}
path="applications/:applicationName"
>
<Route element={<ServeApplicationDetailPage />} path="" />
<Route
element={<ServeReplicaDetailPage />}
path=":deploymentName/:replicaId"
/>
</Route>
</Route>
<Route element={<LogsLayout />} path="logs">
{/* TODO(aguo): Refactor Logs component to use optional query
Expand Down
14 changes: 13 additions & 1 deletion dashboard/client/src/components/StatusChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import {
green,
grey,
lightBlue,
orange,
red,
yellow,
} from "@material-ui/core/colors";
import { CSSProperties } from "@material-ui/core/styles/withStyles";
import React, { ReactNode } from "react";
import { ActorEnum } from "../type/actor";
import { PlacementGroupState } from "../type/placementGroup";
import { ServeApplicationStatus, ServeDeploymentStatus } from "../type/serve";
import {
ServeApplicationStatus,
ServeDeploymentStatus,
ServeReplicaState,
} from "../type/serve";
import { TypeTaskStatus } from "../type/task";

const colorMap = {
Expand Down Expand Up @@ -70,6 +75,13 @@ const colorMap = {
[ServeDeploymentStatus.HEALTHY]: green,
[ServeDeploymentStatus.UNHEALTHY]: red,
},
serveReplica: {
[ServeReplicaState.STARTING]: yellow,
[ServeReplicaState.UPDATING]: yellow,
[ServeReplicaState.RECOVERING]: orange,
[ServeReplicaState.RUNNING]: green,
[ServeReplicaState.STOPPING]: red,
},
} as {
[key: string]: {
[key: string]: Color | string;
Expand Down
11 changes: 5 additions & 6 deletions dashboard/client/src/pages/layout/MainNavLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,11 @@ const NAV_ITEMS = [
path: "/jobs",
id: "jobs",
},
// TODO(aguo): Add this nav button in once the page is fully implemented.
// {
// title: "Serve",
// path: "/serve",
// id: "serve",
// },
{
title: "Serve",
path: "/serve",
id: "serve",
},
{
title: "Cluster",
path: "/cluster",
Expand Down
66 changes: 46 additions & 20 deletions dashboard/client/src/pages/serve/ServeApplicationDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from "@material-ui/core";
import { Autocomplete, Pagination } from "@material-ui/lab";
import React, { ReactElement } from "react";
import { useParams } from "react-router-dom";
import { Outlet, useParams } from "react-router-dom";
import { CodeDialogButton } from "../../common/CodeDialogButton";
import { CollapsibleSection } from "../../common/CollapsibleSection";
import { DurationText } from "../../common/DurationText";
Expand All @@ -39,16 +39,19 @@ const useStyles = makeStyles((theme) =>
);

const columns: { label: string; helpInfo?: ReactElement; width?: string }[] = [
{ label: "" }, // For expand/collapse button
{ label: "Name" },
{ label: "Status" },
{ label: "Status message", width: "30%" },
{ label: "Actions" },
{ label: "Last deployed at" },
{ label: "Duration" },
{ label: "Replicas" },
{ label: "Deployment config" },
];

export const ServeApplicationDetailPage = () => {
const classes = useStyles();
const { name } = useParams();
const { applicationName } = useParams();

const {
application,
Expand All @@ -57,33 +60,19 @@ export const ServeApplicationDetailPage = () => {
setPage,
changeFilter,
allDeployments,
} = useServeApplicationDetails(name);
} = useServeApplicationDetails(applicationName);

if (!application) {
return (
<Typography color="error">
Application with name "{name}" not found.
Application with name "{applicationName}" not found.
</Typography>
);
}

const appName = application.name ? application.name : "-";
return (
<div>
{/* Extra MainNavPageInfo to add an extra layer of nesting in breadcrumbs */}
<MainNavPageInfo
pageInfo={{
id: "serveApplicationsList",
title: "Applications",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put this in ServeApplicationsListPage? Although I guess the use of main nav is mainly used to navigate up from the current breadcrumbs level

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, this has to be here because Serve Application Page is a parent of ServeReplicaDetails and we need to have this nav bar show up for the detail page.

}}
/>
<MainNavPageInfo
pageInfo={{
id: "serveApplicationDetail",
title: appName,
path: `/serve/applications/${appName}`,
}}
/>
<MetadataSection
metadataList={[
{
Expand Down Expand Up @@ -148,7 +137,7 @@ export const ServeApplicationDetailPage = () => {
},
]}
/>
<CollapsibleSection title="Deployments" startExpanded>
<CollapsibleSection title="Deployments / Replicas" startExpanded>
<TableContainer>
<div style={{ flex: 1, display: "flex", alignItems: "center" }}>
<Autocomplete
Expand Down Expand Up @@ -228,6 +217,7 @@ export const ServeApplicationDetailPage = () => {
<ServeDeploymentRow
key={deployment.name}
deployment={deployment}
application={application}
/>
))}
</TableBody>
Expand All @@ -237,3 +227,39 @@ export const ServeApplicationDetailPage = () => {
</div>
);
};

export const ServeApplicationDetailLayout = () => {
const { applicationName } = useParams();

const { application } = useServeApplicationDetails(applicationName);

if (!application) {
return (
<Typography color="error">
Application with name "{applicationName}" not found.
</Typography>
);
}

const appName = application.name ? application.name : "-";

return (
<React.Fragment>
{/* Extra MainNavPageInfo to add an extra layer of nesting in breadcrumbs */}
<MainNavPageInfo
pageInfo={{
id: "serveApplicationsList",
title: "Applications",
}}
/>
<MainNavPageInfo
pageInfo={{
id: "serveApplicationDetail",
title: appName,
path: `/serve/applications/${appName}`,
}}
/>
<Outlet />
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ export const ServeApplicationsListPage = () => {
value: `${serveDetails.port}`,
},
},
{
label: "Proxy location",
content: {
value: `${serveDetails.proxy_location}`,
},
},
]}
/>
) : (
Expand Down
141 changes: 124 additions & 17 deletions dashboard/client/src/pages/serve/ServeDeploymentRow.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,149 @@
import { TableCell, TableRow } from "@material-ui/core";
import React from "react";
import {
createStyles,
makeStyles,
TableCell,
TableRow,
} from "@material-ui/core";
import AddIcon from "@material-ui/icons/Add";
import RemoveIcon from "@material-ui/icons/Remove";
import React, { useState } from "react";
import { Link } from "react-router-dom";
import {
CodeDialogButton,
CodeDialogButtonWithPreview,
} from "../../common/CodeDialogButton";
import { DurationText } from "../../common/DurationText";
import { formatDateFromTimeMs } from "../../common/formatUtils";
import { StatusChip } from "../../components/StatusChip";
import { ServeDeployment } from "../../type/serve";
import {
ServeApplication,
ServeDeployment,
ServeReplica,
} from "../../type/serve";
import { ServeReplicaLogsLink } from "./ServeReplicaDetailPage";

const useStyles = makeStyles((theme) =>
createStyles({
deploymentName: {
fontWeight: 500,
},
expandCollapseIcon: {
color: theme.palette.text.secondary,
fontSize: "1.5em",
verticalAlign: "middle",
},
}),
);

export type ServeDeployentRowProps = {
deployment: ServeDeployment;
application: ServeApplication;
};

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

const classes = useStyles();

const [expanded, setExpanded] = useState(false);

return (
<React.Fragment>
<TableRow>
<TableCell
align="center"
onClick={() => {
setExpanded(!expanded);
}}
>
{!expanded ? (
<AddIcon className={classes.expandCollapseIcon} />
) : (
<RemoveIcon className={classes.expandCollapseIcon} />
)}
</TableCell>
<TableCell align="center" className={classes.deploymentName}>
{name}
</TableCell>
<TableCell align="center">
<StatusChip type="serveDeployment" status={status} />
</TableCell>
<TableCell align="center">
{message ? (
<CodeDialogButtonWithPreview
title="Message details"
code={message}
/>
) : (
"-"
)}
</TableCell>
<TableCell align="center">
<CodeDialogButton
title={`Deployment config for ${name}`}
code={deployment_config}
buttonText="Deployment config"
/>
</TableCell>
<TableCell align="center">
{formatDateFromTimeMs(last_deployed_time_s * 1000)}
</TableCell>
<TableCell align="center">
<DurationText startTime={last_deployed_time_s * 1000} />
</TableCell>
<TableCell align="center">{Object.keys(replicas).length}</TableCell>
</TableRow>
{expanded &&
replicas.map((replica) => (
<ServeReplicaRow
key={replica.replica_id}
replica={replica}
deployment={deployment}
/>
))}
</React.Fragment>
);
};

export type ServeReplicaRowProps = {
replica: ServeReplica;
deployment: ServeDeployment;
};

export const ServeReplicaRow = ({
replica,
deployment,
}: ServeReplicaRowProps) => {
const { replica_id, state, start_time_s } = replica;
const { name } = deployment;

return (
<TableRow>
<TableCell align="center">{name}</TableCell>
<TableCell align="center"></TableCell>
<TableCell align="center">
<Link
to={`${encodeURIComponent(name)}/${encodeURIComponent(replica_id)}`}
>
{replica_id}
</Link>
</TableCell>
<TableCell align="center">
<StatusChip type="serveDeployment" status={status} />
<StatusChip type="serveReplica" status={state} />
</TableCell>
<TableCell align="center">-</TableCell>
<TableCell align="center">
{message ? (
<CodeDialogButtonWithPreview title="Message details" code={message} />
) : (
"-"
)}
<ServeReplicaLogsLink replica={replica} deployment={deployment} />
</TableCell>
<TableCell align="center">
{/* TODO(aguo): Add number of replicas (instead of target number of replicas) once available from API */}
{deployment_config.num_replicas}
{formatDateFromTimeMs(start_time_s * 1000)}
</TableCell>
<TableCell align="center">
<CodeDialogButton
title={`Deployment config for ${name}`}
code={deployment_config}
/>
<DurationText startTime={start_time_s * 1000} />
</TableCell>
<TableCell align="center">-</TableCell>
</TableRow>
);
};
Loading