From 85c4d49c4a0cadbda6c2e8052f700b6a76924473 Mon Sep 17 00:00:00 2001 From: Ben King <9087625+benfdking@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:40:07 +0100 Subject: [PATCH] fix(vscode): passing any lineage error through --- vscode/extension/src/lsp/custom.ts | 19 +++++---- vscode/extension/src/lsp/lsp.ts | 3 ++ vscode/extension/src/webviews/lineagePanel.ts | 41 ++++++++++++++++--- vscode/extension/tests/broken_project.spec.ts | 36 ++++++++++++++++ vscode/react/src/pages/lineage.tsx | 10 ++++- 5 files changed, 95 insertions(+), 14 deletions(-) diff --git a/vscode/extension/src/lsp/custom.ts b/vscode/extension/src/lsp/custom.ts index be11419b79..7a9de4ca6f 100644 --- a/vscode/extension/src/lsp/custom.ts +++ b/vscode/extension/src/lsp/custom.ts @@ -14,7 +14,7 @@ interface RenderModelRequest { textDocumentUri: string } -interface RenderModelResponse { +interface RenderModelResponse extends BaseResponse { models: RenderModelEntry[] } @@ -25,7 +25,6 @@ export interface RenderModelEntry { rendered_query: string } -// @eslint-disable-next-line @typescript-eslint/consistent-type-definition export type CustomLSPMethods = | AllModelsMethod | AbstractAPICall @@ -40,7 +39,7 @@ interface AllModelsRequest { } } -interface AllModelsResponse { +interface AllModelsResponse extends BaseResponse { models: string[] keywords: string[] } @@ -55,9 +54,11 @@ export interface AbstractAPICallRequest { export interface AbstractAPICall { method: 'sqlmesh/api' request: AbstractAPICallRequest - response: object + response: AbstractAPICallResponse } +type AbstractAPICallResponse = object & BaseResponse + export interface AllModelsForRenderMethod { method: 'sqlmesh/all_models_for_render' request: AllModelsForRenderRequest @@ -67,7 +68,7 @@ export interface AllModelsForRenderMethod { // eslint-disable-next-line @typescript-eslint/no-empty-object-type interface AllModelsForRenderRequest {} -interface AllModelsForRenderResponse { +interface AllModelsForRenderResponse extends BaseResponse { models: ModelForRendering[] } @@ -87,7 +88,7 @@ export interface SupportedMethodsMethod { // eslint-disable-next-line @typescript-eslint/no-empty-object-type interface SupportedMethodsRequest {} -interface SupportedMethodsResponse { +interface SupportedMethodsResponse extends BaseResponse { methods: CustomMethod[] } @@ -105,4 +106,8 @@ export interface FormatProjectMethod { interface FormatProjectRequest {} // eslint-disable-next-line @typescript-eslint/no-empty-object-type -interface FormatProjectResponse {} +interface FormatProjectResponse extends BaseResponse {} + +interface BaseResponse { + response_error?: string +} diff --git a/vscode/extension/src/lsp/lsp.ts b/vscode/extension/src/lsp/lsp.ts index 9432762aed..769a0b3753 100644 --- a/vscode/extension/src/lsp/lsp.ts +++ b/vscode/extension/src/lsp/lsp.ts @@ -256,6 +256,9 @@ export class LSPClient implements Disposable { try { const result = await this.client.sendRequest(method, request) + if (result.response_error) { + return err(result.response_error) + } return ok(result) } catch (error) { traceError( diff --git a/vscode/extension/src/webviews/lineagePanel.ts b/vscode/extension/src/webviews/lineagePanel.ts index 5bb3e1693f..ee05112a64 100644 --- a/vscode/extension/src/webviews/lineagePanel.ts +++ b/vscode/extension/src/webviews/lineagePanel.ts @@ -10,6 +10,7 @@ import { } from 'vscode' import { getWorkspaceFolders } from '../utilities/common/vscodeapi' import { LSPClient } from '../lsp/lsp' +import { isErr } from '@bus/result' export class LineagePanel implements WebviewViewProvider, Disposable { public static readonly viewType = 'sqlmesh.lineage' @@ -97,12 +98,40 @@ export class LineagePanel implements WebviewViewProvider, Disposable { 'sqlmesh/api', payload.params, ) - const responseCallback: CallbackEvent = { - key: 'rpcResponse', - payload: { - requestId, - result: response, - }, + let responseCallback: CallbackEvent + if (isErr(response)) { + let errorMessage: string + switch (response.error.type) { + case 'generic': + errorMessage = response.error.message + break + case 'invalid_state': + errorMessage = `Invalid state: ${response.error.message}` + break + case 'sqlmesh_outdated': + errorMessage = `SQLMesh version issue: ${response.error.message}` + break + default: + errorMessage = 'Unknown error' + } + responseCallback = { + key: 'rpcResponse', + payload: { + requestId, + result: { + ok: false, + error: errorMessage, + }, + }, + } + } else { + responseCallback = { + key: 'rpcResponse', + payload: { + requestId, + result: response, + }, + } } await webviewView.webview.postMessage(responseCallback) break diff --git a/vscode/extension/tests/broken_project.spec.ts b/vscode/extension/tests/broken_project.spec.ts index eff1b7cb02..d2c7212a51 100644 --- a/vscode/extension/tests/broken_project.spec.ts +++ b/vscode/extension/tests/broken_project.spec.ts @@ -92,3 +92,39 @@ test('bad project, double model, then fixed', async ({}) => { await fs.remove(tempDir) } }) + +test('bad project, double model, check lineage', async ({}) => { + const tempDir = await fs.mkdtemp( + path.join(os.tmpdir(), 'vscode-test-tcloud-'), + ) + await fs.copy(SUSHI_SOURCE_PATH, tempDir) + + // Read the customers.sql file + const customersSql = await fs.readFile( + path.join(tempDir, 'models', 'customers.sql'), + 'utf8', + ) + + // Write the customers.sql file with a double model + await fs.writeFile( + path.join(tempDir, 'models', 'customers_duplicated.sql'), + customersSql, + ) + + const { window, close } = await startVSCode(tempDir) + try { + await window.waitForSelector('text=models') + + // Open the lineage view + await openLineageView(window) + + await window.waitForSelector('text=Error creating context') + + await window.waitForSelector('text=Error:') + + await window.waitForTimeout(1000) + } finally { + await close() + await fs.remove(tempDir) + } +}) diff --git a/vscode/react/src/pages/lineage.tsx b/vscode/react/src/pages/lineage.tsx index 0bb7e1a7aa..2aef1e6525 100644 --- a/vscode/react/src/pages/lineage.tsx +++ b/vscode/react/src/pages/lineage.tsx @@ -73,7 +73,11 @@ function Lineage() { const { on } = useEventBus() const queryClient = useQueryClient() - const { data: models, isLoading: isLoadingModels } = useApiModels() + const { + data: models, + isLoading: isLoadingModels, + error: modelsError, + } = useApiModels() const rpc = useRpc() React.useEffect(() => { const fetchFirstTimeModelIfNotSet = async ( @@ -143,6 +147,10 @@ function Lineage() { } }, [on, queryClient, modelsRecord]) + if (modelsError) { + return
Error: {modelsError.message}
+ } + if ( isLoadingModels || models === undefined ||