diff --git a/packages/context/src/ingest/adapters/metabase/client-port.ts b/packages/context/src/ingest/adapters/metabase/client-port.ts index 2eec804e..a5fdb6ce 100644 --- a/packages/context/src/ingest/adapters/metabase/client-port.ts +++ b/packages/context/src/ingest/adapters/metabase/client-port.ts @@ -116,11 +116,17 @@ interface MetabaseNativeStage { 'template-tags'?: Record; } +interface MetabaseLegacyNativeQuery { + query?: string; + 'template-tags'?: Record; +} + export interface MetabaseDatasetQuery { 'lib/type'?: 'mbql/query'; database?: number; type?: 'native' | 'query'; stages?: MetabaseNativeStage[]; + native?: MetabaseLegacyNativeQuery; } export interface MetabaseNativeQueryResult { diff --git a/packages/context/src/ingest/adapters/metabase/client.test.ts b/packages/context/src/ingest/adapters/metabase/client.test.ts index 431efe76..1c0fdfa9 100644 --- a/packages/context/src/ingest/adapters/metabase/client.test.ts +++ b/packages/context/src/ingest/adapters/metabase/client.test.ts @@ -37,6 +37,21 @@ function nativeCard(query: string, templateTags: Record = {}): MetabaseCard { + return { + id: 1, + name: 'Legacy native card', + type: 'model', + query_type: 'native', + database_id: 6, + dataset_query: { + type: 'native', + database: 6, + native: { query, 'template-tags': templateTags }, + }, + }; +} + describe('DefaultMetabaseConnectionClientFactory', () => { it('resolves runtime credentials by the explicit Metabase source connection id and merges overrides', async () => { const resolveCredentials = vi.fn().mockResolvedValue(runtime); @@ -274,6 +289,25 @@ describe('getDummyValueForWidgetType', () => { }); }); +describe('MetabaseClient legacy native dataset query support', () => { + it('reads SQL and template tags from dataset_query.native', async () => { + const client = new MetabaseClient(runtime, fastRetryConfig); + const card = legacyNativeCard('SELECT * FROM orders WHERE status = {{ status }}', { + status: { + name: 'status', + type: 'text', + default: 'paid', + }, + }); + + expect(client.getNativeSql(card)).toBe('SELECT * FROM orders WHERE status = {{ status }}'); + expect(client.getTemplateTags(card)).toEqual({ + status: expect.objectContaining({ name: 'status', type: 'text' }), + }); + await expect(client.getCardSql(card)).resolves.toBe('SELECT * FROM orders WHERE status = {{ status }}'); + }); +}); + describe('MetabaseClient.getResolvedSql', () => { function makeClient(setup?: (client: MetabaseClient) => void): MetabaseClient { const client = new MetabaseClient({ apiUrl: 'http://test', apiKey: 'k' }); diff --git a/packages/context/src/ingest/adapters/metabase/client.ts b/packages/context/src/ingest/adapters/metabase/client.ts index 2ab8b81a..1962bfe0 100644 --- a/packages/context/src/ingest/adapters/metabase/client.ts +++ b/packages/context/src/ingest/adapters/metabase/client.ts @@ -150,6 +150,9 @@ function injectNativeSql(datasetQuery: MetabaseDatasetQuery, sql: string): Metab stages[0] = { ...stages[0], native: sql }; return { ...datasetQuery, stages }; } + if (datasetQuery?.native?.query !== undefined) { + return { ...datasetQuery, native: { ...datasetQuery.native, query: sql } }; + } return datasetQuery; } @@ -368,11 +371,11 @@ export class MetabaseClient implements MetabaseRuntimeClient { } getNativeSql(card: MetabaseCard): string | null { - return card.dataset_query?.stages?.[0]?.native ?? null; + return card.dataset_query?.stages?.[0]?.native ?? card.dataset_query?.native?.query ?? null; } getTemplateTags(card: MetabaseCard): Record { - return card.dataset_query?.stages?.[0]?.['template-tags'] ?? {}; + return card.dataset_query?.stages?.[0]?.['template-tags'] ?? card.dataset_query?.native?.['template-tags'] ?? {}; } async getCardSql(card: MetabaseCard): Promise {