From 2faef83ff87437c0e312a54030241e7cf10e642f Mon Sep 17 00:00:00 2001 From: Yann Amsellem Date: Fri, 17 Jan 2025 16:31:51 +0100 Subject: [PATCH] feat: handle errors --- src/lib/components/Console.svelte | 104 +++++++++++++++++++++++++++ src/lib/components/Editor/theme.ts | 2 +- src/lib/components/Result.svelte | 12 +++- src/lib/icons/Trash.svelte | 20 ++++++ src/lib/olap-engine/Logger.ts | 25 +++++++ src/lib/olap-engine/engine-chdb.ts | 8 ++- src/lib/olap-engine/engine-remote.ts | 37 ++++++---- src/lib/olap-engine/index.ts | 9 ++- src/routes/+page.svelte | 23 +++++- 9 files changed, 218 insertions(+), 22 deletions(-) create mode 100644 src/lib/components/Console.svelte create mode 100644 src/lib/icons/Trash.svelte create mode 100644 src/lib/olap-engine/Logger.ts diff --git a/src/lib/components/Console.svelte b/src/lib/components/Console.svelte new file mode 100644 index 0000000..3bc3595 --- /dev/null +++ b/src/lib/components/Console.svelte @@ -0,0 +1,104 @@ + + + + +
+ {#each logs as log} +
+ {log.timestamp.toUTCString()} + {log.data} +
+ {/each} + +
+ + diff --git a/src/lib/components/Editor/theme.ts b/src/lib/components/Editor/theme.ts index d417787..b5f544d 100644 --- a/src/lib/components/Editor/theme.ts +++ b/src/lib/components/Editor/theme.ts @@ -36,6 +36,6 @@ export const theme = syntaxHighlighting( { tag: t.heading, fontWeight: 'bold', color: 'hsl(220deg 2% 90%)' }, { tag: [t.atom, t.bool], color: 'hsl(45, 7%, 75%)' }, { tag: [t.processingInstruction, t.string, t.inserted], color: 'hsl(41, 37%, 68%)' }, - { tag: t.invalid, color: '#ff008c' } + { tag: t.invalid, color: 'hsl(327deg 100% 50%)' } ]) ); diff --git a/src/lib/components/Result.svelte b/src/lib/components/Result.svelte index 9323540..4c5b6bb 100644 --- a/src/lib/components/Result.svelte +++ b/src/lib/components/Result.svelte @@ -3,14 +3,17 @@ import type { OLAPResponse } from '$lib/olap-engine'; import { untrack } from 'svelte'; import ChartContainer from './ChartContainer.svelte'; + import Console, { type Log } from './Console.svelte'; interface Props { response?: OLAPResponse; + logs?: Log[]; + tab?: 'data' | 'chart' | 'logs'; + onClearLogs?: () => void; } - let { response }: Props = $props(); + let { response, logs = [], tab = $bindable('data'), onClearLogs }: Props = $props(); - let tab = $state<'data' | 'chart'>('data'); let yAxis = $state(''); let xAxis = $state(''); let chartType = $state('line'); @@ -27,6 +30,7 @@
{#if response} @@ -36,6 +40,10 @@ {/if} {/if} + + {#if tab === 'logs'} + + {/if}
diff --git a/src/lib/icons/Trash.svelte b/src/lib/icons/Trash.svelte new file mode 100644 index 0000000..b3944d0 --- /dev/null +++ b/src/lib/icons/Trash.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/src/lib/olap-engine/Logger.ts b/src/lib/olap-engine/Logger.ts new file mode 100644 index 0000000..694bc9a --- /dev/null +++ b/src/lib/olap-engine/Logger.ts @@ -0,0 +1,25 @@ +type Callback = (param: any) => void; + +export interface ILogger { + on(level: 'error', fn: Callback): () => void; + log(level: 'error', param: any): void; +} + +export abstract class Logger implements ILogger { + #listeners: { [key: string]: Set } = {}; + + on(logEvent: 'error', fn: Callback) { + this.#listeners[logEvent] ??= new Set(); + + this.#listeners[logEvent].add(fn); + return () => this.#listeners[logEvent].delete(fn); + } + + log(level: 'error', param: any) { + if (!this.#listeners[level]?.size) return; + + queueMicrotask(() => { + for (const fn of this.#listeners[level]) fn(param); + }); + } +} diff --git a/src/lib/olap-engine/engine-chdb.ts b/src/lib/olap-engine/engine-chdb.ts index 556ee4b..198ee3d 100644 --- a/src/lib/olap-engine/engine-chdb.ts +++ b/src/lib/olap-engine/engine-chdb.ts @@ -1,10 +1,11 @@ import { invoke } from '@tauri-apps/api/core'; -import type { OLAPEngine, Table, OLAPResponse } from './index'; +import type { OLAPEngine, OLAPResponse, Table } from './index'; +import { Logger } from './Logger'; import CLICKHOUSE_GET_SCHEMA from './queries/clickhouse_get_schema.sql?raw'; import CLICKHOUSE_INIT_DB from './queries/clickhouse_init_db.sql?raw'; -export class CHDBEngine implements OLAPEngine { +export class CHDBEngine extends Logger implements OLAPEngine { async init() { await this.exec(CLICKHOUSE_INIT_DB); } @@ -16,8 +17,9 @@ export class CHDBEngine implements OLAPEngine { return JSON.parse(r) as OLAPResponse; } catch (e) { + if (typeof e === 'string') e = new Error(e); console.error(e); - return undefined; + this.log('error', e); } } diff --git a/src/lib/olap-engine/engine-remote.ts b/src/lib/olap-engine/engine-remote.ts index 570f75f..d70fc48 100644 --- a/src/lib/olap-engine/engine-remote.ts +++ b/src/lib/olap-engine/engine-remote.ts @@ -1,28 +1,30 @@ -import type { OLAPEngine, Table, OLAPResponse } from './index'; +import type { OLAPEngine, OLAPResponse, Table } from './index'; +import { Logger } from './Logger'; import CLICKHOUSE_GET_SCHEMA from './queries/clickhouse_get_schema.sql?raw'; -export class RemoteEngine implements OLAPEngine { +export class RemoteEngine extends Logger implements OLAPEngine { async init() {} async exec(query: string) { try { const proxy = new URLSearchParams(window.location.search).get('proxy') ?? 'https://proxy.agx.app/query'; - const response = await fetch(`${proxy}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: query - }); - const r = await response.text(); - if (!r) return; + const response = await fetch(proxy, { method: 'POST', body: query }); - return JSON.parse(r) as OLAPResponse; + if (!response.ok) throw new Error(`HTTP Error: ${response.status}`); + + const r = await response.text(); + if (!r) throw new Error(`Empty Response`); + + const data: RemoteEngineResponse = JSON.parse(r); + + if ('exception' in data) throw new Error(data.exception); + + return data; } catch (e) { console.error(e); - return undefined; + this.log('error', e); } } @@ -32,3 +34,12 @@ export class RemoteEngine implements OLAPEngine { return response.data as Table[]; } } + +interface RemoteEngineException { + meta: []; + data: []; + rows: 0; + exception: string; +} + +type RemoteEngineResponse = OLAPResponse | RemoteEngineException; diff --git a/src/lib/olap-engine/index.ts b/src/lib/olap-engine/index.ts index 5dfc6a6..8540334 100644 --- a/src/lib/olap-engine/index.ts +++ b/src/lib/olap-engine/index.ts @@ -1,9 +1,16 @@ import { CHDBEngine } from './engine-chdb'; import { RemoteEngine } from './engine-remote'; +import type { ILogger } from './Logger'; export type OLAPResponse = { meta: Array; data: Array<{ [key: string]: any }>; + rows: number; + statistics: { + bytes_read: number; + elapsed: number; + rows_read: number; + }; }; export interface ColumnDescriptor { @@ -17,7 +24,7 @@ export interface Table { columns: ColumnDescriptor[]; } -export interface OLAPEngine { +export interface OLAPEngine extends ILogger { init(): Promise; exec(query: string): Promise; getSchema(): Promise; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 389290b..c9f35f7 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,4 +1,5 @@ @@ -299,7 +313,12 @@ {/snippet} {#snippet b()} - + (errors = [])} + /> {/snippet} {/snippet}