diff --git a/ui/src/components/CreateEditVdbeForm.jsx b/ui/src/components/CreateEditVdbeForm.jsx new file mode 100644 index 00000000..8c58fefe --- /dev/null +++ b/ui/src/components/CreateEditVdbeForm.jsx @@ -0,0 +1,139 @@ +import { useState } from "react"; +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 VdbeView from "./VdbeView"; +import "./styles/CreateEditVdbeForm.css"; + +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 }); + }; + + return ( +
+ setVdbe({ ...vdbe, name: event.target.value })} + /> + minutes + ), + }, + }} + value={ + vdbe.max_staleness_ms != null ? vdbe.max_staleness_ms / 60000 : "" + } + onChange={onStalenessChange} + /> + milliseconds + ), + }, + }} + value={vdbe.p90_latency_slo_ms != null ? vdbe.p90_latency_slo_ms : ""} + onChange={onSloChange} + /> + + Query Interface + + +
+ ); +} + +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 ( + +

+ {isEdit ? ( + + ) : ( + + )} + {isEdit ? "Edit VDBE" : "Create VDBE"} +

+
+ +
+ +
+
+
+ + +
+
+ ); +} + +export default CreateEditVdbeForm; diff --git a/ui/src/components/InsetPanel.jsx b/ui/src/components/InsetPanel.jsx new file mode 100644 index 00000000..78c183de --- /dev/null +++ b/ui/src/components/InsetPanel.jsx @@ -0,0 +1,7 @@ +import "./styles/InsetPanel.css"; + +function InsetPanel({ children, className }) { + return
{children}
; +} + +export default InsetPanel; diff --git a/ui/src/components/VdbeView.jsx b/ui/src/components/VdbeView.jsx index 2acc3958..0f7e6a68 100644 --- a/ui/src/components/VdbeView.jsx +++ b/ui/src/components/VdbeView.jsx @@ -13,6 +13,10 @@ import { } from "../highlight"; function formatMilliseconds(milliseconds) { + if (milliseconds == null) { + return null; + } + const precision = 2; if (milliseconds >= 1000 * 60 * 60) { // Use hours. @@ -27,6 +31,10 @@ function formatMilliseconds(milliseconds) { } function formatFreshness(maxStalenessMs) { + if (maxStalenessMs == null) { + return null; + } + if (maxStalenessMs === 0) { return "No staleness"; } @@ -34,12 +42,19 @@ function formatFreshness(maxStalenessMs) { } function formatDialect(queryInterface) { + if (queryInterface == null) { + return null; + } + if (queryInterface === "postgresql") { return "PostgreSQL SQL"; } else if (queryInterface === "athena") { return "Athena SQL"; } else if (queryInterface === "common") { return "SQL-99"; + } else { + console.error("Unknown", queryInterface); + return null; } } @@ -60,7 +75,13 @@ function EditControls({ onEditClick, onDeleteClick }) { ); } -function VdbeView({ vdbe, highlight, onTableHoverEnter, onTableHoverExit }) { +function VdbeView({ + vdbe, + highlight, + onTableHoverEnter, + onTableHoverExit, + editable, +}) { const vengName = vdbe.name; const tables = vdbe.tables; const freshness = formatFreshness(vdbe.max_staleness_ms); @@ -74,13 +95,20 @@ function VdbeView({ vdbe, highlight, onTableHoverEnter, onTableHoverExit }) { >
{vengName} - {}} onDeleteClick={() => {}} /> + {editable && ( + {}} onDeleteClick={() => {}} /> + )}
diff --git a/ui/src/components/VirtualInfraView.jsx b/ui/src/components/VirtualInfraView.jsx index ba53b255..974d8442 100644 --- a/ui/src/components/VirtualInfraView.jsx +++ b/ui/src/components/VirtualInfraView.jsx @@ -69,6 +69,7 @@ function VirtualInfraView({ onTableHoverEnter={onTableHoverEnter} onTableHoverExit={onTableHoverExit} vdbe={vdbe} + editable={true} /> ))} diff --git a/ui/src/components/styles/CreateEditVdbeForm.css b/ui/src/components/styles/CreateEditVdbeForm.css new file mode 100644 index 00000000..159cd283 --- /dev/null +++ b/ui/src/components/styles/CreateEditVdbeForm.css @@ -0,0 +1,42 @@ +.create-edit-vdbe-form { + display: flex; + flex-direction: column; +} + +.create-edit-vdbe-form h2 { + margin-bottom: 20px; +} + +.cev-form-body { + display: flex; + flex-direction: row; +} + +.cev-form-fields { + display: flex; + flex-direction: column; + flex-grow: 3; + margin-bottom: 10px; +} + +.cev-form-fields .cev-field { + margin: 0 0 15px 0; +} + +.cev-preview { + flex-grow: 2; + display: flex; + justify-content: center; + margin-top: -20px; +} + +.cev-buttons { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; +} + +.cev-buttons button { + margin-left: 20px; +} diff --git a/ui/src/components/styles/InsetPanel.css b/ui/src/components/styles/InsetPanel.css new file mode 100644 index 00000000..891855b2 --- /dev/null +++ b/ui/src/components/styles/InsetPanel.css @@ -0,0 +1,15 @@ +.inset-panel-wrap { + display: flex; + flex-direction: column; + background-color: #f7f8f8; + border-radius: 19px; + padding: 29px; + margin-bottom: 19px; +} + +.inset-panel-wrap h2 { + display: flex; + align-items: center; + font-size: 1.5em; + margin: 0 0 5px 0; +}