Skip to content

Commit

Permalink
feat(toolbar): Refactor toolbar and enhance functionality (#1219)
Browse files Browse the repository at this point in the history
Significant overhaul of debug toolbar, using [themed RadixUI
components](https://www.radix-ui.com/themes/docs/overview/getting-started)
and [Glide Data Grid](https://grid.glideapps.com/) to minimize project
complexity on our side.

Toolbar now offers 5 tabs:
- **Connection** - inspect connection status and toggle it on and off
- **Local DB** - view stored tables and their schemas and allow
resetting local DB
- **Shapes** - this needs work and is currently a bit hacky but gives an
overview of active shape subscriptions
- **Inspect Tables** - inspect contents of local tables (including
internal ones), gets updated live
- **Shell** - execute queries and view them in either table or JSON
format, ultimate debug tool

Attaching some screenshots:

<img width="797" alt="Screenshot 2024-05-08 at 16 17 01"
src="https://github.com/electric-sql/electric/assets/12274098/f75759d7-0b11-4e58-b188-b1743bd133b7">
<img width="792" alt="Screenshot 2024-05-08 at 16 17 08"
src="https://github.com/electric-sql/electric/assets/12274098/4955ec19-ecda-4e0b-bb2e-084288cfec44">
<img width="799" alt="Screenshot 2024-05-08 at 16 17 14"
src="https://github.com/electric-sql/electric/assets/12274098/a3b0fd7a-f15d-47aa-9371-c9debbcb8e98">
<img width="797" alt="Screenshot 2024-05-08 at 16 17 20"
src="https://github.com/electric-sql/electric/assets/12274098/99955bc3-c9c6-42ba-b01e-5b43a6444b57">
<img width="799" alt="Screenshot 2024-05-08 at 16 17 31"
src="https://github.com/electric-sql/electric/assets/12274098/a0f28dec-a9af-4ad0-8fbd-1e965c6f642b">



P.S. with the recent release of PG on the client and PGlite support I'll
have to also introduce multi-dialect support to this but I'll do it in a
separate PR, for now it's SQLite focused.
  • Loading branch information
msfstef authored and alco committed May 19, 2024
1 parent d4f92ab commit 48c081b
Show file tree
Hide file tree
Showing 24 changed files with 6,937 additions and 7,490 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-flowers-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@electric-sql/debug-toolbar": minor
---

Complete restyling of toolbar and new features (table inspector, shape inspector)
4 changes: 2 additions & 2 deletions .github/workflows/toolbar_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
node-version: 18
cache: pnpm
- run: make deps
- run: pnpm run check-styleguide
- run: make check_styleguide
check_types:
name: Check types
runs-on: ubuntu-latest
Expand All @@ -41,7 +41,7 @@ jobs:
node-version: 18
cache: pnpm
- run: make deps
- run: pnpm run typecheck
- run: make check_types
test:
runs-on: ubuntu-latest
strategy:
Expand Down
2 changes: 1 addition & 1 deletion clients/typescript/src/drivers/expo-sqlite/mock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Row } from '../../util/types'
import { Query, ResultSet, SQLiteCallback } from 'expo-sqlite/src/SQLite.types'
import { type Query, type ResultSet, type SQLiteCallback } from 'expo-sqlite'
import { Database } from './database'

export class MockDatabase implements Database {
Expand Down
20 changes: 20 additions & 0 deletions components/toolbar/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module.exports = {
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
extends: [
'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react
'plugin:@typescript-eslint/recommended', // Uses the recommended rules from @typescript-eslint/eslint-plugin
],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
rules: {},
settings: {
react: {
version: 'detect',
},
},
}
9 changes: 8 additions & 1 deletion components/toolbar/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ deps:
pnpm install --frozen-lockfile
make -C ../../clients/typescript build

build: deps
build:
deps
pnpm run build

check_types:
pnpm run typecheck

check_styleguide:
pnpm run check-styleguide

tests:
CI=true pnpm run test
42 changes: 0 additions & 42 deletions components/toolbar/builder.js

This file was deleted.

32 changes: 22 additions & 10 deletions components/toolbar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,50 @@
"version": "1.0.2",
"type": "module",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"author": "ElectricSQL",
"license": "Apache-2.0",
"scripts": {
"build": "rm -rf ./dist && node builder.js && tsc -p tsconfig.build.json",
"check-styleguide": "prettier --check --loglevel warn .",
"build": "rm -rf ./dist && vite build && tsc -p tsconfig.build.json",
"check-styleguide": "prettier --check --loglevel warn . && eslint src --quiet",
"typecheck": "tsc --noEmit",
"prepublishOnly": "pnpm build",
"test": "vitest run"
},
"engines": {
"node": ">=16.11.0"
},
"dependencies": {
"@glideapps/glide-data-grid": "^6.0.4-alpha8",
"@radix-ui/themes": "^3.0.3",
"clsx": "^2.1.1",
"codemirror": "^5.65.16",
"react": "^18.2.0",
"lodash": "^4.17.21",
"marked": "^4.0.10",
"react": "^18.3.1",
"react-codemirror2": "^8.0.0",
"react-dom": "^18.2.0"
"react-dom": "^18.3.1",
"react-responsive-carousel": "^3.2.23",
"sql-formatter": "^15.3.1"
},
"devDependencies": {
"@types/better-sqlite3": "7.6.3",
"@types/node": "^20.12.7",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@vitejs/plugin-react": "^4.2.1",
"better-sqlite3": "^8.4.0",
"electric-sql": "workspace:*",
"esbuild": "^0.20.2",
"esbuild-plugin-inline-image": "^0.0.9",
"esbuild-plugin-inline-import": "^1.0.4",
"eslint": "^8.22.0",
"eslint-plugin-react": "^7.34.1",
"jsdom": "24.0.0",
"prettier": "3.2.5",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vite-plugin-css-injected-by-js": "^3.5.1",
"vitest": "^1.5.0"
},
"peerDependencies": {
Expand Down
25 changes: 24 additions & 1 deletion components/toolbar/src/api/interface.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { Shape } from 'electric-sql/satellite'
import { Row, Statement, ConnectivityState } from 'electric-sql/util'

export type UnsubscribeFunction = () => void

export type DebugShape = Shape & { id: string }

export interface TableColumn {
name: string
type: 'NULL' | 'INTEGER' | 'REAL' | 'TEXT' | 'BLOB'
}

export interface DbTableInfo {
name: string
sql: string
columns: TableColumn[]
}

export interface ToolbarInterface {
getSatelliteNames(): string[]
getSatelliteStatus(name: string): ConnectivityState | null
Expand All @@ -12,8 +26,17 @@ export interface ToolbarInterface {

toggleSatelliteStatus(name: string): Promise<void>

getSatelliteShapeSubscriptions(name: string): string[]
getSatelliteShapeSubscriptions(name: string): DebugShape[]

resetDb(dbName: string): Promise<void>
queryDb(dbName: string, statement: Statement): Promise<Row[]>

getDbTables(dbName: string): Promise<DbTableInfo[]>
getElectricTables(dbName: string): Promise<DbTableInfo[]>

subscribeToDbTable(
dbName: string,
tableName: string,
callback: () => void,
): UnsubscribeFunction
}
99 changes: 92 additions & 7 deletions components/toolbar/src/api/toolbar.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { ToolbarInterface, UnsubscribeFunction } from './interface'
import {
DbTableInfo,
DebugShape,
TableColumn,
ToolbarInterface,
UnsubscribeFunction,
} from './interface'
import { Row, Statement, ConnectivityState } from 'electric-sql/util'
import { Registry, GlobalRegistry, Satellite } from 'electric-sql/satellite'
import {
Registry,
GlobalRegistry,
Satellite,
Shape,
} from 'electric-sql/satellite'
import { SubscriptionsManager } from 'electric-sql/satellite/shapes'

export class Toolbar implements ToolbarInterface {
Expand Down Expand Up @@ -48,15 +59,19 @@ export class Toolbar implements ToolbarInterface {
return sat.connectWithBackoff()
}

getSatelliteShapeSubscriptions(name: string): string[] {
getSatelliteShapeSubscriptions(name: string): DebugShape[] {
const sat = this.getSatellite(name)
//@ts-expect-error accessing private field
const manager = sat['subscriptions'] as SubscriptionsManager
const shapes = JSON.parse(manager.serialize()) as Record<string, any>
const shapes = JSON.parse(manager.serialize()) as Record<
string,
{ definition: Shape }[]
>
return Object.entries(shapes).flatMap((shapeKeyDef) =>
shapeKeyDef[1].map((x: any) =>
JSON.stringify({ id: shapeKeyDef[0], ...x.definition }, null, 2),
),
shapeKeyDef[1].map((x) => ({
id: shapeKeyDef[0],
...x.definition,
})),
)
}

Expand All @@ -76,4 +91,74 @@ export class Toolbar implements ToolbarInterface {
const sat = this.getSatellite(dbName)
return sat.adapter.query(statement)
}

async getDbTables(dbName: string): Promise<DbTableInfo[]> {
const adapter = this.getSatellite(dbName).adapter
const tables = (await adapter.query({
sql: `
SELECT name, sql FROM sqlite_master WHERE type='table'
AND name NOT LIKE 'sqlite_%'
AND name NOT LIKE '_electric_%'`,
})) as unknown as Omit<DbTableInfo, 'columns'>[]

return Promise.all(
tables.map(async (tbl) => ({
...tbl,
columns: await this.getTableColumns(dbName, tbl.name),
})),
)
}

async getElectricTables(dbName: string): Promise<DbTableInfo[]> {
const adapter = this.getSatellite(dbName).adapter
const tables = (await adapter.query({
sql: `
SELECT name, sql FROM sqlite_master WHERE type='table'
AND name LIKE '_electric_%'`,
})) as unknown as Omit<DbTableInfo, 'columns'>[]

return Promise.all(
tables.map(async (tbl) => ({
...tbl,
columns: await this.getTableColumns(dbName, tbl.name),
})),
)
}

subscribeToDbTable(
dbName: string,
tableName: string,
callback: () => void,
): UnsubscribeFunction {
const sat = this.getSatellite(dbName)
const unsubscribe = sat.notifier.subscribeToDataChanges((notification) => {
if (notification.dbName !== dbName) return
for (const change of notification.changes) {
if (
change.qualifiedTablename.tablename === tableName ||
// always trigger an update if subscribing to internal tables
tableName.startsWith('_electric')
) {
callback()
return
}
}
})

return unsubscribe
}

private async getTableColumns(
dbName: string,
tableName: string,
): Promise<TableColumn[]> {
const adapter = this.getSatellite(dbName).adapter
const columns = await adapter.query({
sql: `PRAGMA table_info(${tableName})`,
})
return columns.map((c) => ({
name: c.name,
type: c.type,
})) as TableColumn[]
}
}

0 comments on commit 48c081b

Please sign in to comment.