diff --git a/apps/desktop/package.json b/apps/desktop/package.json index b5a1a256482..44a226bf84c 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -98,6 +98,8 @@ "@t3-oss/env-core": "^0.13.8", "@tanstack/db": "0.6.5", "@tanstack/electric-db-collection": "0.3.3", + "@tanstack/electron-db-sqlite-persistence": "0.1.9", + "@tanstack/node-db-sqlite-persistence": "0.1.9", "@tanstack/react-db": "0.1.83", "@tanstack/react-query": "^5.90.19", "@tanstack/react-router": "^1.147.3", diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 3c0eabe04c6..d300f08b7bf 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -33,6 +33,10 @@ import { loadWebviewBrowserExtension } from "./lib/extensions"; import { getHostServiceCoordinator } from "./lib/host-service-coordinator"; import { localDb } from "./lib/local-db"; import { requestLocalNetworkAccess } from "./lib/local-network-permission"; +import { + initTanstackDbPersistence, + shutdownTanstackDbPersistence, +} from "./lib/persistence/persistence"; import { ensureProjectIconsDir, getProjectIconPath } from "./lib/project-icons"; import { initSentry } from "./lib/sentry"; import { @@ -207,6 +211,7 @@ app.on("before-quit", async (event) => { isQuitting = true; try { getHostServiceCoordinator().releaseAll(); + shutdownTanstackDbPersistence(); disposeTray(); } catch (error) { console.error("[main] Cleanup during quit failed:", error); @@ -345,6 +350,7 @@ if (!gotTheLock) { setWorkspaceDockIcon(); initSentry(); await initAppState(); + initTanstackDbPersistence(); await loadWebviewBrowserExtension(); diff --git a/apps/desktop/src/main/lib/persistence/persistence.ts b/apps/desktop/src/main/lib/persistence/persistence.ts new file mode 100644 index 00000000000..fb2a34d4afc --- /dev/null +++ b/apps/desktop/src/main/lib/persistence/persistence.ts @@ -0,0 +1,26 @@ +import { join } from "node:path"; +import { exposeElectronSQLitePersistence } from "@tanstack/electron-db-sqlite-persistence/main"; +import { createNodeSQLitePersistence } from "@tanstack/node-db-sqlite-persistence"; +import Database from "better-sqlite3"; +import { ipcMain } from "electron"; +import { + ensureSupersetHomeDirExists, + SUPERSET_HOME_DIR, +} from "../app-environment"; + +let dispose: (() => void) | null = null; +let database: Database.Database | null = null; + +export function initTanstackDbPersistence(): void { + ensureSupersetHomeDirExists(); + database = new Database(join(SUPERSET_HOME_DIR, "tanstack-db.sqlite")); + const persistence = createNodeSQLitePersistence({ database }); + dispose = exposeElectronSQLitePersistence({ ipcMain, persistence }); +} + +export function shutdownTanstackDbPersistence(): void { + dispose?.(); + dispose = null; + database?.close(); + database = null; +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx index 5062a4599fd..9611eef13ef 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx @@ -1,4 +1,3 @@ -import { Spinner } from "@superset/ui/spinner"; import { useLiveQuery } from "@tanstack/react-db"; import { useNavigate } from "@tanstack/react-router"; import { useCallback, useEffect, useRef, useState } from "react"; @@ -78,7 +77,7 @@ export function TasksView({ storeSetSearch(searchQuery); }, [searchQuery, storeSetSearch]); - const { data: integrations, isLoading: isCheckingLinear } = useLiveQuery( + const { data: integrations } = useLiveQuery( (q) => q .from({ integrationConnections: collections.integrationConnections }) @@ -134,7 +133,7 @@ export function TasksView({ }); }; - const showLinearCTA = !isCheckingLinear && !isLinearConnected; + const showLinearCTA = integrations !== undefined && !isLinearConnected; return (
@@ -153,11 +152,7 @@ export function TasksView({ /> )} - {isCheckingLinear ? ( -
- -
- ) : showLinearCTA ? ( + {showLinearCTA ? ( ) : viewMode === "board" ? ( - -
- ); - } - if (data.length === 0) { return (
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TableContent/TableContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TableContent/TableContent.tsx index f2425da20b1..b49262bc15e 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TableContent/TableContent.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TableContent/TableContent.tsx @@ -1,4 +1,3 @@ -import { Spinner } from "@superset/ui/spinner"; import { useCallback, useEffect, useMemo } from "react"; import { HiCheckCircle } from "react-icons/hi2"; import type { TaskWithStatus } from "../../hooks/useTasksData"; @@ -25,7 +24,7 @@ export function TableContent({ onTaskClick, onSelectionChange, }: TableContentProps) { - const { table, isLoading, slugColumnWidth, rowSelection, setRowSelection } = + const { table, slugColumnWidth, rowSelection, setRowSelection } = useTasksTable({ filterTab, searchQuery, @@ -44,14 +43,6 @@ export function TableContent({ onSelectionChange?.(selectedTasks, clearSelection); }, [selectedTasks, clearSelection, onSelectionChange]); - if (isLoading) { - return ( -
- -
- ); - } - if (table.getRowModel().rows.length === 0) { return (
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksData/useTasksData.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksData/useTasksData.tsx index 8c632d1fe64..e70ae23cdf9 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksData/useTasksData.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksData/useTasksData.tsx @@ -29,11 +29,10 @@ export function useTasksData({ }: UseTasksDataParams): { data: TaskWithStatus[]; allStatuses: SelectTaskStatus[]; - isLoading: boolean; } { const collections = useCollections(); - const { data: allData, isLoading } = useLiveQuery( + const { data: allData } = useLiveQuery( (q) => q .from({ tasks: collections.tasks }) @@ -52,7 +51,7 @@ export function useTasksData({ [collections], ); - const { data: statusData, isLoading: isStatusesLoading } = useLiveQuery( + const { data: statusData } = useLiveQuery( (q) => q .from({ taskStatuses: collections.taskStatuses }) @@ -119,6 +118,5 @@ export function useTasksData({ return { data: filteredData, allStatuses, - isLoading: isLoading || isStatusesLoading, }; } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksTable/useTasksTable.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksTable/useTasksTable.tsx index 8831e44b173..2cb776f481a 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksTable/useTasksTable.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/hooks/useTasksTable/useTasksTable.tsx @@ -71,7 +71,6 @@ export function useTasksTable({ assigneeFilter, }: UseTasksTableParams): { table: Table; - isLoading: boolean; slugColumnWidth: string; rowSelection: RowSelectionState; setRowSelection: ( @@ -87,7 +86,7 @@ export function useTasksTable({ const rowSelection = useRowSelectionStore((s) => s.rowSelection); const setRowSelection = useRowSelectionStore((s) => s.setRowSelection); - const { data: allData, isLoading } = useLiveQuery( + const { data: allData } = useLiveQuery( (q) => q .from({ tasks: collections.tasks }) @@ -348,5 +347,5 @@ export function useTasksTable({ autoResetExpanded: false, }); - return { table, isLoading, slugColumnWidth, rowSelection, setRowSelection }; + return { table, slugColumnWidth, rowSelection, setRowSelection }; } diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts index aba18dad1a0..66ccea64bd1 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts @@ -24,6 +24,10 @@ import type { } from "@superset/db/schema"; import type { AppRouter } from "@superset/trpc"; import { electricCollectionOptions } from "@tanstack/electric-db-collection"; +import { + createElectronSQLitePersistence, + persistedCollectionOptions, +} from "@tanstack/electron-db-sqlite-persistence"; import type { Collection, LocalStorageCollectionUtils, @@ -57,6 +61,10 @@ const columnMapper = snakeCamelMapper(); const electricUrl = `${env.NEXT_PUBLIC_ELECTRIC_URL}/v1/shape`; +const persistence = createElectronSQLitePersistence({ + invoke: (channel, request) => window.ipcRenderer.invoke(channel, request), +}); + const indexDefaults = { autoIndex: "eager", defaultIndexType: BasicIndex, @@ -67,6 +75,21 @@ const createIndexedCollection = (( ) => createCollection({ ...config, ...indexDefaults })) as typeof createCollection; +type ElectricSyncConfig = ReturnType; +const createPersistedElectricCollection = ((config: ElectricSyncConfig) => { + const persisted = persistedCollectionOptions({ + ...config, + persistence, + schemaVersion: 1, + // biome-ignore lint/suspicious/noExplicitAny: forces sync-wrapped overload + } as any); + return createCollection({ + ...persisted, + ...indexDefaults, + // biome-ignore lint/suspicious/noExplicitAny: persisted utils widen generics + } as any); +}) as unknown as typeof createCollection; + const apiKeyDisplaySchema = z.object({ id: z.string(), name: z.string().nullable(), @@ -176,7 +199,7 @@ const electricHeaders = { }, }; -const organizationsCollection = createIndexedCollection( +const organizationsCollection = createPersistedElectricCollection( electricCollectionOptions({ id: "organizations", shapeOptions: { @@ -190,7 +213,7 @@ const organizationsCollection = createIndexedCollection( ); function createOrgCollections(organizationId: string): OrgCollections { - const tasks = createIndexedCollection( + const tasks = createPersistedElectricCollection( electricCollectionOptions({ id: `tasks-${organizationId}`, shapeOptions: { @@ -224,7 +247,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const taskStatuses = createIndexedCollection( + const taskStatuses = createPersistedElectricCollection( electricCollectionOptions({ id: `task_statuses-${organizationId}`, shapeOptions: { @@ -240,7 +263,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const projects = createIndexedCollection( + const projects = createPersistedElectricCollection( electricCollectionOptions({ id: `projects-${organizationId}`, shapeOptions: { @@ -256,7 +279,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const v2Projects = createIndexedCollection( + const v2Projects = createPersistedElectricCollection( electricCollectionOptions({ id: `v2_projects-${organizationId}`, shapeOptions: { @@ -288,7 +311,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const v2Hosts = createIndexedCollection( + const v2Hosts = createPersistedElectricCollection( electricCollectionOptions({ id: `v2_hosts-${organizationId}`, shapeOptions: { @@ -306,7 +329,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const v2Clients = createIndexedCollection( + const v2Clients = createPersistedElectricCollection( electricCollectionOptions({ id: `v2_clients-${organizationId}`, shapeOptions: { @@ -324,7 +347,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const v2UsersHosts = createIndexedCollection( + const v2UsersHosts = createPersistedElectricCollection( electricCollectionOptions({ id: `v2_users_hosts-${organizationId}`, shapeOptions: { @@ -369,7 +392,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const v2Workspaces = createIndexedCollection( + const v2Workspaces = createPersistedElectricCollection( electricCollectionOptions({ id: `v2_workspaces-${organizationId}`, shapeOptions: { @@ -396,7 +419,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const workspaces = createIndexedCollection( + const workspaces = createPersistedElectricCollection( electricCollectionOptions({ id: `workspaces-${organizationId}`, shapeOptions: { @@ -412,7 +435,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const members = createIndexedCollection( + const members = createPersistedElectricCollection( electricCollectionOptions({ id: `members-${organizationId}`, shapeOptions: { @@ -428,7 +451,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const users = createIndexedCollection( + const users = createPersistedElectricCollection( electricCollectionOptions({ id: `users-${organizationId}`, shapeOptions: { @@ -444,7 +467,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const invitations = createIndexedCollection( + const invitations = createPersistedElectricCollection( electricCollectionOptions({ id: `invitations-${organizationId}`, shapeOptions: { @@ -460,7 +483,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const agentCommands = createIndexedCollection( + const agentCommands = createPersistedElectricCollection( electricCollectionOptions({ id: `agent_commands-${organizationId}`, shapeOptions: { @@ -484,7 +507,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const integrationConnections = createIndexedCollection( + const integrationConnections = createPersistedElectricCollection( electricCollectionOptions({ id: `integration_connections-${organizationId}`, shapeOptions: { @@ -500,7 +523,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const subscriptions = createIndexedCollection( + const subscriptions = createPersistedElectricCollection( electricCollectionOptions({ id: `subscriptions-${organizationId}`, shapeOptions: { @@ -516,7 +539,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const apiKeys = createIndexedCollection( + const apiKeys = createPersistedElectricCollection( electricCollectionOptions({ id: `apikeys-${organizationId}`, shapeOptions: { @@ -532,7 +555,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const chatSessions = createIndexedCollection( + const chatSessions = createPersistedElectricCollection( electricCollectionOptions({ id: `chat_sessions-${organizationId}`, shapeOptions: { @@ -558,7 +581,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const githubRepositories = createIndexedCollection( + const githubRepositories = createPersistedElectricCollection( electricCollectionOptions({ id: `github_repositories-${organizationId}`, shapeOptions: { @@ -574,7 +597,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const githubPullRequests = createIndexedCollection( + const githubPullRequests = createPersistedElectricCollection( electricCollectionOptions({ id: `github_pull_requests-${organizationId}`, shapeOptions: { @@ -590,7 +613,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const automations = createIndexedCollection( + const automations = createPersistedElectricCollection( electricCollectionOptions({ id: `automations-${organizationId}`, shapeOptions: { @@ -606,7 +629,7 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const automationRuns = createIndexedCollection( + const automationRuns = createPersistedElectricCollection( electricCollectionOptions({ id: `automation_runs-${organizationId}`, shapeOptions: { diff --git a/bun.lock b/bun.lock index f68abeb726f..a0de8ed3144 100644 --- a/bun.lock +++ b/bun.lock @@ -175,6 +175,8 @@ "@t3-oss/env-core": "^0.13.8", "@tanstack/db": "0.6.5", "@tanstack/electric-db-collection": "0.3.3", + "@tanstack/electron-db-sqlite-persistence": "0.1.9", + "@tanstack/node-db-sqlite-persistence": "0.1.9", "@tanstack/react-db": "0.1.83", "@tanstack/react-query": "^5.90.19", "@tanstack/react-router": "^1.147.3", @@ -2634,10 +2636,16 @@ "@tanstack/db-ivm": ["@tanstack/db-ivm@0.1.18", "", { "dependencies": { "fractional-indexing": "^3.2.0", "sorted-btree": "^1.8.1" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-+pZJiRKdoKRM5Epq9T7otD9ZJl82pRFauo7LKuJGrarjVKQ7r+QQlPe3kGdN9LEKSnuNGIWjX9OOY4M8kH4eLw=="], + "@tanstack/db-sqlite-persistence-core": ["@tanstack/db-sqlite-persistence-core@0.1.9", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@tanstack/db": "0.6.5" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-Ju4HcBzaSXTaPJFMRWNjiyXRfCmom0AXBr/22sqLoQWY1TjFM/UTbSSTQgsrExkNuFDVFiV8EDpCt8WUBFiKcQ=="], + "@tanstack/electric-db-collection": ["@tanstack/electric-db-collection@0.3.3", "", { "dependencies": { "@electric-sql/client": "^1.5.15", "@standard-schema/spec": "^1.1.0", "@tanstack/db": "0.6.5", "@tanstack/store": "^0.9.2", "debug": "^4.4.3" } }, "sha512-ywx5s6kv/ZNDszl2GwnlvZPlimEBAUREo9cEN9oCN7SyO36/5andaAKgjWQYXiXDc6FamjQfE6N6WuUFb0Zg0Q=="], + "@tanstack/electron-db-sqlite-persistence": ["@tanstack/electron-db-sqlite-persistence@0.1.9", "", { "dependencies": { "@tanstack/db-sqlite-persistence-core": "0.1.9" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-QlH4pt0vhw9BZIdf6iyIioFoUy58vJoe5gXNIpXFEXY1uP4awKpTJ7ZrVTlBPPpaYvbcJxMxqjusoFCRVA5XQg=="], + "@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="], + "@tanstack/node-db-sqlite-persistence": ["@tanstack/node-db-sqlite-persistence@0.1.9", "", { "dependencies": { "@tanstack/db-sqlite-persistence-core": "0.1.9", "better-sqlite3": "^12.6.2" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-irgaRzZ6eMV4Ky7fIoiy823vPfGllnsJPI0oqdjWS4kD09juktR8JfS0Gqg9ONBbwLB2kLBrC3ei72Dzz92RKg=="], + "@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.2.1", "", {}, "sha512-3PouiFjR4B6x1c969/Pl4ZIJleof1M0n6fNX8NRiC9Sqv1g06CVDlEaXUR4212ycGFyfq4q+t8Gi37Xy+z34iQ=="], "@tanstack/query-core": ["@tanstack/query-core@5.95.2", "", {}, "sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ=="],