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

Zui with No Tabs Open #2797

Merged
merged 40 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8714aab
Add a current reducer to hold current window state
jameskerr Jul 5, 2023
67f36ed
Remove lakeId from urls
jameskerr Jul 7, 2023
ed72e71
Close the window if there are no tabs left and cmd+w
jameskerr Jul 7, 2023
af9b971
Handle case when there are no tabs open
jameskerr Jul 7, 2023
1972fbc
Fix detail window
jameskerr Jul 7, 2023
f89d464
Fix the tabs alignment
jameskerr Jul 7, 2023
15eaab9
Add keys to array
jameskerr Jul 7, 2023
5c86526
Tweek colors
jameskerr Jul 7, 2023
0510b55
Fix log details test to have a tab open.
jameskerr Jul 10, 2023
8e5ea84
Remove the Chart Slice
jameskerr Jul 10, 2023
ba16b8a
Fix other tests
jameskerr Jul 10, 2023
c63cbf0
Lint fix
jameskerr Jul 10, 2023
d4f685c
Merge remote-tracking branch 'origin/main' into no-tabs
jameskerr Jul 10, 2023
fd7d014
Cleanup autoupdater and dont run in tests
jameskerr Jul 10, 2023
0660e41
Start url migration
jameskerr Jul 10, 2023
0fed9cb
Add json state
jameskerr Jul 10, 2023
367846e
Create migrations for urls
jameskerr Jul 10, 2023
1e0ae02
Initialize the lake in the main process
jameskerr Jul 10, 2023
aa7b1df
Migration Guide
jameskerr Jul 10, 2023
9d68705
Migrate the current lake id
jameskerr Jul 10, 2023
7210fcf
Check for histories
jameskerr Jul 10, 2023
3bd6977
Associate tabs with the lake
jameskerr Jul 11, 2023
36636a0
Use a window reducer instead of a current reducer
jameskerr Jul 13, 2023
3073947
Wrote migration to move tabs under lakeids
jameskerr Jul 13, 2023
d8bcfb1
Deprecate the old getAllTabs helper function
jameskerr Jul 13, 2023
5529e7a
Refactor the lakes reducer
jameskerr Jul 13, 2023
89463d6
Fix removeLake
jameskerr Jul 13, 2023
f0d518a
Fix logout
jameskerr Jul 13, 2023
08ba3eb
Remove lakes list and rename setAccessToken
jameskerr Jul 13, 2023
4aae2fb
Update eslint deps to remove warning
jameskerr Jul 13, 2023
07af8fb
Restyle a bit
jameskerr Jul 13, 2023
acd8a15
Update migration to sort tabs into lake groups
jameskerr Jul 14, 2023
f82bb4e
Get better test data
jameskerr Jul 14, 2023
21b41fc
Fix all migrations
jameskerr Jul 14, 2023
12464a9
Fix migration
jameskerr Jul 14, 2023
de91560
Add a little shaddow
jameskerr Jul 14, 2023
6f113cd
Merge remote-tracking branch 'origin/main' into no-tabs
jameskerr Jul 14, 2023
98e9ac9
Fix Loading Workflow
jameskerr Jul 17, 2023
fd6e6de
Fix lint
jameskerr Jul 17, 2023
9e40b99
Updates tests
jameskerr Jul 17, 2023
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
44 changes: 42 additions & 2 deletions CODE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,33 @@

This is here to document the design patterns chosen by the developers. It documents structures, abstractions, and philosophy in this repo.

## Dependencies

**All dependencies should be installed as development.** This app is unlike traditional web or node apps. All dependencies should be development dependencies unless esbuild cannot bundle it. The reason: before we package the app, we bundle all the code into a single file. The bundled JavaScript has no need to look into node_modules because all dependencies are already included in the bundle. When we package the app, it will include all the production dependencies in the app package. So, since we bundle most ourselves, there's no need to have duplicate packages in the packaged app's node_modules.

## FAQs

How do I add a main process initializer?
### How do I add a main process initializer?

1. Create a new file in `src/electron/initializers/`.
2. Add the following line to `src/electron/initializers/index.ts`.
2. Export a function called _initialize_(main) that takes the main object as its only argument.
3. Add the following line to `src/electron/initializers/index.ts`.

```
export * as myNewInitializer from "./my-new-initializer"
```

Export all symbols as a camel cased alias of the file name. This will now run automatically when the app starts.

### How do I write a state migration?

1. Run `bin/gen migration my_migration_name`.
2. Edit the files it produced to perform your migration.
3. Use the getAllTabs and getAllStates helpers as needed.
4. Remember that the states are either the main process state or the window states.
5. Add that file to the src/js/state/migrations/index.ts following the pattern there.
6. Create a sample state, if needed, by running the app at the latest released version, getting it into the state you want, then copying run/appState.json into src/test/unit/states/v0.0.0.json using the version as the file name.

## Folders

Documentation for where code should go.
Expand Down Expand Up @@ -66,3 +80,29 @@ The plugin api runs in the Node main process and is given to plugin authors to e
_Main Process Initializers_

Code that needs to be run one time before the app starts up can be put in an initializer. An initializer is a file that lives in the folder `src/electron/initializers/`. It must export a function named _initialize(main)_ that takes the Main Object as its only argument. See the FAQ for an example of creating a new initializer.

_Query_

A query in the app is like a container object. It holds the name and id of the query. It does not contain the zed code. Those are stored in a QueryVersion. Each Query has many QueryVersions, showing the history of that query.

_Session Query_

A session query is like an unnamed Query. Each session (tab) has exactly one SessionQuery associated with it. The SessionQuery has many QueryVersions associated with it.

_Store_

The store contains the state for the whole application. Parts of the store apply to the whole app (main process and all windows), like the list of lakes, the list of the pools, the list of queries, and the configurations. Then state only relevant to one window, then state that's only relative to the tab.

**State Hierarchy**

1. Application Level
2. Window Level
3. Tab Level

Application level state has a `$` prefix to the action names. Actions dispatched with the `$` prefix get dispatched to the main process and all windows.

Window level state is everything that's not in the `tabReducer`, but doesn't have a `$` prefix.

Tabs state is found in the `tabReducer` function.

The tabs are grouped by lakeId within the window state. Each window has a different group of tabs per lakeId. When a user switches lakes, the tabs from the previous lake will be hidden and the tabs from the current lake shown. When switching back, the old tabs will be restored.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Zed User Interface",
"repository": "https://github.com/brimdata/zui",
"license": "BSD-3-Clause",
"version": "1.0.1",
"version": "1.1.0",
"main": "dist/main.js",
"author": "Brim Data <support@brimdata.io> (http://www.brimdata.io)",
"workspaces": [
Expand Down Expand Up @@ -83,8 +83,8 @@
"@types/sprintf-js": "^1.1.2",
"@types/styled-components": "^5.1.3",
"@types/tmp": "^0.2.0",
"@typescript-eslint/eslint-plugin": "5.60.1",
"@typescript-eslint/parser": "5.60.1",
"@typescript-eslint/eslint-plugin": "6.0.0",
"@typescript-eslint/parser": "6.0.0",
"abort-controller": "^3.0.0",
"acorn": "^7.4.1",
"ajv": "^6.9.1",
Expand All @@ -111,7 +111,7 @@
"esbuild": "^0.17.18",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-jest": "^26.1.2",
"eslint-plugin-jest": "^27.2.3",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"event-source-polyfill": "^1.0.25",
Expand Down
2 changes: 2 additions & 0 deletions pages/detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {Modals} from "src/js/components/Modals"
import Tooltip from "src/js/components/Tooltip"
import initialize from "src/js/initializers/initialize"
import TabHistories from "src/js/state/TabHistories"
import Tabs from "src/js/state/Tabs"
import {getPersistedWindowState} from "src/js/state/stores/get-persistable"

export default function DetailPage() {
const [app, setApp] = useState(null)

useEffect(() => {
initialize().then((vars) => {
vars.store.dispatch(Tabs.create()) // Make a "tab" so that selectors work
window.onbeforeunload = () => {
vars.api.abortables.abortAll()
vars.store.dispatch(TabHistories.save(global.tabHistories.serialize()))
Expand Down
4 changes: 2 additions & 2 deletions src/app/commands/new-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import Tabs from "src/js/state/Tabs"
import {newPoolPath} from "../router/utils/paths"
import {createCommand} from "./command"

export const newPool = createCommand({id: "newPool"}, ({api, dispatch}) => {
dispatch(Tabs.activateUrl(newPoolPath(api.current.lakeId)))
export const newPool = createCommand({id: "newPool"}, ({dispatch}) => {
dispatch(Tabs.activateUrl(newPoolPath()))
})
11 changes: 8 additions & 3 deletions src/app/commands/pins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import submitSearch from "../query-home/flows/submit-search"
import {createCommand} from "./command"
import Current from "src/js/state/Current"
import PoolSettings from "src/js/state/PoolSettings"
import Tabs from "src/js/state/Tabs"

export const createFromEditor = createCommand(
"pins.createFromEditor",
Expand Down Expand Up @@ -40,9 +41,13 @@ export const createFrom = createCommand<[value?: string]>(

export const updateFrom = createCommand(
"pins.updateFrom",
({dispatch}, value: string) => {
dispatch(Editor.setFrom(value))
dispatch(submitSearch())
({dispatch, getState, api}, value: string) => {
if (Tabs.none(getState())) {
api.queries.open({pins: [{type: "from", value}], value: ""})
} else {
dispatch(Editor.setFrom(value))
dispatch(submitSearch())
}
}
)

Expand Down
11 changes: 4 additions & 7 deletions src/app/commands/pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import errors from "src/js/errors"
import {ErrorData} from "src/js/errors/types"
import ErrorFactory from "src/js/models/ErrorFactory"
import {PoolName} from "../features/sidebar/pools-section/pool-name"
import {lakePoolPath} from "../router/utils/paths"
import {createCommand} from "./command"
import {deletePools} from "./delete-pools"
import {invoke} from "src/core/invoke"
Expand Down Expand Up @@ -61,13 +60,13 @@ export const rename = createCommand(
export const deleteGroup = createCommand(
"pools.deleteGroup",
({api}, group: string[]) => {
const decendentIds = api.pools.all
const descendantIds = api.pools.all
.filter((pool) => {
return new PoolName(pool.name, api.pools.nameDelimiter).isIn(group)
})
.map((pool) => pool.id)

return deletePools.run(decendentIds)
return deletePools.run(descendantIds)
}
)

Expand All @@ -79,8 +78,6 @@ export const createAndLoadFiles = createCommand(
opts: {name?: string; format?: LoadFormat} & Partial<CreatePoolOpts> = {}
) => {
let poolId: string | null = null
const lakeId = api.current.lakeId
const tabId = api.current.tabId
const poolNames = api.pools.all.map((p) => p.name)
if (!opts.name && files.length === 0) {
api.toast("No pool name and no files provided.")
Expand All @@ -101,14 +98,14 @@ export const createAndLoadFiles = createCommand(
error: "Load error",
})
await promise
return poolId
}

api.url.push(lakePoolPath(poolId, lakeId), {tabId})
} catch (e) {
console.error(e)
if (poolId) await api.pools.delete(poolId)
api.notice.error(parseError(e))
api.pools.syncAll()
throw e
}
}
)
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/models/zed-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class ZedAst {
if (!from) return null
const trunk = from.trunks.find((t) => t.source.kind === "Pool")
if (!trunk) return null
const name = trunk.source.spec.pool.text
const name = trunk.source.spec.pool?.text
if (!name) return null
return name
}
Expand Down
11 changes: 2 additions & 9 deletions src/app/detail/NoSelection.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import React from "react"
import {EmptyText} from "../features/right-pane/common"

const NoSelection = () => (
<div className="empty-message-wrapper">
<div className="empty-message">
<h3>No Log Selected</h3>
<p>Click a log line to view details.</p>
<p>
Toggle this pane with <code>Cmd + ]</code>.
</p>
</div>
</div>
<EmptyText>Select a value in the results to view details.</EmptyText>
)

export default NoSelection
6 changes: 5 additions & 1 deletion src/app/features/right-pane/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ export const PaneHeader = styled.header`

export const EmptyText = styled.p`
padding: 24px;
margin-top: 33%;
opacity: 0.5;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
`
2 changes: 1 addition & 1 deletion src/app/features/right-pane/history/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const Circle = styled.div<{color: string}>`
const Line = styled.div`
width: 2px;
flex: 1;
background: #f6f6f7;
background: var(--border-color);

&:first-child {
border-radius: 0 0 1px 1px;
Expand Down
19 changes: 10 additions & 9 deletions src/app/features/right-pane/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import {HistorySection} from "./history/section"
import {SectionTabs} from "src/components/section-tabs"
import {PaneName} from "src/js/state/Layout/types"
import {ColumnsPane} from "src/panes/columns-pane/columns-pane"
import Appearance from "src/js/state/Appearance"

const Pane = styled(DraggablePane)`
display: flex;
flex-direction: column;
border-left: 1px solid var(--border-color);
background: white;
background: var(--chrome-color);
`

const PaneContentSwitch = ({paneName}) => {
Expand All @@ -43,19 +44,19 @@ const BG = styled.div`
padding: 0 8px;
`

export function Menu() {
export function Menu(props: {paneName: string}) {
const dispatch = useDispatch()
const currentPaneName = useSelector(Layout.getCurrentPaneName)

const onChange = (name: string) => {
if (name === currentPaneName) return
if (name === props.paneName) return
dispatch(Layout.setCurrentPaneName(name as PaneName))
}

function makeOption(label: string, value: string) {
return {
label,
click: () => onChange(value),
checked: currentPaneName === value,
checked: props.paneName === value,
}
}

Expand All @@ -74,14 +75,14 @@ export function Menu() {
}

function Container({children}) {
const width = useSelector(Layout.getDetailPaneWidth)
const dispatch = useDispatch()
const isOpen = useSelector(Layout.getDetailPaneIsOpen)
const width = useSelector(Appearance.secondarySidebarWidth)
const isOpen = useSelector(Appearance.secondarySidebarIsOpen)

const onDrag = (e: React.MouseEvent) => {
const width = window.innerWidth - e.clientX
const max = window.innerWidth
dispatch(Layout.setDetailPaneWidth(Math.min(width, max)))
dispatch(Appearance.resizeSecondarySidebar(Math.min(width, max)))
}

if (!isOpen) return null
Expand All @@ -99,7 +100,7 @@ const RightPane = () => {

return (
<Container>
<Menu />
<Menu paneName={currentPaneName} />
<AppErrorBoundary>
<PaneContentSwitch paneName={currentPaneName} />
</AppErrorBoundary>
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/right-pane/versions-section.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ beforeEach(async () => {
system.store.dispatch(Queries.addItem({id: testQueryId, name: "test query"}))
system.store.dispatch(QueryVersions.at(testQueryId).create(testVersion1))
system.store.dispatch(QueryVersions.at(testQueryId).create(testVersion2))
system.navTo(lakeQueryPath(testQueryId, "testLakeId", testVersion2.version))
system.navTo(lakeQueryPath(testQueryId, testVersion2.version))
system.render(<VersionsSection />)
await screen.findAllByText(/test value/i)
})
Expand Down
3 changes: 1 addition & 2 deletions src/app/features/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ const PaneSwitch = ({name}) => {

const Pane = styled(DraggablePane)`
height: 100%;
width: 100%;
background: var(--sidebar-background);
overflow-x: unset;
grid-area: sidebar;
border-right: 1px solid var(--border-color-dark);
display: flex;
flex-direction: column;
`
Expand All @@ -58,7 +58,6 @@ export function Sidebar() {
const isOpen = useSelector(Appearance.sidebarIsOpen)
const currentSectionName = useSelector(Appearance.getCurrentSectionName)
const l = useSelector(Current.getLake)

const id = get(l, ["id"], "")
function onDragPane(e: MouseEvent) {
const width = e.clientX
Expand Down
5 changes: 2 additions & 3 deletions src/app/features/sidebar/lake-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React from "react"
import useLakeId from "src/app/router/hooks/use-lake-id"
import tabHistory from "src/app/router/tab-history"
import {newPoolPath} from "src/app/router/utils/paths"
import {MenuItemConstructorOptions} from "electron"
import {useDispatch, useSelector} from "react-redux"
import styled from "styled-components"
Expand All @@ -13,6 +11,7 @@ import {AppDispatch} from "src/js/state/types"
import Lakes from "src/js/state/Lakes"
import {Lake} from "src/js/state/Lakes/types"
import lake from "src/js/models/lake"
import Window from "src/js/state/Window"

const LakeNameGroup = styled.div`
display: flex;
Expand Down Expand Up @@ -82,7 +81,7 @@ const showLakeSelectMenu = () => (dispatch, getState) => {
checked: isCurrent,
click: () => {
if (isCurrent) return
dispatch(tabHistory.push(newPoolPath(l.id)))
dispatch(Window.setLakeId(l.id))
},
})
})
Expand Down
14 changes: 8 additions & 6 deletions src/app/features/sidebar/pools-section/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import {useFilesDrop} from "src/util/hooks/use-files-drop"
import {createAndLoadFiles} from "src/app/commands/pools"
import {useDispatch} from "src/app/core/state"
import Tabs from "src/js/state/Tabs"
import {useZuiApi} from "src/app/core/context"
import {lakePath} from "src/app/router/utils/paths"
import {lakePoolPath} from "src/app/router/utils/paths"

const PoolsSection = () => {
const dispatch = useDispatch()
const api = useZuiApi()
const [{isOver}, drop] = useFilesDrop({
onDrop: (files) => {
dispatch(Tabs.activateUrl(lakePath(api.current.lakeId)))
createAndLoadFiles.run(files.map((f) => f.path))
onDrop: async (files) => {
try {
const poolId = await createAndLoadFiles.run(files.map((f) => f.path))
dispatch(Tabs.activateUrl(lakePoolPath(poolId)))
} catch (e) {
// Handled
}
},
})
const [searchTerm, setSearchTerm] = useState("")
Expand Down
Loading
Loading