-
Notifications
You must be signed in to change notification settings - Fork 965
feat: unified WS event bus, v2Hosts data model, real diff stats #3224
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
Changes from all commits
7214d06
7afe516
6e2584d
f9f443f
4d28b35
822dd91
d00e769
e2c5324
f84c6e5
b1755da
3614f5c
1aeccdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { type DiffStats, useDiffStats } from "./useDiffStats"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { useCallback, useEffect, useState } from "react"; | ||
| import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; | ||
| import { useWorkspaceEvent } from "../useWorkspaceEvent"; | ||
| import { useWorkspaceHostUrl } from "../useWorkspaceHostUrl"; | ||
|
|
||
| export interface DiffStats { | ||
| additions: number; | ||
| deletions: number; | ||
| } | ||
|
|
||
| /** | ||
| * Fetches diff stats for a single workspace, auto-updates on git changes. | ||
| * Just pass the workspaceId — host resolution is handled internally. | ||
| */ | ||
| export function useDiffStats(workspaceId: string): DiffStats | null { | ||
| const [stats, setStats] = useState<DiffStats | null>(null); | ||
| const hostUrl = useWorkspaceHostUrl(workspaceId); | ||
|
|
||
| const fetchStats = useCallback(async () => { | ||
| if (!hostUrl) return; | ||
| try { | ||
| const client = getHostServiceClientByUrl(hostUrl); | ||
| const status = await client.git.getStatus.query({ workspaceId }); | ||
|
|
||
| // Deduplicate by path — a file can appear in multiple categories | ||
| const byPath = new Map< | ||
| string, | ||
| { additions: number; deletions: number } | ||
| >(); | ||
| for (const file of status.againstBase) { | ||
| byPath.set(file.path, file); | ||
| } | ||
| for (const file of status.staged) { | ||
| byPath.set(file.path, file); | ||
| } | ||
| for (const file of status.unstaged) { | ||
| byPath.set(file.path, file); | ||
| } | ||
|
|
||
| let additions = 0; | ||
| let deletions = 0; | ||
| for (const file of byPath.values()) { | ||
| additions += file.additions; | ||
| deletions += file.deletions; | ||
| } | ||
|
|
||
| setStats({ additions, deletions }); | ||
| } catch { | ||
| // Host unavailable or workspace deleted | ||
| } | ||
| }, [hostUrl, workspaceId]); | ||
|
|
||
| useEffect(() => { | ||
| void fetchStats(); | ||
| }, [fetchStats]); | ||
|
|
||
| useWorkspaceEvent("git:changed", workspaceId, () => { | ||
| void fetchStats(); | ||
| }); | ||
|
|
||
| return stats; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { useWorkspaceEvent } from "./useWorkspaceEvent"; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,65 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getEventBus } from "@superset/workspace-client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { FsWatchEvent } from "@superset/workspace-fs/client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useEffectEvent } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getHostServiceWsToken } from "renderer/lib/host-service-auth"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useWorkspaceHostUrl } from "../useWorkspaceHostUrl"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Subscribe to an event bus event for a workspace. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Resolves the workspace's host and connects to the correct event bus automatically. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function useWorkspaceEvent( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "git:changed", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| callback: () => void, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| enabled?: boolean, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function useWorkspaceEvent( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "fs:events", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| callback: (event: FsWatchEvent) => void, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| enabled?: boolean, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function useWorkspaceEvent( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "git:changed" | "fs:events", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| callback: ((event: FsWatchEvent) => void) | (() => void), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| enabled = true, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hostUrl = useWorkspaceHostUrl(workspaceId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handler = useEffectEvent(callback); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!enabled || !hostUrl) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const bus = getEventBus(hostUrl, () => getHostServiceWsToken(hostUrl)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const cleanups: Array<() => void> = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (type === "fs:events") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bus.watchFs(workspaceId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const removeListener = bus.on( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fs:events", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspaceId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| (_wid, payload) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const event of payload.events) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| (handler as (event: FsWatchEvent) => void)(event); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cleanups.push(removeListener, () => bus.unwatchFs(workspaceId)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+39
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify event-bus fs watch/listener semantics (read-only).
# Expected: confirm whether watchFs sends subscription immediately and whether inbound fs events are buffered or only dispatched to current listeners.
set -euo pipefail
FILE="packages/workspace-client/src/lib/eventBus.ts"
echo "== watch/unwatch and listener APIs =="
rg -n -C4 'watchFs\s*\(|unwatchFs\s*\(|\bon\s*\(' "$FILE"
echo
echo "== inbound ws message dispatch paths =="
rg -n -C5 'onmessage|message|emit|dispatch|listeners|fs:events|git:changed' "$FILE"Repository: superset-sh/superset Length of output: 4748 Attach the fs listener before calling watchFs to prevent dropped events. At line 44, Suggested fix if (type === "fs:events") {
- bus.watchFs(workspaceId);
const removeListener = bus.on(
"fs:events",
workspaceId,
(_wid, payload) => {
for (const event of payload.events) {
(handler as (event: FsWatchEvent) => void)(event);
}
},
);
- cleanups.push(removeListener, () => bus.unwatchFs(workspaceId));
+ cleanups.push(removeListener);
+ bus.watchFs(workspaceId);
+ cleanups.push(() => bus.unwatchFs(workspaceId));
} else {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const removeListener = bus.on("git:changed", workspaceId, () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| (handler as () => void)(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cleanups.push(removeListener); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| cleanups.push(bus.retain()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const cleanup of cleanups) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| cleanup(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [enabled, hostUrl, type, workspaceId]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { useWorkspaceHostUrl } from "./useWorkspaceHostUrl"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import { eq } from "@tanstack/db"; | ||
| import { useLiveQuery } from "@tanstack/react-db"; | ||
| import { useMemo } from "react"; | ||
| import { getRemoteHostUrl } from "renderer/lib/v2-workspace-host"; | ||
| import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; | ||
| import { useHostService } from "renderer/routes/_authenticated/providers/HostServiceProvider"; | ||
|
|
||
| /** | ||
| * Resolves a workspace ID to its host-service URL. | ||
| * Local host → localhost port. Remote host → relay proxy URL. | ||
| */ | ||
| export function useWorkspaceHostUrl(workspaceId: string): string | null { | ||
| const collections = useCollections(); | ||
| const { services } = useHostService(); | ||
|
|
||
| const { data: workspaceWithHost = [] } = useLiveQuery( | ||
| (q) => | ||
| q | ||
| .from({ workspaces: collections.v2Workspaces }) | ||
| .innerJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => | ||
| eq(workspaces.hostId, hosts.id), | ||
| ) | ||
| .where(({ workspaces }) => eq(workspaces.id, workspaceId)) | ||
| .select(({ workspaces, hosts }) => ({ | ||
| hostId: workspaces.hostId, | ||
| hostOrgId: hosts.organizationId, | ||
| })), | ||
| [collections, workspaceId], | ||
| ); | ||
|
|
||
| const match = workspaceWithHost[0] ?? null; | ||
|
|
||
| return useMemo(() => { | ||
| if (!match) return null; | ||
| const localService = services.get(match.hostOrgId); | ||
| if (localService) return localService.url; | ||
| return getRemoteHostUrl(match.hostId); | ||
| }, [match, services]); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.