mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-03 16:10:21 +00:00
Co-authored-by: Shrinath-O2 <214651540+Shrinath-O2@users.noreply.github.com> Co-authored-by: Neha P <neha@openobserve.ai>
162 lines
5.4 KiB
JavaScript
162 lines
5.4 KiB
JavaScript
// API seeding for the workflow-folders specs.
|
|
//
|
|
// The folder assertions are about listing, scoping and moving — not about building a graph
|
|
// on the canvas — so every fixture is created through the REST API. That keeps the spec fast
|
|
// and, more importantly, keeps it off the canvas paths that need a live destination (which
|
|
// the SSRF guard refuses to create on *.common-dev envs).
|
|
//
|
|
// `page.request` shares the browser context's cookies, so this works on cloud (OIDC session)
|
|
// and self-hosted (Basic auth header) alike.
|
|
|
|
const { getAuthHeaders, getOrgIdentifier } = require('../../playwright-tests/utils/cloud-auth.js');
|
|
|
|
const api = () => `${process.env.ZO_BASE_URL}/api`;
|
|
const SINK_URL = 'http://example.com/sink';
|
|
|
|
// Maps the trigger registry's per-node kind to the top-level backend enum value.
|
|
const TRIGGER_TYPE_BY_KIND = {
|
|
alert_fired: 'AlertFired',
|
|
incident_event: 'IncidentEvent',
|
|
};
|
|
|
|
async function jsonOrThrow(resp, what) {
|
|
if (!resp.ok()) {
|
|
throw new Error(`${what} failed: ${resp.status()} ${(await resp.text()).slice(0, 300)}`);
|
|
}
|
|
return await resp.json();
|
|
}
|
|
|
|
async function createFolder(page, name, description = 'workflow folder automation') {
|
|
const resp = await page.request.post(`${api()}/v2/${getOrgIdentifier()}/folders/workflows`, {
|
|
headers: getAuthHeaders(),
|
|
data: { name, description },
|
|
});
|
|
const body = await jsonOrThrow(resp, `create folder "${name}"`);
|
|
return body.folderId;
|
|
}
|
|
|
|
async function deleteFolder(page, folderId) {
|
|
return await page.request.delete(
|
|
`${api()}/v2/${getOrgIdentifier()}/folders/workflows/${folderId}`,
|
|
{ headers: getAuthHeaders() }
|
|
);
|
|
}
|
|
|
|
async function createPipelineDestination(page, name) {
|
|
const resp = await page.request.post(
|
|
`${api()}/${getOrgIdentifier()}/alerts/destinations?module=pipeline`,
|
|
{
|
|
headers: getAuthHeaders(),
|
|
// NOT loopback: the SSRF guard rejects private/localhost URLs at destination-save
|
|
// time. example.com is IANA-reserved and never contacted — no spec fires the workflow.
|
|
data: { name, url: SINK_URL, method: 'post', type: 'http' },
|
|
}
|
|
);
|
|
await jsonOrThrow(resp, `create pipeline destination "${name}"`);
|
|
return name;
|
|
}
|
|
|
|
async function deleteDestination(page, name) {
|
|
return await page.request.delete(
|
|
`${api()}/${getOrgIdentifier()}/alerts/destinations/${name}`,
|
|
{ headers: getAuthHeaders() }
|
|
);
|
|
}
|
|
|
|
function workflowPayload(
|
|
name,
|
|
destName,
|
|
{ description = 'folder automation workflow', triggerKind = 'alert_fired' } = {},
|
|
) {
|
|
const triggerType = TRIGGER_TYPE_BY_KIND[triggerKind];
|
|
if (!triggerType) {
|
|
throw new Error(`workflowPayload: unmapped triggerKind "${triggerKind}"`);
|
|
}
|
|
return {
|
|
workflow: {
|
|
id: '',
|
|
org_id: '',
|
|
created_at: 0,
|
|
updated_at: 0,
|
|
created_by: '',
|
|
name,
|
|
description,
|
|
enabled: true,
|
|
nodes: [
|
|
{
|
|
id: 'trigger-1',
|
|
data: { node_type: 'workflow_trigger' },
|
|
// NodeData::WorkflowTrigger is a backend unit variant, so the kind
|
|
// survives only in meta (strings) — the same field the editor serializes.
|
|
meta: { trigger_kind: triggerKind },
|
|
position: { x: 100, y: 100 },
|
|
io_type: 'input',
|
|
},
|
|
{
|
|
id: 'dest-1',
|
|
data: { node_type: 'destination', destination_id: destName, template_override: null },
|
|
position: { x: 400, y: 100 },
|
|
io_type: 'output',
|
|
},
|
|
],
|
|
edges: [{ id: 'etrigger-1-dest-1', source: 'trigger-1', target: 'dest-1' }],
|
|
},
|
|
trigger_type: triggerType,
|
|
};
|
|
}
|
|
|
|
// The backend lowercases and trims the name on save, so the caller's name is not
|
|
// necessarily the one the row (and therefore the row's data-test) carries.
|
|
async function createWorkflow(
|
|
page,
|
|
{ name, destName, folderId, draft = false, triggerKind = 'alert_fired' },
|
|
) {
|
|
const params = new URLSearchParams();
|
|
if (folderId) params.set('folder', folderId);
|
|
if (draft) params.set('draft', 'true');
|
|
const qs = params.toString() ? `?${params}` : '';
|
|
const resp = await page.request.post(`${api()}/${getOrgIdentifier()}/workflows${qs}`, {
|
|
headers: getAuthHeaders(),
|
|
data: workflowPayload(name, destName, { triggerKind }),
|
|
});
|
|
const body = await jsonOrThrow(resp, `create workflow "${name}"`);
|
|
return { id: body.id, name: name.trim().toLowerCase() };
|
|
}
|
|
|
|
async function deleteWorkflow(page, workflowId) {
|
|
return await page.request.delete(
|
|
`${api()}/${getOrgIdentifier()}/workflows/${workflowId}`,
|
|
{ headers: getAuthHeaders() }
|
|
);
|
|
}
|
|
|
|
async function listWorkflows(page, params = {}) {
|
|
const qs = new URLSearchParams(params).toString();
|
|
const resp = await page.request.get(
|
|
`${api()}/${getOrgIdentifier()}/workflows${qs ? `?${qs}` : ''}`,
|
|
{ headers: getAuthHeaders() }
|
|
);
|
|
const body = await jsonOrThrow(resp, 'list workflows');
|
|
return Array.isArray(body) ? body : (body.list ?? body.data ?? []);
|
|
}
|
|
|
|
// Empty a folder before deleting it — a folder holding a workflow OR a draft refuses
|
|
// to delete, so a failed test would otherwise leak the folder permanently.
|
|
async function purgeFolder(page, folderId) {
|
|
for (const row of await listWorkflows(page, { folder: folderId })) {
|
|
await deleteWorkflow(page, row.id);
|
|
}
|
|
await deleteFolder(page, folderId);
|
|
}
|
|
|
|
module.exports = {
|
|
createFolder,
|
|
deleteFolder,
|
|
purgeFolder,
|
|
createPipelineDestination,
|
|
deleteDestination,
|
|
createWorkflow,
|
|
deleteWorkflow,
|
|
listWorkflows,
|
|
};
|