mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
Fix database setup edit preservation
This commit is contained in:
parent
359d46e230
commit
cd1bd27e24
5 changed files with 96 additions and 5 deletions
|
|
@ -283,10 +283,12 @@ export async function pickDatabaseScope(
|
|||
continue;
|
||||
}
|
||||
|
||||
const selectedNoun =
|
||||
selectedSchemas.length === 1 ? args.schemaNoun : args.schemaNounPlural;
|
||||
const action = await args.prompts.select({
|
||||
message: `Save ${selectedSchemas.length} ${selectedSchemas.length === 1 ? args.schemaNoun : args.schemaNounPlural} or refine tables?`,
|
||||
message: `Enable all tables in ${selectedSchemas.length} ${selectedNoun}, or refine tables?`,
|
||||
options: [
|
||||
{ value: 'save', label: 'Save selection' },
|
||||
{ value: 'save', label: `Enable all tables in selected ${selectedNoun}` },
|
||||
{ value: 'refine', label: 'Refine: choose individual tables' },
|
||||
{ value: 'back', label: 'Back' },
|
||||
],
|
||||
|
|
|
|||
|
|
@ -167,6 +167,40 @@ describe('CLI local ingest adapters', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('resolves BigQuery credentials_json from a file: reference for query history ingest', async () => {
|
||||
const credentialsPath = join(tempDir, 'credentials.json');
|
||||
await writeFile(credentialsPath, JSON.stringify({ project_id: 'demo-project' }), 'utf-8');
|
||||
await writeProject(
|
||||
tempDir,
|
||||
[
|
||||
'connections:',
|
||||
' bq:',
|
||||
' driver: bigquery',
|
||||
' dataset_id: analytics',
|
||||
' location: us',
|
||||
` credentials_json: 'file:${credentialsPath}'`,
|
||||
' historicSql:',
|
||||
' enabled: true',
|
||||
' dialect: bigquery',
|
||||
'ingest:',
|
||||
' adapters:',
|
||||
' - historic-sql',
|
||||
'',
|
||||
].join('\n'),
|
||||
);
|
||||
const project = await loadKtxProject({ projectDir: tempDir });
|
||||
|
||||
const adapters = createKtxCliLocalIngestAdapters(project, {
|
||||
historicSqlConnectionId: 'bq',
|
||||
sqlAnalysis: sqlAnalysisStub(),
|
||||
});
|
||||
|
||||
expect(adapters.find((adapter) => adapter.source === 'historic-sql')?.skillNames).toEqual([
|
||||
'historic_sql_table_digest',
|
||||
'historic_sql_patterns',
|
||||
]);
|
||||
});
|
||||
|
||||
it('uses query-history wording for public BigQuery capability errors', async () => {
|
||||
await writeProject(
|
||||
tempDir,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
type ManagedPythonCoreDaemonOptions,
|
||||
} from './managed-python-http.js';
|
||||
import type { KtxOperationalLogger } from './io/logger.js';
|
||||
import { resolveKtxConfigReference } from './context/core/config-reference.js';
|
||||
|
||||
function hasSnowflakeDriver(connection: unknown): boolean {
|
||||
return (
|
||||
|
|
@ -279,7 +280,10 @@ async function createEphemeralSnowflakeHistoricSqlClient(
|
|||
|
||||
function bigQueryProjectId(connection: KtxBigQueryConnectionConfig, env: NodeJS.ProcessEnv): string {
|
||||
const raw = typeof connection.credentials_json === 'string' ? connection.credentials_json : '';
|
||||
const resolved = raw.startsWith('env:') ? env[raw.slice('env:'.length)] ?? '' : raw;
|
||||
const resolved = resolveKtxConfigReference(raw, env);
|
||||
if (!resolved) {
|
||||
throw new Error('Query history BigQuery connection requires credentials_json');
|
||||
}
|
||||
const parsed = JSON.parse(resolved) as { project_id?: unknown };
|
||||
if (typeof parsed.project_id !== 'string' || parsed.project_id.trim().length === 0) {
|
||||
throw new Error('Query history BigQuery connection requires credentials_json.project_id');
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ function makePromptAdapter(options: {
|
|||
: ['back'];
|
||||
}),
|
||||
select: vi.fn(async ({ message }) => {
|
||||
if (message.startsWith('Save ') && message.includes(' or refine tables?')) {
|
||||
if (message.startsWith('Enable all tables in ') && message.includes(', or refine tables?')) {
|
||||
return 'save';
|
||||
}
|
||||
if (message.includes('How much database context should KTX build?')) {
|
||||
|
|
@ -260,6 +260,48 @@ describe('setup databases step', () => {
|
|||
expect(prompts.select).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('preserves context.depth when editing an existing database connection', async () => {
|
||||
await writeFile(
|
||||
join(tempDir, 'ktx.yaml'),
|
||||
[
|
||||
'connections:',
|
||||
' warehouse:',
|
||||
' driver: sqlite',
|
||||
' path: ./warehouse.sqlite',
|
||||
' context:',
|
||||
' depth: deep',
|
||||
'',
|
||||
].join('\n'),
|
||||
'utf-8',
|
||||
);
|
||||
const prompts = makePromptAdapter({
|
||||
selectValues: ['edit', 'warehouse', 'continue'],
|
||||
textValues: ['./warehouse.sqlite'],
|
||||
});
|
||||
const testConnection = vi.fn(async () => 0);
|
||||
const scanConnection = vi.fn(async () => 0);
|
||||
const io = makeIo();
|
||||
const result = await runKtxSetupDatabasesStep(
|
||||
{
|
||||
projectDir: tempDir,
|
||||
inputMode: 'auto',
|
||||
skipDatabases: false,
|
||||
databaseSchemas: [],
|
||||
disableQueryHistory: true,
|
||||
},
|
||||
io.io,
|
||||
{ prompts, testConnection, scanConnection },
|
||||
);
|
||||
|
||||
expect(result.status, io.stderr()).toBe('ready');
|
||||
const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8'));
|
||||
expect(config.connections.warehouse).toMatchObject({
|
||||
driver: 'sqlite',
|
||||
path: './warehouse.sqlite',
|
||||
context: { depth: 'deep' },
|
||||
});
|
||||
});
|
||||
|
||||
it('labels existing database connections with the database type', async () => {
|
||||
await writeFile(
|
||||
join(tempDir, 'ktx.yaml'),
|
||||
|
|
|
|||
|
|
@ -663,6 +663,12 @@ function normalizeFileReference(value: string): string {
|
|||
return `file:${normalized}`;
|
||||
}
|
||||
|
||||
function displayFileReference(value: string | undefined): string | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
if (value.startsWith('file:')) return value.slice('file:'.length);
|
||||
return value;
|
||||
}
|
||||
|
||||
function scriptedScopeConfigForDriver(
|
||||
driver: KtxSetupDatabaseDriver,
|
||||
databaseSchemas: string[],
|
||||
|
|
@ -910,7 +916,7 @@ async function buildConnectionConfig(input: {
|
|||
const credentialsPath = await promptText(
|
||||
prompts,
|
||||
'Path to service account JSON file',
|
||||
stringConfigField(input.existingConnection, 'credentials_json'),
|
||||
displayFileReference(stringConfigField(input.existingConnection, 'credentials_json')),
|
||||
);
|
||||
if (credentialsPath === undefined) return 'back';
|
||||
const location = await promptText(
|
||||
|
|
@ -1359,6 +1365,9 @@ function withExistingPrimaryEditPromptDefaults(input: {
|
|||
if (!Object.hasOwn(input.next, 'enabled_tables') && Array.isArray(input.previous.enabled_tables)) {
|
||||
merged.enabled_tables = input.previous.enabled_tables;
|
||||
}
|
||||
if (!Object.hasOwn(input.next, 'context') && input.previous.context !== undefined) {
|
||||
merged.context = input.previous.context;
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue