Skip to content

Commit

Permalink
Dashboard UI element enhancements (#516)
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffxy authored Jan 23, 2025
1 parent 8ccd2e6 commit 3de53ae
Show file tree
Hide file tree
Showing 24 changed files with 929 additions and 345 deletions.
316 changes: 150 additions & 166 deletions ui/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.15.14",
"@mui/icons-material": "^6.4.1",
"@mui/material": "^6.4.1",
"axios": "^1.6.7",
"chart.js": "^4.4.2",
"react": "^18.2.0",
Expand Down
43 changes: 30 additions & 13 deletions ui/src/App.css
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
:root {
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

html,
body {
height: 100%;
margin: 0;
padding: 0;

font-family: "Source Sans 3", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
}

body {
justify-content: center !important;
align-items: flex-start !important;
}

#root {
width: 100%;
height: auto;
min-height: 100%;
background-color: #fff;

display: flex;
justify-content: center;
align-items: stretch;
Expand Down Expand Up @@ -50,6 +44,8 @@ body {
margin: 0;
font-size: 1.75em;
font-weight: 600;
display: flex;
align-items: center;
}

.column-inner {
Expand All @@ -72,6 +68,27 @@ body {
flex-grow: 1;
}

.infra-separator {
box-shadow: 0px 20px 20px -15px rgba(0, 0, 0, 0.1);
height: 40px;
width: 95%;
margin: 20px auto 40px auto;
}

.infra-region {
position: relative;
}

.infra-region h2 {
position: absolute;
top: 0;
left: 0;
font-size: 1.75em;
color: #ababab;
transform-origin: top left;
transform: rotate(-90deg) translate(calc(-100% - 20px), -10px);
}

/* Responsive layout */
@media only screen and (max-width: 800px) {
.body-container {
Expand Down
42 changes: 27 additions & 15 deletions ui/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import Header from "./components/Header";
import VirtualInfraView from "./components/VirtualInfraView";
import BlueprintView from "./components/BlueprintView";
import PerfView from "./components/PerfView";
import WorkloadInput from "./components/WorkloadInput";
import CreateEditVdbeForm from "./components/CreateEditVdbeForm";
import StorageRoundedIcon from "@mui/icons-material/StorageRounded";
import Panel from "./components/Panel";
import SystemConfig from "./components/SystemConfig";
import { fetchSystemState } from "./api";

Expand Down Expand Up @@ -117,22 +121,30 @@ function App() {
<Header status={systemState.status} />
<div class="body-container">
<div class="column" style={{ flexGrow: 3 }}>
<h2 class="col-h2">Data Infrastructure</h2>
<h2 class="col-h2">
<StorageRoundedIcon style={{ marginRight: "8px" }} />
Data Infrastructure
</h2>
<div class="column-inner">
<VirtualInfraView
virtualInfra={systemState.virtual_infra}
highlight={highlight}
onTableHoverEnter={onTableHoverEnter}
onTableHoverExit={onTableHoverExit}
endpoints={endpoints}
/>
<BlueprintView
blueprint={systemState.blueprint}
nextBlueprint={systemState.next_blueprint}
highlight={highlight}
onTableHoverEnter={onTableHoverEnter}
onTableHoverExit={onTableHoverExit}
/>
<Panel>
<WorkloadInput min={1} max={10} />
<CreateEditVdbeForm isEdit={false} />
<VirtualInfraView
virtualInfra={systemState.virtual_infra}
highlight={highlight}
onTableHoverEnter={onTableHoverEnter}
onTableHoverExit={onTableHoverExit}
endpoints={endpoints}
/>
<div class="infra-separator" />
<BlueprintView
blueprint={systemState.blueprint}
nextBlueprint={systemState.next_blueprint}
highlight={highlight}
onTableHoverEnter={onTableHoverEnter}
onTableHoverExit={onTableHoverExit}
/>
</Panel>
</div>
</div>
<PerfView virtualInfra={systemState.virtual_infra} />
Expand Down
8 changes: 4 additions & 4 deletions ui/src/components/BlueprintView.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Panel from "./Panel";
import PhysDbView from "./PhysDbView";
import "./styles/BlueprintView.css";

Expand All @@ -20,8 +19,9 @@ function BlueprintView({
onTableHoverExit,
}) {
return (
<Panel heading="Physical Infrastructure" className="infra-column-panel">
<div class="bp-view-wrap">
<div class="infra-region bp-view-wrap">
<h2>Physical</h2>
<div class="bp-view-engines-wrap">
{blueprint &&
blueprint.engines &&
blueprint.engines.map(({ name, ...props }) => (
Expand All @@ -36,7 +36,7 @@ function BlueprintView({
/>
))}
</div>
</Panel>
</div>
);
}

Expand Down
219 changes: 219 additions & 0 deletions ui/src/components/CreateEditVdbeForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { useState } from "react";
import { useTheme } from "@mui/material/styles";
import InsetPanel from "./InsetPanel";
import CheckCircleOutlineRoundedIcon from "@mui/icons-material/CheckCircleOutlineRounded";
import FormControl from "@mui/material/FormControl";
import InputLabel from "@mui/material/InputLabel";
import TextField from "@mui/material/TextField";
import MenuItem from "@mui/material/MenuItem";
import InputAdornment from "@mui/material/InputAdornment";
import Button from "@mui/material/Button";
import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline";
import EditRoundedIcon from "@mui/icons-material/EditRounded";
import Select from "@mui/material/Select";
import Chip from "@mui/material/Chip";
import Box from "@mui/material/Box";
import OutlinedInput from "@mui/material/OutlinedInput";
import VdbeView from "./VdbeView";
import "./styles/CreateEditVdbeForm.css";

const ITEM_HEIGHT = 47;
const ITEM_PADDING_TOP = 7;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 3.5 + ITEM_PADDING_TOP,
width: 249,
},
},
};

function getStyles(name, selectedTables, theme) {
return {
fontWeight: selectedTables.includes(name)
? theme.typography.fontWeightMedium
: theme.typography.fontWeightRegular,
};
}

function TableSelector({ tables }) {
const theme = useTheme();
const [selectedTables, setSelectedTables] = useState([]);

const handleChange = (event) => {
const {
target: { value },
} = event;
setSelectedTables(
// On autofill we get a stringified value.
typeof value === "string" ? value.split(",") : value,
);
};

return (
<div>
<FormControl fullWidth>
<InputLabel id="cev-table-selector-label">Tables</InputLabel>
<Select
labelId="cev-table-selector-label"
label="Tables"
id="cev-table-selector"
multiple
value={selectedTables}
onChange={handleChange}
input={<OutlinedInput id="cev-table-selector-field" label="Tables" />}
renderValue={(selected) => (
<Box sx={{ display: "flex", flexWrap: "wrap", gap: -1.5 }}>
{selected.map((value) => (
<Chip
key={value}
label={value}
style={{ marginRight: "8px" }}
/>
))}
</Box>
)}
MenuProps={MenuProps}
>
{tables.map((name) => (
<MenuItem
key={name}
value={name}
style={getStyles(name, selectedTables, theme)}
>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}

function CreateEditFormFields({ vdbe, setVdbe }) {
const onStalenessChange = (event) => {
const maxStalenessMins = parseInt(event.target.value);
if (isNaN(maxStalenessMins)) {
setVdbe({ ...vdbe, max_staleness_ms: null });
return;
}
setVdbe({ ...vdbe, max_staleness_ms: maxStalenessMins * 60 * 1000 });
};

const onSloChange = (event) => {
const sloMs = parseInt(event.target.value);
if (isNaN(sloMs)) {
setVdbe({ ...vdbe, p90_latency_slo_ms: null });
return;
}
setVdbe({ ...vdbe, p90_latency_slo_ms: sloMs });
};

const tables = ["tickets", "theatres", "movies"];

return (
<div className="cev-form-fields">
<TextField
variant="outlined"
label="Name"
className="cev-field"
value={vdbe.name}
onChange={(event) => setVdbe({ ...vdbe, name: event.target.value })}
/>
<TextField
variant="outlined"
label="Maximum Staleness (Freshness Constraint)"
className="cev-field"
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">minutes</InputAdornment>
),
},
}}
value={
vdbe.max_staleness_ms != null ? vdbe.max_staleness_ms / 60000 : ""
}
onChange={onStalenessChange}
/>
<TextField
variant="outlined"
label="Maximum p90 Latency (Performance SLO)"
className="cev-field"
slotProps={{
input: {
endAdornment: (
<InputAdornment position="end">milliseconds</InputAdornment>
),
},
}}
value={vdbe.p90_latency_slo_ms != null ? vdbe.p90_latency_slo_ms : ""}
onChange={onSloChange}
/>
<FormControl fullWidth>
<InputLabel id="cev-query-interface">Query Interface</InputLabel>
<Select
labelId="cev-query-interface"
variant="outlined"
label="Query Interface"
className="cev-field"
value={vdbe.interface}
onChange={(event) =>
setVdbe({ ...vdbe, interface: event.target.value })
}
>
<MenuItem value="common">Common SQL</MenuItem>
<MenuItem value="postgresql">PostgreSQL SQL</MenuItem>
<MenuItem value="athena">Athena SQL</MenuItem>
</Select>
<TableSelector tables={tables} />
</FormControl>
</div>
);
}

function getEmptyVdbe() {
return {
name: null,
max_staleness_ms: null,
p90_latency_slo_ms: null,
queryInterface: "postgresql",
tables: [],
};
}

function CreateEditVdbeForm({ isEdit, currentVdbe }) {
const [vdbe, setVdbe] = useState(
currentVdbe != null ? currentVdbe : getEmptyVdbe(),
);

return (
<InsetPanel className="create-edit-vdbe-form">
<h2>
{isEdit ? (
<EditRoundedIcon style={{ marginRight: "10px" }} />
) : (
<AddCircleOutlineIcon style={{ marginRight: "10px" }} />
)}
{isEdit ? "Edit VDBE" : "Create VDBE"}
</h2>
<div className="cev-form-body">
<CreateEditFormFields vdbe={vdbe} setVdbe={setVdbe} />
<div className="cev-preview">
<VdbeView vdbe={vdbe} highlight={{}} editable={false} />
</div>
</div>
<div className="cev-buttons">
<Button variant="outlined">Cancel</Button>
<Button
variant="contained"
startIcon={<CheckCircleOutlineRoundedIcon />}
>
{isEdit ? "Save" : "Create"}
</Button>
</div>
</InsetPanel>
);
}

export default CreateEditVdbeForm;
Loading

0 comments on commit 3de53ae

Please sign in to comment.