Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 4 additions & 176 deletions docs/reference/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1859,28 +1859,6 @@
}
}
},
"/api/v1/events/health": {
"get": {
"tags": [
"sse"
],
"summary": "Sse Health",
"description": "Get SSE service health status.",
"operationId": "sse_health_api_v1_events_health_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SSEHealthResponse"
}
}
}
}
}
}
},
"/api/v1/events/executions/{execution_id}/events": {
"get": {
"tags": [
Expand Down Expand Up @@ -9068,14 +9046,7 @@
"title": "Exit Code"
},
"error_type": {
"anyOf": [
{
"$ref": "#/components/schemas/ExecutionErrorType"
},
{
"type": "null"
}
]
"$ref": "#/components/schemas/ExecutionErrorType"
},
"error_message": {
"type": "string",
Expand Down Expand Up @@ -9896,7 +9867,6 @@
"saga_commands",
"dead_letter_queue",
"dlq_events",
"event_bus_stream",
"websocket_events"
],
"title": "KafkaTopic",
Expand Down Expand Up @@ -13104,10 +13074,7 @@
"enum": [
"connected",
"subscribed",
"heartbeat",
"shutdown",
"status",
"error"
"status"
],
"title": "SSEControlEvent",
"description": "Control events for execution SSE streams (not from Kafka)."
Expand Down Expand Up @@ -13178,31 +13145,7 @@
}
],
"title": "Message",
"description": "Human-readable message (heartbeat, shutdown)"
},
"grace_period": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Grace Period",
"description": "Shutdown grace period in seconds"
},
"error": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Error",
"description": "Error message (error event)"
"description": "Human-readable message (subscribed event)"
},
"status": {
"anyOf": [
Expand Down Expand Up @@ -13294,71 +13237,6 @@
"title": "SSEExecutionEventData",
"description": "Typed model for SSE execution stream event payload.\n\nThis represents the JSON data sent inside each SSE message for execution streams.\nAll fields except event_type and execution_id are optional since different\nevent types carry different data."
},
"SSEHealthResponse": {
"properties": {
"status": {
"$ref": "#/components/schemas/SSEHealthStatus",
"description": "Health status: healthy or draining"
},
"kafka_enabled": {
"type": "boolean",
"title": "Kafka Enabled",
"description": "Whether Kafka features are enabled",
"default": true
},
"active_connections": {
"type": "integer",
"title": "Active Connections",
"description": "Total number of active SSE connections"
},
"active_executions": {
"type": "integer",
"title": "Active Executions",
"description": "Number of executions being monitored"
},
"active_consumers": {
"type": "integer",
"title": "Active Consumers",
"description": "Number of active Kafka consumers"
},
"max_connections_per_user": {
"type": "integer",
"title": "Max Connections Per User",
"description": "Maximum connections allowed per user"
},
"shutdown": {
"$ref": "#/components/schemas/ShutdownStatusResponse",
"description": "Shutdown status information"
},
"timestamp": {
"type": "string",
"format": "date-time",
"title": "Timestamp",
"description": "Health check timestamp"
}
},
"type": "object",
"required": [
"status",
"active_connections",
"active_executions",
"active_consumers",
"max_connections_per_user",
"shutdown",
"timestamp"
],
"title": "SSEHealthResponse",
"description": "Response model for SSE health check."
},
"SSEHealthStatus": {
"type": "string",
"enum": [
"healthy",
"draining"
],
"title": "SSEHealthStatus",
"description": "Health status for SSE service."
},
"SagaCancellationResponse": {
"properties": {
"success": {
Expand Down Expand Up @@ -14782,57 +14660,6 @@
"title": "SettingsHistoryResponse",
"description": "Response model for settings history (limited snapshot of recent changes)"
},
"ShutdownStatusResponse": {
"properties": {
"phase": {
"type": "string",
"title": "Phase",
"description": "Current shutdown phase"
},
"initiated": {
"type": "boolean",
"title": "Initiated",
"description": "Whether shutdown has been initiated"
},
"complete": {
"type": "boolean",
"title": "Complete",
"description": "Whether shutdown is complete"
},
"active_connections": {
"type": "integer",
"title": "Active Connections",
"description": "Number of active connections"
},
"draining_connections": {
"type": "integer",
"title": "Draining Connections",
"description": "Number of connections being drained"
},
"duration": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Duration",
"description": "Duration of shutdown in seconds"
}
},
"type": "object",
"required": [
"phase",
"initiated",
"complete",
"active_connections",
"draining_connections"
],
"title": "ShutdownStatusResponse",
"description": "Response model for shutdown status."
},
"SortOrder": {
"type": "string",
"enum": [
Expand Down Expand Up @@ -15984,6 +15811,7 @@
"title": "Reason"
}
},
"additionalProperties": true,
"type": "object",
"required": [
"event_id",
Expand Down
71 changes: 71 additions & 0 deletions frontend/e2e/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,77 @@
await optionsToggle.click();
await expect(userPage.getByRole('heading', { name: 'Saved Scripts' })).toBeVisible();
});

test('can load a previously saved script', async ({ userPage }) => {
await userPage.goto(PATH);
const scriptName = `Load Test ${Date.now()}`;

// Save a script first
await userPage.getByRole('button', { name: /Example/i }).click();
await expect(userPage.locator('.cm-content')).not.toBeEmpty({ timeout: 3000 });
await userPage.locator('#scriptNameInput').fill(scriptName);
const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' });
await optionsToggle.click();
await userPage.locator('button[title="Save current script"]').click();
await expectToastVisible(userPage);

// Create new script to clear state
await userPage.getByRole('button', { name: /New/i }).click();
await expect(userPage.locator('#scriptNameInput')).toHaveValue('');

// Find and load the saved script
const savedScript = userPage.locator(`text="${scriptName}"`).first();
if (await savedScript.isVisible({ timeout: 3000 }).catch(() => false)) {
await savedScript.click();
await expectToastVisible(userPage);
await expect(userPage.locator('#scriptNameInput')).toHaveValue(scriptName);
}
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
});

test('can delete a saved script', async ({ userPage }) => {
await userPage.goto(PATH);
const scriptName = `Delete Test ${Date.now()}`;

// Save a script first
await userPage.getByRole('button', { name: /Example/i }).click();

Check failure on line 170 in frontend/e2e/editor.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend E2E (2/2)

[chromium] › e2e/editor.spec.ts:165:3 › Editor Script Management › can delete a saved script

1) [chromium] › e2e/editor.spec.ts:165:3 › Editor Script Management › can delete a saved script ── Error: locator.click: Error: strict mode violation: getByRole('button', { name: /Example/i }) resolved to 2 elements: 1) <button title="Load an example script for the selected language" class="btn btn-secondary-outline btn-sm inline-flex items-center space-x-1.5">…</button> aka getByRole('button', { name: 'Example', exact: true }) 2) <button class="btn btn-primary inline-flex items-center space-x-2 pointer-events-auto">…</button> aka getByRole('button', { name: 'Start with an Example' }) Call log: - waiting for getByRole('button', { name: /Example/i }) 168 | 169 | // Save a script first > 170 | await userPage.getByRole('button', { name: /Example/i }).click(); | ^ 171 | await expect(userPage.locator('.cm-content')).not.toBeEmpty({ timeout: 3000 }); 172 | await userPage.locator('#scriptNameInput').fill(scriptName); 173 | const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' }); at /home/runner/work/Integr8sCode/Integr8sCode/frontend/e2e/editor.spec.ts:170:62
await expect(userPage.locator('.cm-content')).not.toBeEmpty({ timeout: 3000 });
await userPage.locator('#scriptNameInput').fill(scriptName);
const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' });
await optionsToggle.click();
await userPage.locator('button[title="Save current script"]').click();
await expectToastVisible(userPage);

// Find the delete button near the saved script and click it
const scriptRow = userPage.locator(`text="${scriptName}"`).first();
if (await scriptRow.isVisible({ timeout: 3000 }).catch(() => false)) {
// Accept the confirm dialog
userPage.on('dialog', dialog => dialog.accept());
const deleteBtn = scriptRow.locator('..').locator('button').filter({ has: userPage.locator('svg') }).last();
if (await deleteBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
await deleteBtn.click();
await expectToastVisible(userPage);
}
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
}
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
});

test('can export script as file', async ({ userPage }) => {
await userPage.goto(PATH);
await userPage.getByRole('button', { name: /Example/i }).click();

Check failure on line 193 in frontend/e2e/editor.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend E2E (2/2)

[chromium] › e2e/editor.spec.ts:191:3 › Editor Script Management › can export script as file

2) [chromium] › e2e/editor.spec.ts:191:3 › Editor Script Management › can export script as file ── Error: locator.click: Error: strict mode violation: getByRole('button', { name: /Example/i }) resolved to 2 elements: 1) <button title="Load an example script for the selected language" class="btn btn-secondary-outline btn-sm inline-flex items-center space-x-1.5">…</button> aka getByRole('button', { name: 'Example', exact: true }) 2) <button class="btn btn-primary inline-flex items-center space-x-2 pointer-events-auto">…</button> aka getByRole('button', { name: 'Start with an Example' }) Call log: - waiting for getByRole('button', { name: /Example/i }) 191 | test('can export script as file', async ({ userPage }) => { 192 | await userPage.goto(PATH); > 193 | await userPage.getByRole('button', { name: /Example/i }).click(); | ^ 194 | await expect(userPage.locator('.cm-content')).not.toBeEmpty({ timeout: 3000 }); 195 | await userPage.locator('#scriptNameInput').fill('export-test'); 196 | const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' }); at /home/runner/work/Integr8sCode/Integr8sCode/frontend/e2e/editor.spec.ts:193:62
await expect(userPage.locator('.cm-content')).not.toBeEmpty({ timeout: 3000 });
await userPage.locator('#scriptNameInput').fill('export-test');
const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' });
await optionsToggle.click();

// Listen for download
const [download] = await Promise.all([
Comment thread
HardMax71 marked this conversation as resolved.
userPage.waitForEvent('download', { timeout: 5000 }).catch(() => null),
userPage.getByRole('button', { name: /Export/i }).click(),
]);
// Download may or may not trigger depending on browser handling
if (download) {
expect(download.suggestedFilename()).toContain('export-test');
}
});
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
});

describeAuthRequired(test, PATH);
41 changes: 40 additions & 1 deletion frontend/e2e/notifications.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { test, expect, describeAuthRequired } from './fixtures';
import { test, expect, describeAuthRequired, expectToastVisible } from './fixtures';

const PATH = '/notifications';
const HEADING = 'Notifications';
Expand Down Expand Up @@ -50,7 +50,7 @@
const notificationCard = userPage.locator('[aria-label="Mark notification as read"]');
const hasEmptyState = await emptyState.isVisible({ timeout: 3000 }).catch(() => false);
const hasNotifications = await notificationCard.first().isVisible({ timeout: 3000 }).catch(() => false);
expect(hasEmptyState || hasNotifications).toBe(true);

Check failure on line 53 in frontend/e2e/notifications.spec.ts

View workflow job for this annotation

GitHub Actions / Frontend E2E (2/2)

[chromium] › e2e/notifications.spec.ts:46:3 › Notifications Page › shows empty state or notifications

3) [chromium] › e2e/notifications.spec.ts:46:3 › Notifications Page › shows empty state or notifications Error: expect(received).toBe(expected) // Object.is equality Expected: true Received: false 51 | const hasEmptyState = await emptyState.isVisible({ timeout: 3000 }).catch(() => false); 52 | const hasNotifications = await notificationCard.first().isVisible({ timeout: 3000 }).catch(() => false); > 53 | expect(hasEmptyState || hasNotifications).toBe(true); | ^ 54 | }); 55 | }); 56 | at /home/runner/work/Integr8sCode/Integr8sCode/frontend/e2e/notifications.spec.ts:53:47
});
});

Expand Down Expand Up @@ -83,6 +83,45 @@
});
});

test.describe('Notification Actions', () => {
test('can mark notification as read by clicking', async ({ userPage }) => {
await gotoAndWaitForNotifications(userPage);
const notificationCard = userPage.locator('[aria-label="Mark notification as read"]').first();
if (await notificationCard.isVisible({ timeout: 3000 }).catch(() => false)) {
// Check if it's unread (has blue background class)
const hasBlue = await notificationCard.evaluate(el => el.classList.toString().includes('bg-blue'));
await notificationCard.click();
// After clicking, check if styling changed or "Read" label appeared
if (hasBlue) {
await expect(
notificationCard.locator('text=Read').or(notificationCard)
).toBeVisible({ timeout: 3000 });
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
}
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
}
});

test('can delete a notification', async ({ userPage }) => {
await gotoAndWaitForNotifications(userPage);
const notificationCard = userPage.locator('[aria-label="Mark notification as read"]').first();
if (await notificationCard.isVisible({ timeout: 3000 }).catch(() => false)) {
const deleteBtn = notificationCard.locator('button').filter({ has: userPage.locator('svg') }).first();
if (await deleteBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
await deleteBtn.click();
await expectToastVisible(userPage);
}
}
});

test('can mark all as read', async ({ userPage }) => {
await gotoAndWaitForNotifications(userPage);
const markAllBtn = userPage.getByRole('button', { name: /mark all as read/i });
if (await markAllBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
await markAllBtn.click();
await expectToastVisible(userPage);
}
});
});

test.describe('Notification Center Header Component', () => {
test('shows notification icon in header when authenticated', async ({ userPage }) => {
await userPage.goto(PATH);
Expand Down
23 changes: 23 additions & 0 deletions frontend/e2e/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,29 @@ test.describe('Settings Save and History', () => {
await userPage.getByRole('button', { name: 'Close', exact: true }).click();
await expect(userPage.getByRole('heading', { name: 'Settings History' })).not.toBeVisible();
});

test('can restore settings from history', async ({ userPage }) => {
// First make a change and save to ensure history exists
await userPage.goto(PATH);
await userPage.getByRole('button', { name: 'Editor' }).click();
const fontSizeInput = userPage.locator('#font-size');
const currentValue = await fontSizeInput.inputValue();
await fontSizeInput.fill(currentValue === '14' ? '15' : '14');
await userPage.getByRole('button', { name: 'Save Settings' }).click();
await expectToastVisible(userPage);

// Open history and look for restore button
await userPage.getByRole('button', { name: 'View History' }).click();
await expect(userPage.getByRole('heading', { name: 'Settings History' })).toBeVisible();

const restoreBtn = userPage.getByRole('button', { name: 'Restore' }).first();
if (await restoreBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
// Accept the confirm dialog
userPage.on('dialog', dialog => dialog.accept());
await restoreBtn.click();
await expectToastVisible(userPage);
}
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
Comment thread
HardMax71 marked this conversation as resolved.
Outdated
});
});

test.describe('Settings Access Control', () => {
Expand Down
Loading
Loading