mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
feat(ingest): use foreground view for interactive public ingest
This commit is contained in:
parent
0764acaadd
commit
9c7b4f9b84
4 changed files with 149 additions and 5 deletions
|
|
@ -428,6 +428,43 @@ describe('runContextBuild', () => {
|
|||
expect(callOrder).toEqual(['warehouse', 'dbt_main']);
|
||||
});
|
||||
|
||||
it('runs only the requested connection when foreground build receives a target', async () => {
|
||||
const io = makeIo();
|
||||
const project = projectWithConnections({
|
||||
warehouse: { driver: 'postgres' },
|
||||
docs: { driver: 'notion' },
|
||||
});
|
||||
const executeTarget = vi.fn(async (target) =>
|
||||
successResult(target.connectionId, target.driver, target.operation),
|
||||
);
|
||||
|
||||
await expect(
|
||||
runContextBuild(
|
||||
project,
|
||||
{
|
||||
projectDir: '/tmp/project',
|
||||
inputMode: 'disabled',
|
||||
targetConnectionId: 'warehouse',
|
||||
all: false,
|
||||
depth: 'fast',
|
||||
queryHistory: 'default',
|
||||
},
|
||||
io.io,
|
||||
{ executeTarget, now: () => 1000 },
|
||||
),
|
||||
).resolves.toMatchObject({ exitCode: 0 });
|
||||
|
||||
expect(executeTarget).toHaveBeenCalledTimes(1);
|
||||
expect(executeTarget.mock.calls[0]?.[0]).toMatchObject({
|
||||
connectionId: 'warehouse',
|
||||
operation: 'database-ingest',
|
||||
databaseDepth: 'fast',
|
||||
});
|
||||
expect(io.stdout()).toContain('Databases:');
|
||||
expect(io.stdout()).toContain('warehouse');
|
||||
expect(io.stdout()).not.toContain('docs');
|
||||
});
|
||||
|
||||
it('returns exit code 1 when any target fails', async () => {
|
||||
const io = makeIo();
|
||||
const project = projectWithConnections({
|
||||
|
|
|
|||
|
|
@ -39,6 +39,11 @@ export interface ContextBuildViewState {
|
|||
export interface ContextBuildArgs {
|
||||
projectDir: string;
|
||||
inputMode: 'auto' | 'disabled';
|
||||
targetConnectionId?: string;
|
||||
all?: boolean;
|
||||
depth?: Extract<KtxPublicIngestArgs, { command: 'run' }>['depth'];
|
||||
queryHistory?: Extract<KtxPublicIngestArgs, { command: 'run' }>['queryHistory'];
|
||||
queryHistoryWindowDays?: number;
|
||||
scanMode?: 'structural' | 'enriched';
|
||||
detectRelationships?: boolean;
|
||||
}
|
||||
|
|
@ -565,7 +570,15 @@ export async function runContextBuild(
|
|||
io: KtxCliIo,
|
||||
deps: ContextBuildDeps = {},
|
||||
): Promise<ContextBuildResult> {
|
||||
const plan = buildPublicIngestPlan(project, { projectDir: args.projectDir, all: true });
|
||||
const plan = buildPublicIngestPlan(project, {
|
||||
projectDir: args.projectDir,
|
||||
...(args.targetConnectionId ? { targetConnectionId: args.targetConnectionId } : {}),
|
||||
all: args.all ?? true,
|
||||
...(args.depth ? { depth: args.depth } : {}),
|
||||
...(args.queryHistory ? { queryHistory: args.queryHistory } : {}),
|
||||
...(args.queryHistoryWindowDays !== undefined ? { queryHistoryWindowDays: args.queryHistoryWindowDays } : {}),
|
||||
...(args.scanMode ? { scanMode: args.scanMode } : {}),
|
||||
});
|
||||
const state = initViewState(plan.targets);
|
||||
const isTTY = io.stdout.isTTY === true;
|
||||
const nowFn = deps.now ?? (() => Date.now());
|
||||
|
|
@ -614,11 +627,15 @@ export async function runContextBuild(
|
|||
const runArgs: Extract<KtxPublicIngestArgs, { command: 'run' }> = {
|
||||
command: 'run',
|
||||
projectDir: args.projectDir,
|
||||
all: true,
|
||||
...(args.targetConnectionId ? { targetConnectionId: args.targetConnectionId } : {}),
|
||||
all: args.all ?? true,
|
||||
json: false,
|
||||
inputMode: args.inputMode,
|
||||
scanMode: args.scanMode,
|
||||
detectRelationships: args.detectRelationships,
|
||||
...(args.depth ? { depth: args.depth } : {}),
|
||||
...(args.queryHistory ? { queryHistory: args.queryHistory } : {}),
|
||||
...(args.queryHistoryWindowDays !== undefined ? { queryHistoryWindowDays: args.queryHistoryWindowDays } : {}),
|
||||
...(args.scanMode ? { scanMode: args.scanMode } : {}),
|
||||
...(args.detectRelationships !== undefined ? { detectRelationships: args.detectRelationships } : {}),
|
||||
};
|
||||
|
||||
let hasFailure = false;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,19 @@ import { buildDefaultKtxProjectConfig, type KtxProjectConfig } from '@ktx/contex
|
|||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { buildPublicIngestPlan, type KtxPublicIngestProject, runKtxPublicIngest } from './public-ingest.js';
|
||||
|
||||
function makeIo(options: { isTTY?: boolean } = {}) {
|
||||
function makeIo(options: { isTTY?: boolean; interactive?: boolean } = {}) {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
return {
|
||||
io: {
|
||||
...(options.interactive
|
||||
? {
|
||||
stdin: {
|
||||
isTTY: true,
|
||||
setRawMode: vi.fn(),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
stdout: {
|
||||
isTTY: options.isTTY,
|
||||
write: (chunk: string) => {
|
||||
|
|
@ -399,6 +407,43 @@ describe('runKtxPublicIngest', () => {
|
|||
expect(io.stdout()).not.toContain('live-database');
|
||||
});
|
||||
|
||||
it('delegates interactive TTY public ingest to the foreground context-build view', async () => {
|
||||
const io = makeIo({ isTTY: true, interactive: true });
|
||||
const project = projectWithConnections({ warehouse: { driver: 'postgres' } });
|
||||
const runContextBuild = vi.fn(async () => ({ exitCode: 0 }));
|
||||
const runScan = vi.fn(async () => 0);
|
||||
|
||||
await expect(
|
||||
runKtxPublicIngest(
|
||||
{
|
||||
command: 'run',
|
||||
projectDir: '/tmp/project',
|
||||
targetConnectionId: 'warehouse',
|
||||
all: false,
|
||||
json: false,
|
||||
inputMode: 'auto',
|
||||
depth: 'fast',
|
||||
queryHistory: 'default',
|
||||
},
|
||||
io.io,
|
||||
{ loadProject: vi.fn(async () => project), runContextBuild, runScan },
|
||||
),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(runContextBuild).toHaveBeenCalledWith(
|
||||
project,
|
||||
expect.objectContaining({
|
||||
projectDir: '/tmp/project',
|
||||
targetConnectionId: 'warehouse',
|
||||
all: false,
|
||||
depth: 'fast',
|
||||
queryHistory: 'default',
|
||||
}),
|
||||
io.io,
|
||||
);
|
||||
expect(runScan).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('runs all independent targets and reports partial failures', async () => {
|
||||
const io = makeIo();
|
||||
const project = projectWithConnections({
|
||||
|
|
|
|||
|
|
@ -86,10 +86,27 @@ export interface KtxPublicIngestDeps {
|
|||
loadProject?: (options: Parameters<typeof loadKtxProject>[0]) => Promise<KtxPublicIngestProject>;
|
||||
runScan?: (args: KtxScanArgs, io: KtxCliIo, deps?: KtxScanDeps) => Promise<number>;
|
||||
runIngest?: (args: KtxIngestArgs, io: KtxCliIo, deps?: KtxIngestDeps) => Promise<number>;
|
||||
runContextBuild?: (
|
||||
project: KtxPublicIngestProject,
|
||||
args: KtxPublicContextBuildArgs,
|
||||
io: KtxCliIo,
|
||||
) => Promise<{ exitCode: number }>;
|
||||
scanProgress?: KtxProgressPort;
|
||||
ingestProgress?: (update: KtxIngestProgressUpdate) => void;
|
||||
}
|
||||
|
||||
interface KtxPublicContextBuildArgs {
|
||||
projectDir: string;
|
||||
inputMode: 'auto' | 'disabled';
|
||||
targetConnectionId?: string;
|
||||
all?: boolean;
|
||||
depth?: KtxPublicIngestDepth;
|
||||
queryHistory?: KtxPublicIngestQueryHistoryFlag;
|
||||
queryHistoryWindowDays?: number;
|
||||
scanMode?: Extract<KtxScanArgs, { command: 'run' }>['mode'];
|
||||
detectRelationships?: boolean;
|
||||
}
|
||||
|
||||
const sourceAdapterByDriver = new Map<string, string>([
|
||||
['metabase', 'metabase'],
|
||||
['local_metabase', 'metabase'],
|
||||
|
|
@ -455,6 +472,13 @@ function sourceIngestOutputMode(args: Extract<KtxPublicIngestArgs, { command: 'r
|
|||
return args.inputMode === 'auto' && io.stdout.isTTY === true && hasInteractiveInput(io) ? 'viz' : 'plain';
|
||||
}
|
||||
|
||||
function shouldUseForegroundContextBuildView(
|
||||
args: Extract<KtxPublicIngestArgs, { command: 'run' }>,
|
||||
io: KtxCliIo,
|
||||
): boolean {
|
||||
return args.inputMode === 'auto' && args.json !== true && io.stdout.isTTY === true && hasInteractiveInput(io);
|
||||
}
|
||||
|
||||
interface CapturedPublicIngestIo extends KtxCliIo {
|
||||
capturedOutput(): string;
|
||||
}
|
||||
|
|
@ -597,6 +621,27 @@ export async function runKtxPublicIngest(
|
|||
|
||||
const loadProject = deps.loadProject ?? loadKtxProject;
|
||||
const project = await loadProject({ projectDir: args.projectDir });
|
||||
if (shouldUseForegroundContextBuildView(args, io)) {
|
||||
const { runContextBuild } = await import('./context-build-view.js');
|
||||
const contextBuild = deps.runContextBuild ?? runContextBuild;
|
||||
const result = await contextBuild(
|
||||
project,
|
||||
{
|
||||
projectDir: args.projectDir,
|
||||
...(args.targetConnectionId ? { targetConnectionId: args.targetConnectionId } : {}),
|
||||
all: args.all,
|
||||
inputMode: args.inputMode,
|
||||
...(args.depth ? { depth: args.depth } : {}),
|
||||
...(args.queryHistory ? { queryHistory: args.queryHistory } : {}),
|
||||
...(args.queryHistoryWindowDays !== undefined ? { queryHistoryWindowDays: args.queryHistoryWindowDays } : {}),
|
||||
...(args.scanMode ? { scanMode: args.scanMode } : {}),
|
||||
...(args.detectRelationships !== undefined ? { detectRelationships: args.detectRelationships } : {}),
|
||||
},
|
||||
io,
|
||||
);
|
||||
return result.exitCode;
|
||||
}
|
||||
|
||||
const plan = buildPublicIngestPlan(project, args);
|
||||
const results: KtxPublicIngestTargetResult[] = [];
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue