Skip to content

Commit

Permalink
Create Snapshot Model and Route (#3135)
Browse files Browse the repository at this point in the history
I've removed the query version slice of state in favor
of a snapshot slice of state. The session page now
needs a snapshot id in the url top populate the editor
and pins. The snapshot has this shape.

id, value, pins, queryId, sessionId

The code is much simpler to reason about now.
  • Loading branch information
jameskerr authored Oct 7, 2024
1 parent e622738 commit f27e6a2
Show file tree
Hide file tree
Showing 137 changed files with 1,198 additions and 2,191 deletions.
1 change: 1 addition & 0 deletions apps/zui/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const esModules = [
"immer",
"redux",
"lodash-es",
"when-clause",
].join("|")
// https://github.com/gravitational/teleport/issues/33810

Expand Down
4 changes: 2 additions & 2 deletions apps/zui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@
"ajv": "^6.9.1",
"animejs": "^3.2.0",
"brimcap": "brimdata/brimcap#v1.18.0",
"bullet": "^0.0.2",
"bullet": "^0.0.7",
"chalk": "^4.1.0",
"chevrotain": "^10.5.0",
"chrono-node": "^2.5.0",
"classnames": "^2.2.6",
"commander": "^2.20.3",
Expand Down Expand Up @@ -159,6 +158,7 @@
"utopia-core-scss": "^1.0.1",
"web-file-polyfill": "^1.0.4",
"web-streams-polyfill": "^3.2.0",
"when-clause": "^0.0.4",
"zed": "brimdata/zed#65b575c5ff9a95c7c2bf7dd9ceb935b1a51d7676",
"zui-test-data": "workspace:*"
},
Expand Down
8 changes: 5 additions & 3 deletions apps/zui/src/app/commands/copy-query-to-clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {copyToClipboard} from "src/js/lib/doc"
import Queries from "src/js/state/Queries"
import {createCommand} from "./command"
import {Snapshot} from "src/models/snapshot"

export const copyQueryToClipboard = createCommand(
"copyQueryToClipboard",
({api, getState}, id: string) => {
const q = Queries.build(getState(), id)
if (q) {
copyToClipboard(q.toString())
const query = Queries.find(getState().queries, id)
if (query) {
const text = new Snapshot(query).queryText
copyToClipboard(text)
api.toast("Copied")
}
}
Expand Down
5 changes: 4 additions & 1 deletion apps/zui/src/app/lakes/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import LakeStatuses from "src/js/state/LakeStatuses"
import styled from "styled-components"
import {invoke} from "src/core/invoke"
import {Active} from "src/models/active"
import {BrowserTab} from "src/models/browser-tab"

const SpinnerWrap = styled.div`
width: 100%;
Expand All @@ -26,7 +27,9 @@ export function InitLake({children}) {

useLayoutEffect(() => {
if (Active.lake) {
dispatch(updateStatus(lake.id))
dispatch(updateStatus(lake.id)).then(() => {
BrowserTab.all.forEach((tab) => tab.updateTitle())
})
Active.lake.sync()
}
}, [lake?.id, status])
Expand Down
4 changes: 2 additions & 2 deletions apps/zui/src/app/menus/open-query-menu.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {MenuItemConstructorOptions} from "electron"
import {Item} from "src/js/state/Queries/types"
import {createMenu} from "src/core/menu"
import {NamedQueries} from "src/domain/handlers"
import {QueriesRunner} from "src/runners/queries-runner"

export const openQueryMenu = createMenu(({api}) => {
function createMenuItems(items: Item[]) {
Expand All @@ -14,7 +14,7 @@ export const openQueryMenu = createMenu(({api}) => {
} else {
return {
label: query.name,
click: () => NamedQueries.show(query.id),
click: () => new QueriesRunner().open(query.id),
}
}
})
Expand Down
4 changes: 2 additions & 2 deletions apps/zui/src/app/menus/pool-toolbar-menu.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Pool} from "../../models/pool"
import {createMenu} from "src/core/menu"
import {Snapshots} from "src/domain/handlers"
import {chooseFiles} from "src/domain/loads/handlers"
import {QuerySession} from "src/models/query-session"

export const poolToolbarMenu = createMenu((_, pool: Pool) => {
return [
Expand All @@ -19,7 +19,7 @@ export const poolToolbarMenu = createMenu((_, pool: Pool) => {
label: "Query Pool",
iconName: "query",
click: () => {
Snapshots.createAndShow({
QuerySession.activateOrCreate().navigate({
pins: [{type: "from", value: pool.name}],
value: "",
})
Expand Down
4 changes: 2 additions & 2 deletions apps/zui/src/app/menus/query-context-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {copyQueryToClipboard} from "../commands/copy-query-to-clipboard"
import {deleteQueries} from "../commands/delete-queries"
import {exportQueryGroup} from "../commands/export-query-group"
import {createMenu} from "src/core/menu"
import {NamedQueries} from "src/domain/handlers"
import {QueriesRunner} from "src/runners/queries-runner"

export const queryContextMenu = createMenu(
(_, tree: TreeApi<Query | Group>, node: NodeApi<Query | Group>) => {
Expand Down Expand Up @@ -32,7 +32,7 @@ export const queryContextMenu = createMenu(
{
label: "Open Query",
visible: node.isLeaf,
click: () => NamedQueries.show(node.id),
click: () => new QueriesRunner().open(node.id),
},
{type: "separator"},
{
Expand Down
21 changes: 4 additions & 17 deletions apps/zui/src/app/router/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,41 @@ import {IconName} from "../../components/icon"
export const root: Route = {
name: "root",
path: "/",
title: "Zui",
}

export const poolShow: Route = {
name: "poolShow",
title: "<pool>",
path: `/pools/:poolId`,
icon: "pool",
}

export const query: Route = {
name: "querySession",
title: "<query>",
path: `/queries/:queryId`,
export const snapshotShow: Route = {
name: "snapshot",
path: "/snapshots/:id",
icon: "query",
}

export const queryVersion: Route = {
name: "querySession",
title: "<query>",
path: `${query.path}/versions/:version`,
icon: "query",
}
export const releaseNotes: Route = {
name: "releaseNotes",
title: "Release Notes",
path: `/release-notes`,
icon: "doc_plain",
}

export const welcome: Route = {
name: "welcome",
title: "Welcome to Zui",
path: "/welcome",
icon: "zui",
}

type Route = {
name: string
title: string
path: string
icon?: IconName
}

export const allRoutes: Route[] = [
poolShow,
query,
queryVersion,
snapshotShow,
releaseNotes,
welcome,
root,
Expand Down
4 changes: 4 additions & 0 deletions apps/zui/src/app/router/utils/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export function queryPath(queryId: string, version: string) {
return `/queries/${queryId}/versions/${version}`
}

export function snapshotPath(id: string) {
return `/snapshots/${id}`
}

export function releaseNotesPath() {
return "/release-notes"
}
Expand Down
1 change: 1 addition & 0 deletions apps/zui/src/components/button-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export function ButtonMenu(props: {
const menu = useResponsiveMenu(items)

const buttons = menu.items.map((item: MenuItem, i: number) => {
if (item.whenResult === false) return null
return (
<IconButton
{...item}
Expand Down
3 changes: 3 additions & 0 deletions apps/zui/src/components/icon-button.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import classNames from "classnames"
import React, {
CSSProperties,
MouseEvent,
MouseEventHandler,
MutableRefObject,
Expand Down Expand Up @@ -54,6 +55,7 @@ const BG = styled.button`
export const IconButton = forwardRef(function IconButton(
props: MenuItem & {
className?: string
style?: CSSProperties
onClick?: MouseEventHandler<HTMLButtonElement>
onMouseDown?: MouseEventHandler<HTMLButtonElement>
buildMenu?: () => MenuItem[]
Expand All @@ -73,6 +75,7 @@ export const IconButton = forwardRef(function IconButton(
<BG
ref={ref}
className={classNames(props.className, props.display)}
style={props.style}
data-tooltip={
props.display === "icon-label" ? null : props.description ?? props.label
}
Expand Down
2 changes: 2 additions & 0 deletions apps/zui/src/components/icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export function Icon(props: Props) {
className={classNames("icon", styles.icon, props.className)}
fill={props.fill || "currentColor"}
style={style}
height={16}
width={16}
>
<use href={path}></use>
</svg>
Expand Down
16 changes: 7 additions & 9 deletions apps/zui/src/core/menu/use-menu-extension.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {useEffect, useLayoutEffect, useState} from "react"
import {MenuItem} from "src/core/menu"
import {compile} from "../when/compile"
import {invoke} from "../invoke"
import {useTabId} from "src/util/hooks/use-tab-id"
import {evaluate} from "when-clause"

export function useMenuExtension(
name: string,
Expand All @@ -13,31 +13,29 @@ export function useMenuExtension(
const tabId = useTabId()

useLayoutEffect(() => {
invoke("menus.extend", name, menuItems)
.then((extended) => compileMenuItems(extended, whenContext))
.then((compiled) => setItems(compiled))
}, [name, menuItems, whenContext, tabId])
invoke("menus.extend", name, menuItems).then((items) => setItems(items))
}, [name, tabId])

useEffect(() => {
return global.zui.on("menus.update", (e, menu, id, update) => {
if (menu !== name) return
setItems(
setItems((items) =>
items.map((item: MenuItem) => {
return item.id === id ? {...item, ...update} : item
})
)
})
}, [items, name])
}, [name])

return items
return compileMenuItems(items, whenContext)
}

function compileMenuItems(items: MenuItem[], context: Record<string, any>) {
return items
.map<MenuItem>((item) => {
return {
...item,
whenResult: compile(item.when, context),
whenResult: item.when && evaluate(item.when, context),
priority: item.priority ?? 0,
}
})
Expand Down
40 changes: 21 additions & 19 deletions apps/zui/src/core/query/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,28 @@ const run = createHandler(
const prevVals = select(Results.getValues(id))
const prevShapes = select(Results.getShapes(id))
const paginatedQuery = select(Results.getPaginatedQuery(id))
const {signal} = asyncTasks.createOrReplace([tabId, id])
try {
const res = await query(paginatedQuery, {signal})
await res.collect(({rows, shapesMap}) => {
const values = isFirstPage ? rows : [...prevVals, ...rows]
const shapes = isFirstPage ? shapesMap : {...prevShapes, ...shapesMap}
dispatch(Results.setValues({id, tabId, values}))
dispatch(Results.setShapes({id, tabId, shapes}))
})
dispatch(Results.success({id, tabId, count: res.rows.length}))
return res
} catch (e) {
if (isAbortError(e)) {
const task = await asyncTasks.createOrReplace([tabId, id])
task.run(async (signal) => {
try {
const res = await query(paginatedQuery, {signal})
await res.collect(({rows, shapesMap}) => {
const values = isFirstPage ? rows : [...prevVals, ...rows]
const shapes = isFirstPage ? shapesMap : {...prevShapes, ...shapesMap}
dispatch(Results.setValues({id, tabId, values}))
dispatch(Results.setShapes({id, tabId, shapes}))
})
dispatch(Results.success({id, tabId, count: res.rows.length}))
return res
} catch (e) {
if (isAbortError(e)) {
return null
} else {
dispatch(
Results.error({id, tabId, error: ErrorFactory.create(e).message})
)
}
return null
} else {
dispatch(
Results.error({id, tabId, error: ErrorFactory.create(e).message})
)
}
return null
}
})
}
)
16 changes: 16 additions & 0 deletions apps/zui/src/core/view-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Dispatch, State, Store} from "src/js/state/types"
import {ipc} from "src/modules/bullet/view"
import {invoke} from "./invoke"
import toast from "react-hot-toast"
import {useEffect} from "react"

type Selector = (state: State, ...args: any) => any

Expand All @@ -26,4 +27,19 @@ export class ViewHandler {
protected request(path: string, params?: object) {
return ipc.request(path, params)
}

protected listen(eventMap: Record<string, any>) {
useEffect(() => {
const offs = []
for (const [event, handler] of Object.entries(eventMap)) {
offs.push(
global.zui?.on(event, (_event, ...args: any[]) => handler(...args))
)
}

return () => {
offs.forEach((off) => off())
}
})
}
}
Loading

0 comments on commit f27e6a2

Please sign in to comment.