Skip to content

Commit 66b52a7

Browse files
Plataneclaude
andcommitted
Add Quickwit datasource e2e test
Create datasource via UI, ingest dummy logs into Quickwit, and verify they appear in Explore. Uses unique runId per attempt for test isolation with index cleanup after each run. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cf882d8 commit 66b52a7

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

tests/quickwit.spec.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { test, expect } from '@grafana/plugin-e2e';
2+
3+
// Grafana (inside Docker) reaches Quickwit via the docker service name.
4+
// The test process (on the host) reaches the same Quickwit via localhost port mapping.
5+
const QUICKWIT_URL = 'http://quickwit:7280/api/v1';
6+
const QUICKWIT_INGEST_URL = 'http://localhost:7280/api/v1';
7+
const INDEX = 'otel-logs-v0_9';
8+
9+
let runId: string;
10+
let ingestDone: Promise<void>;
11+
12+
test.beforeEach(async () => {
13+
runId = `e2e-${Date.now()}`;
14+
ingestDone = ingestDummyLogs(runId);
15+
});
16+
17+
test.afterEach(async () => {
18+
await fetch(`${QUICKWIT_INGEST_URL}/indexes/${INDEX}/clear`, { method: 'PUT' }).catch(() => {});
19+
});
20+
21+
test('create datasource and explore logs', async ({ page }) => {
22+
let datasourceUid: string;
23+
24+
await test.step('look for the plugin in the list of datasources', async () => {
25+
await page.goto('/connections/datasources/new');
26+
await page.getByPlaceholder('Filter by name or type').fill('quickwit');
27+
await expect(page.getByText('Quickwit', { exact: true })).toBeVisible();
28+
});
29+
30+
await test.step('create datasource via UI', async () => {
31+
await page.goto('/connections/datasources/new');
32+
await page.getByPlaceholder('Filter by name or type').fill('quickwit');
33+
await page.getByText('Quickwit', { exact: true }).click();
34+
35+
// Wait for the config form to load
36+
await expect(page.getByText('Index settings')).toBeVisible();
37+
38+
// Fill required fields
39+
await page.getByRole('textbox', { name: /URL/ }).fill(QUICKWIT_URL);
40+
await page.getByRole('textbox', { name: 'Index ID' }).fill(INDEX);
41+
42+
// Save & test
43+
await page.getByRole('button', { name: 'Save & test' }).click();
44+
await expect(page.getByText('plugin is running')).toBeVisible();
45+
46+
// Extract datasource UID from URL for cleanup
47+
const url = page.url();
48+
const match = url.match(/\/datasources\/edit\/([^/]+)/);
49+
expect(match).toBeDefined();
50+
datasourceUid = match?.[1] ?? '';
51+
});
52+
53+
await test.step('wait for quickwit logs to be ingested', async () => {
54+
await ingestDone;
55+
});
56+
57+
await test.step('explore logs returns hits', async () => {
58+
// Navigate to explore from the datasource config page
59+
await page.goto(`/connections/datasources/edit/${datasourceUid}`);
60+
await page.getByRole('link', { name: 'Explore data' }).click();
61+
62+
await expect(page.getByText(`log-1-${runId}`).first()).toBeVisible();
63+
await expect(page.getByText(`log-2-${runId}`).first()).toBeVisible();
64+
});
65+
});
66+
67+
/**
68+
* call quickwit ingest API to ingest dummy logs
69+
*/
70+
async function ingestDummyLogs(runId: string) {
71+
const now = Date.now() * 1_000_000; // nanoseconds
72+
const ndjson = [
73+
{
74+
timestamp_nanos: now,
75+
service_name: runId,
76+
severity_text: 'INFO',
77+
body: { message: `log-1-${runId}` },
78+
},
79+
{
80+
timestamp_nanos: now - 1_000_000_000,
81+
service_name: runId,
82+
severity_text: 'WARN',
83+
body: { message: `log-2-${runId}` },
84+
},
85+
]
86+
.map((l) => JSON.stringify(l))
87+
.join('\n');
88+
89+
const res = await fetch(`${QUICKWIT_INGEST_URL}/${INDEX}/ingest?commit=wait_for`, {
90+
method: 'POST',
91+
headers: { 'Content-Type': 'application/json' },
92+
body: ndjson,
93+
});
94+
if (!res.ok) {
95+
throw new Error(`Ingest failed: ${res.status} ${await res.text()}`);
96+
}
97+
98+
// Verify logs are indexed and searchable
99+
const search = await fetch(`${QUICKWIT_INGEST_URL}/${INDEX}/search`, {
100+
method: 'POST',
101+
headers: { 'Content-Type': 'application/json' },
102+
body: JSON.stringify({ query: `service_name:${runId}` }),
103+
});
104+
const { num_hits } = await search.json();
105+
if (num_hits === 0) {
106+
throw new Error('Ingest failed: no searchable logs');
107+
}
108+
}

0 commit comments

Comments
 (0)