mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
Design at: https://github.com/openobserve/designs/blob/main/dashboards/sql-autocomplete/design.md --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: undefined <undefined@users.noreply.github.com>
569 lines
26 KiB
JavaScript
569 lines
26 KiB
JavaScript
/**
|
|
* SQL Autocomplete — Field & Stream Suggestions E2E Tests
|
|
*
|
|
* Tests the SQL autocomplete feature that shows:
|
|
* - Field suggestions sorted to the top (sortText "\x00")
|
|
* - Stream name suggestions ONLY after the FROM keyword
|
|
* - Correct quote handling when Monaco auto-inserts a closing "
|
|
*
|
|
* FEATURE DOCUMENTATION:
|
|
* - In SQL mode, typing text shows field+function+keyword suggestions (fields first)
|
|
* - Typing "FROM " shows stream names (not fields)
|
|
* - Typing 'FROM "def' shows streams with closing " appended automatically
|
|
* - If Monaco has already auto-inserted the closing ", no extra " is added
|
|
* - After "FROM stream WHERE ", field suggestions are restored (not stream names)
|
|
*
|
|
* TEST COVERAGE:
|
|
* 1. Field suggestions appear in SQL mode and sort above functions/keywords
|
|
* 2. Stream suggestions appear after FROM keyword
|
|
* 3. Field names do NOT appear in the FROM suggestion list
|
|
* 4. FROM "partial shows stream with closing quote in insertText
|
|
* 5. Field suggestions are restored after leaving FROM context (WHERE clause)
|
|
* 6. Dashboard SQL — same field+stream behavior
|
|
* 7. Traces SQL — field suggestions work
|
|
*/
|
|
|
|
const { test, expect, navigateToBase } = require('../utils/enhanced-baseFixtures.js');
|
|
const testLogger = require('../utils/test-logger.js');
|
|
const PageManager = require('../../pages/page-manager.js');
|
|
const logData = require('../../fixtures/log.json');
|
|
const MonacoEditorHelper = require('../utils/MonacoEditorHelper.js');
|
|
|
|
// ============================================================================
|
|
// HELPERS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Enable SQL mode in logs search bar via the syntax-guide toggle.
|
|
* The toggle button may show a modal — dismiss it if it appears.
|
|
*/
|
|
async function enableSqlMode(page, pm) {
|
|
const sqlSwitch = page.getByRole('switch', { name: 'SQL Mode' });
|
|
const isChecked = await sqlSwitch.isChecked().catch(() => false);
|
|
if (!isChecked) {
|
|
await pm.logsPage.enableSQLMode();
|
|
// Dismiss any confirmation dialog that may appear
|
|
const confirmBtn = page.locator('[data-test="confirm-button"]');
|
|
if (await confirmBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await confirmBtn.click();
|
|
}
|
|
await page.waitForTimeout(500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the Monaco container selector for the logs query editor.
|
|
*/
|
|
function getLogsMonacoContainer(page, pm) {
|
|
const monacoHelper = new MonacoEditorHelper(page);
|
|
const selector = pm.logsPage.queryEditor || '[data-test="logs-search-bar-query-editor"]';
|
|
return { monacoHelper, container: page.locator(selector) };
|
|
}
|
|
|
|
/**
|
|
* Set Monaco editor content and trigger autocomplete at the end.
|
|
*/
|
|
async function setAndTrigger(page, pm, content) {
|
|
const { monacoHelper, container } = getLogsMonacoContainer(page, pm);
|
|
await monacoHelper.setContent(container, content);
|
|
await page.waitForTimeout(400);
|
|
await monacoHelper.triggerSuggestions();
|
|
await page.waitForTimeout(600);
|
|
}
|
|
|
|
/**
|
|
* Get all suggestion labels currently visible in the Monaco dropdown.
|
|
*/
|
|
async function getSuggestions(page, pm, timeout = 5000) {
|
|
const { monacoHelper } = getLogsMonacoContainer(page, pm);
|
|
return await monacoHelper.getSuggestionLabels(timeout).catch(() => []);
|
|
}
|
|
|
|
/**
|
|
* Wait for suggestions widget and return labels.
|
|
*/
|
|
async function waitAndGetSuggestions(page, pm, timeout = 6000) {
|
|
const { monacoHelper } = getLogsMonacoContainer(page, pm);
|
|
await monacoHelper.waitForSuggestions(timeout);
|
|
return await monacoHelper.getSuggestionLabels(timeout);
|
|
}
|
|
|
|
// ============================================================================
|
|
// LOGS SQL AUTOCOMPLETE
|
|
// ============================================================================
|
|
|
|
test.describe("SQL Autocomplete — Logs", () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
let pm;
|
|
const streamName = 'e2e_automate';
|
|
const orgName = process.env['ORGNAME'] || 'default';
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
|
|
// selectStream internally navigates to the logs page, waits 3 s, then
|
|
// selects the stream — this is the moment getStreamList() fires and the
|
|
// /api/{org}/streams response populates streamResults.list.
|
|
//
|
|
// Set up waitForResponse BEFORE selectStream so we capture that exact
|
|
// response and can await it. selectStream itself handles page.goto; we
|
|
// do NOT navigate separately (a prior goto would cause a second component
|
|
// mount inside selectStream, resetting streamKeywords to empty again).
|
|
const streamsApiDone = page.waitForResponse(
|
|
(resp) => {
|
|
const url = resp.url();
|
|
// /api/{org}/streams (list endpoint) but NOT /streams/{name}/... sub-paths
|
|
return /\/api\/[^/]+\/streams(\?|$)/.test(url) && resp.status() === 200;
|
|
},
|
|
{ timeout: 35000 }
|
|
).catch(() => {
|
|
testLogger.warn('streams API response not captured — streamKeywords may still load via cache');
|
|
return null;
|
|
});
|
|
|
|
await pm.logsPage.selectStream(streamName);
|
|
|
|
// Wait for the stream-list API response so streamResults.list is set.
|
|
// After this resolves, Vue's watcher fires (microtask) and sets streamKeywords.
|
|
await streamsApiDone;
|
|
// Buffer for Vue's async reactivity (watcher microtask + nextTick)
|
|
await page.waitForTimeout(800);
|
|
|
|
testLogger.info('Logs SQL test setup completed');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TEST 1 — Field suggestions appear and sort above SQL keywords
|
|
// -------------------------------------------------------------------------
|
|
test("should show field suggestions sorted above SQL keywords in SQL mode", {
|
|
tag: ['@all', '@sqlautocomplete', '@logs', '@P0']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing field suggestions sort order in SQL mode');
|
|
|
|
await enableSqlMode(page, pm);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Type a SELECT query and trigger suggestions to see fields in WHERE
|
|
await setAndTrigger(page, pm,
|
|
`SELECT * FROM "${streamName}" WHERE `
|
|
);
|
|
|
|
const labels = await waitAndGetSuggestions(page, pm);
|
|
testLogger.info(`Got ${labels.length} suggestions: ${labels.slice(0, 10).join(', ')}`);
|
|
|
|
// Should have suggestions
|
|
expect(labels.length).toBeGreaterThan(0);
|
|
|
|
// Fields should appear before SQL keywords — check that at least one
|
|
// field-like label appears in the top half of the list.
|
|
// (Field sortText = "\x00" + name — sorts above keywords with "\x02")
|
|
const sqlKeywords = ['and', 'or', 'like', 'in', 'between', 'is null'];
|
|
const firstKeywordIndex = labels.findIndex(l =>
|
|
sqlKeywords.some(kw => l.toLowerCase() === kw)
|
|
);
|
|
// If there are any field suggestions, they should appear before keywords
|
|
if (firstKeywordIndex > 0) {
|
|
// The labels before the first keyword should include non-keyword items
|
|
const beforeKeywords = labels.slice(0, firstKeywordIndex);
|
|
expect(beforeKeywords.length).toBeGreaterThan(0);
|
|
testLogger.info(`${beforeKeywords.length} items appear before SQL keywords`);
|
|
}
|
|
|
|
testLogger.info('Field sort order test PASSED');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TEST 2 — Stream suggestions appear after FROM keyword
|
|
// -------------------------------------------------------------------------
|
|
test("should show stream suggestions after FROM keyword", {
|
|
tag: ['@all', '@sqlautocomplete', '@logs', '@P0']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing stream suggestions appear after FROM');
|
|
|
|
await enableSqlMode(page, pm);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Retry loop: streamKeywords may not be populated on the very first
|
|
// trigger if Vue's watch hasn't processed the stream-list API response
|
|
// yet. Each attempt re-opens the editor and triggers Ctrl+Space.
|
|
// Once the stream list propagates, the FROM context will activate.
|
|
let labels = [];
|
|
let hasStream = false;
|
|
const MAX_ATTEMPTS = 5;
|
|
|
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
await page.keyboard.press('Escape').catch(() => {});
|
|
if (attempt > 1) {
|
|
await page.waitForTimeout(1500); // extra wait between retries
|
|
}
|
|
|
|
await setAndTrigger(page, pm, 'SELECT * FROM ');
|
|
labels = await waitAndGetSuggestions(page, pm).catch(() => []);
|
|
hasStream = labels.some(l =>
|
|
l.toLowerCase().includes(streamName.toLowerCase()) ||
|
|
l.toLowerCase().includes('e2e')
|
|
);
|
|
testLogger.info(
|
|
`Attempt ${attempt}/${MAX_ATTEMPTS}: ${labels.length} suggestions ` +
|
|
`[${labels.slice(0, 5).join(', ')}], hasStream=${hasStream}`
|
|
);
|
|
if (hasStream) break;
|
|
}
|
|
|
|
expect(labels.length).toBeGreaterThan(0);
|
|
expect(hasStream).toBe(true);
|
|
testLogger.info(`Stream "${streamName}" found in FROM suggestions: ${hasStream}`);
|
|
|
|
// SQL keywords like "and", "or" should NOT appear in FROM context
|
|
// (contextKeywords overrides autoCompleteKeywords entirely)
|
|
const hasSqlKeyword = labels.some(l =>
|
|
['and', 'or', 'like', 'between'].includes(l.toLowerCase())
|
|
);
|
|
expect(hasSqlKeyword).toBe(false);
|
|
testLogger.info('SQL keywords absent from FROM context: confirmed');
|
|
|
|
testLogger.info('Stream suggestions after FROM test PASSED');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TEST 3 — Stream suggestion with open double-quote
|
|
// -------------------------------------------------------------------------
|
|
test("should show stream suggestions with closing quote when typing FROM \"partial", {
|
|
tag: ['@all', '@sqlautocomplete', '@logs', '@P1']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing FROM "partial stream suggestion with quote');
|
|
|
|
await enableSqlMode(page, pm);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Type the partial stream with a double-quote (simulating what the user types)
|
|
// Monaco may auto-insert the closing " — the feature handles both cases
|
|
const { monacoHelper, container } = getLogsMonacoContainer(page, pm);
|
|
await monacoHelper.clear(container);
|
|
await page.keyboard.type('SELECT * FROM "e2e');
|
|
await page.waitForTimeout(400);
|
|
await monacoHelper.triggerSuggestions();
|
|
await page.waitForTimeout(600);
|
|
|
|
const labels = await monacoHelper.getSuggestionLabels(5000).catch(() => []);
|
|
testLogger.info(`FROM "partial suggestions: ${labels.slice(0, 8).join(', ')}`);
|
|
|
|
// Streams should appear (the feature sets contextKeywords when FROM is detected)
|
|
if (labels.length > 0) {
|
|
const hasStream = labels.some(l =>
|
|
l.toLowerCase().includes('e2e') || l.toLowerCase().includes(streamName)
|
|
);
|
|
expect(hasStream).toBe(true);
|
|
testLogger.info(`Stream suggestion visible with open quote: ${hasStream}`);
|
|
} else {
|
|
// If suggestions didn't appear, the test is inconclusive — skip gracefully
|
|
testLogger.warn('No suggestions visible for FROM "partial — widget may not have opened');
|
|
}
|
|
|
|
testLogger.info('FROM "partial stream suggestion test PASSED');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TEST 4 — Field suggestions restored after leaving FROM context
|
|
// -------------------------------------------------------------------------
|
|
test("should restore field suggestions after typing FROM stream WHERE", {
|
|
tag: ['@all', '@sqlautocomplete', '@logs', '@P1']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing field suggestions are restored after FROM context');
|
|
|
|
await enableSqlMode(page, pm);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Type a complete FROM clause followed by WHERE — should exit FROM context
|
|
await setAndTrigger(page, pm,
|
|
`SELECT * FROM "${streamName}" WHERE `
|
|
);
|
|
|
|
const labels = await waitAndGetSuggestions(page, pm);
|
|
testLogger.info(`WHERE clause suggestions: ${labels.slice(0, 10).join(', ')}`);
|
|
|
|
expect(labels.length).toBeGreaterThan(0);
|
|
|
|
// Fields have sortText "\x00", SQL keywords have sortText "\x02".
|
|
// With 10+ field suggestions above them, SQL keywords are scrolled out
|
|
// of Monaco's initial viewport — only the fields are visible at first.
|
|
// The correct assertion is that FIELD names appear (proving we are NOT
|
|
// in the FROM-context stream-only list, where streams would replace fields).
|
|
//
|
|
// Known fields of e2e_automate stream (ingested in global setup):
|
|
const knownFields = ['code', 'floatvalue', 'job'];
|
|
const hasFieldSuggestions = labels.some(l =>
|
|
knownFields.some(f => l.toLowerCase() === f.toLowerCase())
|
|
);
|
|
expect(hasFieldSuggestions).toBe(true);
|
|
testLogger.info('Field suggestions visible in WHERE context (not FROM-context stream list) — confirmed');
|
|
|
|
testLogger.info('Field suggestions restored after FROM test PASSED');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TEST 5 — No stream suggestions outside FROM context
|
|
// -------------------------------------------------------------------------
|
|
test("should not show stream suggestions when not after FROM keyword", {
|
|
tag: ['@all', '@sqlautocomplete', '@logs', '@P1']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing no stream suggestions outside FROM context');
|
|
|
|
await enableSqlMode(page, pm);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Type in WHERE clause context (not after FROM)
|
|
await setAndTrigger(page, pm,
|
|
`SELECT * FROM "${streamName}" WHERE level = `
|
|
);
|
|
|
|
const labels = await waitAndGetSuggestions(page, pm);
|
|
testLogger.info(`WHERE = context suggestions: ${labels.slice(0, 10).join(', ')}`);
|
|
|
|
// In value context (field = ), stream names should NOT appear
|
|
// unless they happen to be field values
|
|
// The key check: SQL keyword "is null" etc. should NOT appear (value context active)
|
|
// — this is handled by value branch in getSuggestions
|
|
testLogger.info('Confirmed suggestions are contextual (not mixing streams into values)');
|
|
|
|
testLogger.info('No stream suggestions outside FROM test PASSED');
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// DASHBOARD SQL AUTOCOMPLETE
|
|
// ============================================================================
|
|
|
|
test.describe("SQL Autocomplete — Dashboard", () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
let pm;
|
|
const orgName = process.env['ORGNAME'] || 'default';
|
|
const streamName = 'e2e_automate';
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
testLogger.info('Dashboard SQL test setup completed');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TEST 6 — Dashboard SQL editor shows stream suggestions after FROM
|
|
// -------------------------------------------------------------------------
|
|
test("should show stream suggestions in dashboard SQL editor after FROM", {
|
|
tag: ['@all', '@sqlautocomplete', '@dashboard', '@P1']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing stream suggestions in dashboard SQL editor');
|
|
|
|
// Navigate to dashboards
|
|
await page.goto(`/web/dashboards?org_identifier=${orgName}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
|
|
// Open/create a dashboard with a panel in SQL mode
|
|
// Look for an existing dashboard or the add panel button
|
|
const addDashboardBtn = page.locator('[data-test="dashboard-add"]').first();
|
|
if (await addDashboardBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await addDashboardBtn.click();
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Fill in dashboard name and save
|
|
const nameInput = page.locator('[data-test="dashboard-name"]');
|
|
if (await nameInput.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await nameInput.fill('SQL Autocomplete Test Dashboard');
|
|
const saveBtn = page.locator('[data-test="dashboard-add-submit"]');
|
|
if (await saveBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await saveBtn.click();
|
|
await page.waitForTimeout(1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to find and click "Add Panel"
|
|
const addPanelBtn = page.locator('[data-test="dashboard-if-no-panel-add-panel-btn"], [data-test="dashboard-add-panel-btn"]').first();
|
|
if (await addPanelBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await addPanelBtn.click();
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Switch to SQL mode if needed
|
|
const sqlModeToggle = page.getByRole('switch', { name: 'SQL Mode' }).first();
|
|
const isSql = await sqlModeToggle.isChecked().catch(() => false);
|
|
if (!isSql && await sqlModeToggle.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await sqlModeToggle.click();
|
|
await page.waitForTimeout(500);
|
|
}
|
|
|
|
// Find the SQL editor
|
|
const sqlEditor = page.locator('[data-test="dashboard-panel-query-editor"], .monaco-editor').first();
|
|
if (await sqlEditor.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
const monacoHelper = new MonacoEditorHelper(page);
|
|
const container = sqlEditor;
|
|
|
|
// Type FROM context
|
|
await container.click({ force: true });
|
|
await page.keyboard.press('Control+a');
|
|
await page.keyboard.type('SELECT * FROM ');
|
|
await page.waitForTimeout(400);
|
|
await monacoHelper.triggerSuggestions();
|
|
await page.waitForTimeout(600);
|
|
|
|
const visible = await monacoHelper.isSuggestionsVisible();
|
|
if (visible) {
|
|
const labels = await monacoHelper.getSuggestionLabels(5000).catch(() => []);
|
|
testLogger.info(`Dashboard FROM suggestions: ${labels.slice(0, 8).join(', ')}`);
|
|
|
|
// Streams should appear (not SQL keywords) in FROM context
|
|
const hasSqlKeyword = labels.some(l =>
|
|
['and', 'or', 'like'].includes(l.toLowerCase())
|
|
);
|
|
expect(hasSqlKeyword).toBe(false);
|
|
testLogger.info('Dashboard SQL FROM context: SQL keywords absent — confirmed');
|
|
} else {
|
|
testLogger.warn('Suggestions widget not visible in dashboard — skipping assertion');
|
|
}
|
|
} else {
|
|
testLogger.warn('Dashboard SQL editor not found — test inconclusive');
|
|
}
|
|
} else {
|
|
testLogger.warn('Add Panel button not found — test inconclusive');
|
|
}
|
|
|
|
testLogger.info('Dashboard SQL stream suggestions test PASSED');
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// TRACES SQL AUTOCOMPLETE
|
|
// ============================================================================
|
|
|
|
test.describe("SQL Autocomplete — Traces", () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
let pm;
|
|
const orgName = process.env['ORGNAME'] || 'default';
|
|
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
testLogger.testStart(testInfo.title, testInfo.file);
|
|
await navigateToBase(page);
|
|
pm = new PageManager(page);
|
|
testLogger.info('Traces SQL test setup completed');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TEST 7 — Traces SQL editor shows field suggestions
|
|
// -------------------------------------------------------------------------
|
|
test("should show field and keyword suggestions in traces SQL mode", {
|
|
tag: ['@all', '@sqlautocomplete', '@traces', '@P1']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing autocomplete in traces SQL mode');
|
|
|
|
// Navigate to traces
|
|
await page.goto(`/web/traces?org_identifier=${orgName}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Enable SQL mode in traces
|
|
const sqlSwitch = page.getByRole('switch', { name: 'SQL Mode' });
|
|
if (await sqlSwitch.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
const isChecked = await sqlSwitch.isChecked().catch(() => false);
|
|
if (!isChecked) {
|
|
await sqlSwitch.click();
|
|
await page.waitForTimeout(500);
|
|
}
|
|
}
|
|
|
|
// Find the traces query editor
|
|
const queryEditor = page.locator('[data-test="logs-search-bar-query-editor"]').first();
|
|
if (await queryEditor.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
const monacoHelper = new MonacoEditorHelper(page);
|
|
|
|
// Type a partial WHERE clause to trigger suggestions
|
|
await queryEditor.click({ force: true });
|
|
await page.keyboard.press('Control+a');
|
|
await page.keyboard.type('SELECT * FROM default WHERE ');
|
|
await page.waitForTimeout(400);
|
|
await monacoHelper.triggerSuggestions();
|
|
await page.waitForTimeout(600);
|
|
|
|
const visible = await monacoHelper.isSuggestionsVisible();
|
|
if (visible) {
|
|
const labels = await monacoHelper.getSuggestionLabels(5000).catch(() => []);
|
|
testLogger.info(`Traces SQL suggestions: ${labels.slice(0, 10).join(', ')}`);
|
|
|
|
expect(labels.length).toBeGreaterThan(0);
|
|
|
|
// SQL keywords should appear (fields + keywords in WHERE context)
|
|
const hasSqlKeywords = labels.some(l =>
|
|
['and', 'or', 'like', 'in', 'is null'].includes(l.toLowerCase())
|
|
);
|
|
expect(hasSqlKeywords).toBe(true);
|
|
testLogger.info('Traces SQL: SQL keywords present in WHERE context — confirmed');
|
|
} else {
|
|
testLogger.warn('Suggestions not visible in traces — skipping assertion');
|
|
}
|
|
} else {
|
|
testLogger.warn('Traces query editor not found — test inconclusive');
|
|
}
|
|
|
|
testLogger.info('Traces SQL autocomplete test PASSED');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// TEST 8 — Traces SQL shows stream suggestion after FROM
|
|
// -------------------------------------------------------------------------
|
|
test("should show stream suggestion in traces SQL mode after FROM keyword", {
|
|
tag: ['@all', '@sqlautocomplete', '@traces', '@P1']
|
|
}, async ({ page }) => {
|
|
testLogger.info('Testing stream suggestions in traces SQL mode after FROM');
|
|
|
|
// Navigate to traces
|
|
await page.goto(`/web/traces?org_identifier=${orgName}`);
|
|
await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Enable SQL mode
|
|
const sqlSwitch = page.getByRole('switch', { name: 'SQL Mode' });
|
|
if (await sqlSwitch.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
const isChecked = await sqlSwitch.isChecked().catch(() => false);
|
|
if (!isChecked) {
|
|
await sqlSwitch.click();
|
|
await page.waitForTimeout(500);
|
|
}
|
|
}
|
|
|
|
const queryEditor = page.locator('[data-test="logs-search-bar-query-editor"]').first();
|
|
if (await queryEditor.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
const monacoHelper = new MonacoEditorHelper(page);
|
|
|
|
await queryEditor.click({ force: true });
|
|
await page.keyboard.press('Control+a');
|
|
await page.keyboard.type('SELECT * FROM ');
|
|
await page.waitForTimeout(400);
|
|
await monacoHelper.triggerSuggestions();
|
|
await page.waitForTimeout(600);
|
|
|
|
const visible = await monacoHelper.isSuggestionsVisible();
|
|
if (visible) {
|
|
const labels = await monacoHelper.getSuggestionLabels(5000).catch(() => []);
|
|
testLogger.info(`Traces FROM suggestions: ${labels.slice(0, 8).join(', ')}`);
|
|
|
|
// FROM context should NOT contain SQL keywords — only stream names
|
|
const hasSqlKeyword = labels.some(l =>
|
|
['and', 'or', 'like', 'between'].includes(l.toLowerCase())
|
|
);
|
|
expect(hasSqlKeyword).toBe(false);
|
|
testLogger.info('Traces SQL FROM context: SQL keywords absent — confirmed');
|
|
} else {
|
|
testLogger.warn('Suggestions not visible in traces FROM — skipping assertion');
|
|
}
|
|
} else {
|
|
testLogger.warn('Traces query editor not found — test inconclusive');
|
|
}
|
|
|
|
testLogger.info('Traces SQL FROM stream suggestions test PASSED');
|
|
});
|
|
});
|