Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
85 changes: 61 additions & 24 deletions frontend/e2e/editor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { test, expect, runExampleAndExecute, expectToastVisible, describeAuthRequired } from './fixtures';
import {
test, expect, runExampleAndExecute, expectToastVisible, describeAuthRequired,
loadExampleScript, openScriptOptions, saveScriptAs, expandSavedScripts,
} from './fixtures';

const PATH = '/editor';

Expand Down Expand Up @@ -35,10 +38,7 @@

test('shows file actions when panel opened', async ({ userPage }) => {
await userPage.goto(PATH);
// Use the button's accessible name from sr-only text
const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' });
await expect(optionsToggle).toBeVisible();
await optionsToggle.click();
await openScriptOptions(userPage);
await expect(userPage.getByText('File Actions')).toBeVisible();
await expect(userPage.getByRole('button', { name: /New/i })).toBeVisible();
await expect(userPage.getByRole('button', { name: /Upload/i })).toBeVisible();
Expand All @@ -48,10 +48,8 @@

test('can load example script', async ({ userPage }) => {
await userPage.goto(PATH);
await userPage.getByRole('button', { name: /Example/i }).click();
const editor = userPage.locator('.cm-content');
await expect(editor).not.toBeEmpty({ timeout: 3000 });
const content = await editor.textContent();
await loadExampleScript(userPage);
const content = await userPage.locator('.cm-content').textContent();
expect(content).toBeTruthy();
expect(content!.length).toBeGreaterThan(0);
});
Expand All @@ -74,7 +72,7 @@
await userPage.goto(PATH);
const result = await runExampleAndExecute(userPage);
expect(result.status).toBe('completed');
expect(result.exit_code).toBe(0);

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

View workflow job for this annotation

GitHub Actions / Frontend E2E (1/2)

[chromium] › e2e/editor.spec.ts:71:3 › Editor Execution › can execute simple python script

1) [chromium] › e2e/editor.spec.ts:71:3 › Editor Execution › can execute simple python script ──── Error: expect(received).toBe(expected) // Object.is equality Expected: 0 Received: 1 73 | const result = await runExampleAndExecute(userPage); 74 | expect(result.status).toBe('completed'); > 75 | expect(result.exit_code).toBe(0); | ^ 76 | expect(result.stdout ?? '').toContain('Hello from a Python'); 77 | }); 78 | at /home/runner/work/Integr8sCode/Integr8sCode/frontend/e2e/editor.spec.ts:75:30
expect(result.stdout ?? '').toContain('Hello from a Python');
});

Expand All @@ -97,8 +95,7 @@

test('run button is disabled during execution', async ({ userPage }) => {
await userPage.goto(PATH);
await userPage.getByRole('button', { name: /Example/i }).click();
await expect(userPage.locator('.cm-content')).not.toBeEmpty({ timeout: 3000 });
await loadExampleScript(userPage);
const runButton = userPage.getByRole('button', { name: /Run Script/i });
await runButton.click();
const executingButton = userPage.getByRole('button', { name: /Executing/i });
Expand All @@ -110,31 +107,71 @@
test.describe('Editor Script Management', () => {
test('can save script when authenticated', async ({ userPage }) => {
await userPage.goto(PATH);
await userPage.getByRole('button', { name: /Example/i }).click();
await expect(userPage.locator('.cm-content')).not.toBeEmpty({ timeout: 3000 });
await userPage.locator('#scriptNameInput').fill(`Test Script ${Date.now()}`);
const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' });
await optionsToggle.click();
await userPage.locator('button[title="Save current script"]').click();
await expectToastVisible(userPage);
await saveScriptAs(userPage, `Test Script ${Date.now()}`);
});

test('can create new script', async ({ userPage }) => {
await userPage.goto(PATH);
await userPage.getByRole('button', { name: /Example/i }).click();
await expect(userPage.locator('.cm-content')).not.toBeEmpty({ timeout: 3000 });
const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' });
await optionsToggle.click();
await loadExampleScript(userPage);
await openScriptOptions(userPage);
await userPage.getByRole('button', { name: /New/i }).click();
await expect(userPage.locator('#scriptNameInput')).toHaveValue('');
});

test('shows saved scripts section when authenticated', async ({ userPage }) => {
await userPage.goto(PATH);
const optionsToggle = userPage.getByRole('button', { name: 'Toggle Script Options' });
await optionsToggle.click();
await openScriptOptions(userPage);
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()}`;
await saveScriptAs(userPage, scriptName);

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

// Expand the saved scripts list to find the saved script
await expandSavedScripts(userPage);

const savedScript = userPage.locator(`text="${scriptName}"`).first();
await expect(savedScript).toBeVisible({ timeout: 3000 });
await savedScript.click();
await expectToastVisible(userPage);
await expect(userPage.locator('#scriptNameInput')).toHaveValue(scriptName);
});

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

// Expand the saved scripts list to find the saved script
await expandSavedScripts(userPage);

const scriptRow = userPage.locator(`text="${scriptName}"`).first();
await expect(scriptRow).toBeVisible({ timeout: 3000 });
const deleteBtn = userPage.locator(`button[title="Delete ${scriptName}"]`);
await expect(deleteBtn).toBeVisible({ timeout: 2000 });
userPage.on('dialog', dialog => dialog.accept());
await deleteBtn.click();
await expectToastVisible(userPage);
});

test('can export script as file', async ({ userPage }) => {
await userPage.goto(PATH);
await loadExampleScript(userPage);
await userPage.locator('#scriptNameInput').fill('export-test');
await openScriptOptions(userPage);

const [download] = await Promise.all([
userPage.waitForEvent('download', { timeout: 5000 }),
userPage.getByRole('button', { name: /Export/i }).click(),
]);
expect(download.suggestedFilename()).toContain('export-test');
});
});

describeAuthRequired(test, PATH);
Loading
Loading