Skip to content

Commit

Permalink
Visualize table placement relationships using hover state (#479)
Browse files Browse the repository at this point in the history
* Set mapping values

* Add UI support for table hover

* Hover state improvements

* Check in

* Fix hover bug

* Hide non-matching

* Hoist relevant tables

* Apply hover effects to engines too

* Format

* Status fix
  • Loading branch information
geoffxy authored Mar 12, 2024
1 parent d2a18c5 commit 238c230
Show file tree
Hide file tree
Showing 16 changed files with 317 additions and 34 deletions.
44 changes: 39 additions & 5 deletions src/brad/ui/manager_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from typing import Optional, List

import brad.ui.static as brad_app
from brad.blueprint import Blueprint
from brad.blueprint.manager import BlueprintManager
from brad.config.engine import Engine
from brad.config.file import ConfigFile
from brad.daemon.monitor import Monitor
from brad.ui.uvicorn_server import PatchedUvicornServer
Expand Down Expand Up @@ -95,7 +97,7 @@ def get_system_state() -> SystemState:
txn_tables = ["theatres", "showings", "ticket_orders", "movie_info", "aka_title"]
txn_only = ["theatres", "showings", "ticket_orders"]
vdbe1 = DisplayableVirtualEngine(
index=1,
name="VDBE 1",
freshness="Serializable",
dialect="PostgreSQL SQL",
peak_latency_s=0.030,
Expand All @@ -112,12 +114,16 @@ def get_system_state() -> SystemState:
)
vdbe1.tables.sort(key=lambda t: t.name)
vdbe2 = DisplayableVirtualEngine(
index=2,
name="VDBE 2",
freshness="≤ 10 minutes stale",
dialect="PostgreSQL SQL",
peak_latency_s=30.0,
tables=[
DisplayableTable(name=table.name, is_writer=False, mapped_to=[])
DisplayableTable(
name=table.name,
is_writer=False,
mapped_to=_analytics_table_mapper_temp(table.name, blueprint),
)
for table in blueprint.tables()
if table.name not in txn_only
],
Expand All @@ -130,8 +136,36 @@ def get_system_state() -> SystemState:
if t.name in txn_tables:
t.is_writer = True
virtual_infra = VirtualInfrastructure(engines=[vdbe1, vdbe2])

return SystemState(virtual_infra=virtual_infra, blueprint=dbp)
system_state = SystemState(virtual_infra=virtual_infra, blueprint=dbp)
_add_reverse_mapping_temp(system_state)
return system_state


def _analytics_table_mapper_temp(table_name: str, blueprint: Blueprint) -> List[str]:
# TODO: This is a hard-coded heurstic for the mock up only.
locations = blueprint.get_table_locations(table_name)
names = []
if Engine.Redshift in locations:
names.append("Redshift")
if Engine.Athena in locations:
names.append("Athena")
return names


def _add_reverse_mapping_temp(system_state: SystemState) -> None:
# TODO: This is a hard-coded heuristic for the mock up only.
# This mutates the passed-in object.
veng_tables = {}
for veng in system_state.virtual_infra.engines:
table_names = {table.name for table in veng.tables}
veng_tables[veng.name] = table_names

for engine in system_state.blueprint.engines:
for table in engine.tables:
name = table.name
for veng_name, tables in veng_tables.items():
if name in tables:
table.mapped_to.append(veng_name)


@app.get("/api/1/system_events")
Expand Down
2 changes: 1 addition & 1 deletion src/brad/ui/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def from_blueprint(cls, blueprint: Blueprint) -> "DisplayableBlueprint":


class DisplayableVirtualEngine(BaseModel):
index: int
name: str
freshness: str
dialect: str
peak_latency_s: Optional[float] = None
Expand Down
5 changes: 5 additions & 0 deletions ui/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ body {
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
}

.infra-column-panel {
flex-basis: 50%;
flex-grow: 1;
}

/* Responsive layout */
@media only screen and (max-width: 800px) {
.body-container {
Expand Down
48 changes: 46 additions & 2 deletions ui/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,40 @@ function App() {
blueprint: null,
virtual_infra: null,
});
const [highlight, setHighlight] = useState({
hoverEngine: null,
virtualEngines: {},
physicalEngines: {},
});

const onTableHoverEnter = (engineMarker, tableName, isVirtual, mappedTo) => {
const virtualEngines = {};
const physicalEngines = {};
if (isVirtual) {
virtualEngines[engineMarker] = tableName;
for (const physMarker of mappedTo) {
physicalEngines[physMarker] = tableName;
}
} else {
physicalEngines[engineMarker] = tableName;
for (const virtMarker of mappedTo) {
virtualEngines[virtMarker] = tableName;
}
}
setHighlight({
hoverEngine: engineMarker,
virtualEngines,
physicalEngines,
});
};

const onTableHoverExit = () => {
setHighlight({
hoverEngine: null,
virtualEngines: {},
physicalEngines: {},
});
};

// Fetch updated system state periodically.
useEffect(() => {
Expand Down Expand Up @@ -44,8 +78,18 @@ function App() {
<div class="column" style={{ flexGrow: 3 }}>
<h2 class="col-h2">Data Infrastructure</h2>
<div class="column-inner">
<VirtualInfraView virtualInfra={systemState.virtual_infra} />
<BlueprintView blueprint={systemState.blueprint} />
<VirtualInfraView
virtualInfra={systemState.virtual_infra}
highlight={highlight}
onTableHoverEnter={onTableHoverEnter}
onTableHoverExit={onTableHoverExit}
/>
<BlueprintView
blueprint={systemState.blueprint}
highlight={highlight}
onTableHoverEnter={onTableHoverEnter}
onTableHoverExit={onTableHoverExit}
/>
</div>
</div>
<PerfView virtualInfra={systemState.virtual_infra} />
Expand Down
21 changes: 18 additions & 3 deletions ui/src/components/BlueprintView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,29 @@ import Panel from "./Panel";
import PhysDbView from "./PhysDbView";
import "./styles/BlueprintView.css";

function BlueprintView({ blueprint }) {
function BlueprintView({
blueprint,
highlight,
onTableHoverEnter,
onTableHoverExit,
}) {
return (
<Panel heading="Current Blueprint (Physical Infrastructure)">
<Panel
heading="Current Blueprint (Physical Infrastructure)"
className="infra-column-panel"
>
<div class="bp-view-wrap">
{blueprint &&
blueprint.engines &&
blueprint.engines.map(({ name, ...props }) => (
<PhysDbView key={name} name={name} {...props} />
<PhysDbView
key={name}
name={name}
{...props}
highlight={highlight}
onTableHoverEnter={onTableHoverEnter}
onTableHoverExit={onTableHoverExit}
/>
))}
</div>
</Panel>
Expand Down
5 changes: 1 addition & 4 deletions ui/src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ function Header() {
<strong>BRAD</strong> Dashboard
</div>
</div>
<StatusIndicator
status="Running normally"
schema="imdb_extended_100g"
/>
<StatusIndicator status="Running" schema="imdb_extended_100g" />
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/Panel.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import "./styles/Panel.css";

function Panel({ heading, children }) {
function Panel({ heading, children, className }) {
return (
<div class="panel">
<div class={`panel ${className != null ? className : ""}`}>
{heading && <h2>{heading}</h2>}
{children}
</div>
Expand Down
40 changes: 36 additions & 4 deletions ui/src/components/PhysDbView.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,47 @@
import DbCylinder from "./DbCylinder";
import TableView from "./TableView";
import "./styles/PhysDbView.css";
import {
highlightTableViewClass,
highlightEngineViewClass,
sortTablesToHoist,
} from "../highlight";

function PhysDbView({
name,
provisioning,
tables,
highlight,
onTableHoverEnter,
onTableHoverExit,
}) {
const physDbName = name;
const sortedTables = sortTablesToHoist(highlight, physDbName, false, tables);

function PhysDbView({ name, provisioning, tables }) {
return (
<div class="physdb-view">
<div
class={`physdb-view ${highlightEngineViewClass(highlight, physDbName, false)}`}
>
<DbCylinder color="blue">{name}</DbCylinder>
<div class="physdb-view-prov">{provisioning}</div>
<div class="db-table-set">
{tables.map(({ name, is_writer }) => (
<TableView key={name} name={name} isWriter={is_writer} color="blue" />
{sortedTables.map(({ name, is_writer, mapped_to }) => (
<TableView
key={name}
name={name}
isWriter={is_writer}
color="blue"
highlightClass={highlightTableViewClass(
highlight,
physDbName,
name,
false,
)}
onTableHoverEnter={() =>
onTableHoverEnter(physDbName, name, false, mapped_to)
}
onTableHoverExit={onTableHoverExit}
/>
))}
</div>
</div>
Expand Down
15 changes: 13 additions & 2 deletions ui/src/components/TableView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@ function WriterMarker({ color }) {
return <div class={`db-table-view-writer ${color}`}>W</div>;
}

function TableView({ name, isWriter, color }) {
function TableView({
name,
isWriter,
color,
onTableHoverEnter,
onTableHoverExit,
highlightClass,
}) {
return (
<div class="db-table-view">
<div
class={`db-table-view ${highlightClass}`}
onMouseEnter={onTableHoverEnter}
onMouseLeave={onTableHoverExit}
>
{name}
{isWriter && <WriterMarker color={color} />}
</div>
Expand Down
37 changes: 33 additions & 4 deletions ui/src/components/VdbeView.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import DbCylinder from "./DbCylinder";
import TableView from "./TableView";
import "./styles/VdbeView.css";
import {
highlightTableViewClass,
highlightEngineViewClass,
sortTablesToHoist,
} from "../highlight";

function VdbeView({
name,
freshness,
dialect,
peak_latency_s,
tables,
highlight,
onTableHoverEnter,
onTableHoverExit,
}) {
const vengName = name;
const sortedTables = sortTablesToHoist(highlight, vengName, true, tables);

function VdbeView({ name, freshness, dialect, peak_latency_s, tables }) {
return (
<div class="vdbe-view">
<DbCylinder color="green">{name}</DbCylinder>
<div
class={`vdbe-view ${highlightEngineViewClass(highlight, vengName, true)}`}
>
<DbCylinder color="green">{vengName}</DbCylinder>
<div class="vdbe-view-props">
<ul>
<li>🌿: {freshness}</li>
Expand All @@ -14,12 +33,22 @@ function VdbeView({ name, freshness, dialect, peak_latency_s, tables }) {
</ul>
</div>
<div class="db-table-set">
{tables.map(({ name, is_writer }) => (
{sortedTables.map(({ name, is_writer, mapped_to }) => (
<TableView
key={name}
name={name}
isWriter={is_writer}
color="green"
highlightClass={highlightTableViewClass(
highlight,
vengName,
name,
true,
)}
onTableHoverEnter={() =>
onTableHoverEnter(vengName, name, true, mapped_to)
}
onTableHoverExit={onTableHoverExit}
/>
))}
</div>
Expand Down
24 changes: 17 additions & 7 deletions ui/src/components/VirtualInfraView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@ import Panel from "./Panel";
import VdbeView from "./VdbeView";
import "./styles/VirtualInfraView.css";

function VirtualInfraView({ virtualInfra }) {
function VirtualInfraView({
virtualInfra,
highlight,
onTableHoverEnter,
onTableHoverExit,
}) {
return (
<Panel heading="Virtual Database Engines">
<Panel heading="Virtual Database Engines" className="infra-column-panel">
<div class="vdbe-view-wrap">
{virtualInfra &&
virtualInfra.engines &&
virtualInfra.engines.map(({ index, ...props }) => (
<VdbeView key={index} name={`VDBE ${index}`} {...props} />
))}
{virtualInfra?.engines?.map(({ name, ...props }) => (
<VdbeView
key={name}
name={name}
highlight={highlight}
onTableHoverEnter={onTableHoverEnter}
onTableHoverExit={onTableHoverExit}
{...props}
/>
))}
</div>
</Panel>
);
Expand Down
2 changes: 2 additions & 0 deletions ui/src/components/styles/DbCylinder.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

opacity: 0.85;
transition: opacity 0.5s;

cursor: default;
}

.db-cylinder:hover {
Expand Down
5 changes: 5 additions & 0 deletions ui/src/components/styles/PhysDbView.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@
font-weight: 600;
height: 30px;
}

.physdb-view.dim .db-cylinder,
.physdb-view.dim .physdb-view-prov {
opacity: 0.3;
}
Loading

0 comments on commit 238c230

Please sign in to comment.