827 lines
23 KiB
TypeScript
827 lines
23 KiB
TypeScript
/**
|
|
* Workflow API (GraphicalEditor)
|
|
* Node types and graph execution for n8n-style flows.
|
|
*/
|
|
|
|
import type { ApiRequestOptions } from '../hooks/useApi';
|
|
|
|
const LOG = '[Workflow]';
|
|
|
|
// ============================================================================
|
|
// TYPES
|
|
// ============================================================================
|
|
|
|
export interface NodeTypeParameter {
|
|
name: string;
|
|
type: string;
|
|
required?: boolean;
|
|
description?: string;
|
|
default?: unknown;
|
|
frontendType?: string;
|
|
frontendOptions?: Record<string, unknown>;
|
|
options?: unknown[];
|
|
validation?: Record<string, unknown>;
|
|
}
|
|
|
|
export interface PortField {
|
|
name: string;
|
|
type: string;
|
|
description: Record<string, string>;
|
|
required: boolean;
|
|
}
|
|
|
|
export interface PortSchema {
|
|
name: string;
|
|
fields: PortField[];
|
|
}
|
|
|
|
export interface InputPortDef {
|
|
accepts: string[];
|
|
}
|
|
|
|
export interface OutputPortDef {
|
|
schema: string;
|
|
dynamic?: boolean;
|
|
deriveFrom?: string;
|
|
}
|
|
|
|
export interface NodeType {
|
|
id: string;
|
|
category: string;
|
|
label: string;
|
|
description: string;
|
|
parameters: NodeTypeParameter[];
|
|
inputs: number;
|
|
outputs: number;
|
|
outputLabels?: string[];
|
|
executor: string;
|
|
inputPorts?: Record<number, InputPortDef>;
|
|
outputPorts?: Record<number, OutputPortDef>;
|
|
meta?: {
|
|
icon?: string;
|
|
color?: string;
|
|
/** True if this node performs an LLM / AI call (credits). */
|
|
usesAi?: boolean;
|
|
method?: string;
|
|
action?: string;
|
|
};
|
|
}
|
|
|
|
export interface NodeTypeCategory {
|
|
id: string;
|
|
label: Record<string, string> | string;
|
|
}
|
|
|
|
export interface SystemVariable {
|
|
type: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface NodeTypesResponse {
|
|
nodeTypes: NodeType[];
|
|
categories: NodeTypeCategory[];
|
|
portTypeCatalog?: Record<string, PortSchema>;
|
|
systemVariables?: Record<string, SystemVariable>;
|
|
}
|
|
|
|
export interface Automation2GraphNode {
|
|
id: string;
|
|
type: string;
|
|
parameters?: Record<string, unknown>;
|
|
inputPorts?: Array<{ name: string; schema: string; accepts?: string[] }>;
|
|
outputPorts?: Array<{ name: string; schema: string }>;
|
|
}
|
|
|
|
export interface Automation2Connection {
|
|
source: string;
|
|
target: string;
|
|
sourceOutput?: number;
|
|
targetInput?: number;
|
|
}
|
|
|
|
export interface Automation2Graph {
|
|
nodes: Automation2GraphNode[];
|
|
connections: Automation2Connection[];
|
|
}
|
|
|
|
export interface ExecuteGraphResponse {
|
|
success: boolean;
|
|
nodeOutputs?: Record<string, unknown>;
|
|
error?: string;
|
|
stopped?: boolean;
|
|
failedNode?: string;
|
|
paused?: boolean;
|
|
taskId?: string;
|
|
runId?: string;
|
|
nodeId?: string;
|
|
}
|
|
|
|
/** Entry point / start configured outside the canvas (manual, form, schedule, …) */
|
|
export interface WorkflowEntryPoint {
|
|
id: string;
|
|
kind: string;
|
|
category: 'on_demand' | 'always_on';
|
|
enabled: boolean;
|
|
title: Record<string, string> | string;
|
|
description?: Record<string, string>;
|
|
config: Record<string, unknown>;
|
|
}
|
|
|
|
export interface Automation2Workflow {
|
|
id: string;
|
|
label: string;
|
|
graph: Automation2Graph;
|
|
active?: boolean;
|
|
/** Entry points (Starts) — how this workflow may be invoked */
|
|
invocations?: WorkflowEntryPoint[];
|
|
/** Enriched: run count */
|
|
runCount?: number;
|
|
/** Enriched: has active (running/paused) run */
|
|
isRunning?: boolean;
|
|
/** Enriched: status of active run */
|
|
runStatus?: string;
|
|
/** Enriched: nodeId where workflow is stuck (paused) */
|
|
stuckAtNodeId?: string;
|
|
/** Enriched: human-readable label for stuck node */
|
|
stuckAtNodeLabel?: string;
|
|
/** Enriched: created timestamp (seconds) */
|
|
createdAt?: number;
|
|
/** Enriched: last run started timestamp (seconds) */
|
|
lastStartedAt?: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// AUTO-PREFIX TYPES (Greenfield)
|
|
// ============================================================================
|
|
|
|
export type AutoWorkflowStatus = 'draft' | 'published' | 'archived';
|
|
export type AutoRunStatus = 'running' | 'paused' | 'completed' | 'failed' | 'cancelled';
|
|
export type AutoStepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
export type AutoTaskStatus = 'pending' | 'completed' | 'cancelled' | 'expired';
|
|
export type AutoTemplateScope = 'user' | 'instance' | 'mandate' | 'system';
|
|
|
|
export interface AutoVersion {
|
|
id: string;
|
|
workflowId: string;
|
|
versionNumber: number;
|
|
status: AutoWorkflowStatus;
|
|
graph: Automation2Graph;
|
|
invocations?: WorkflowEntryPoint[];
|
|
publishedAt?: number;
|
|
publishedBy?: string;
|
|
}
|
|
|
|
export interface AutoRun {
|
|
id: string;
|
|
workflowId: string;
|
|
versionId?: string;
|
|
status: AutoRunStatus;
|
|
trigger?: Record<string, unknown>;
|
|
startedAt?: number;
|
|
completedAt?: number;
|
|
nodeOutputs?: Record<string, unknown>;
|
|
currentNodeId?: string;
|
|
resumeContext?: Record<string, unknown>;
|
|
error?: string;
|
|
costTokens?: number;
|
|
costCredits?: number;
|
|
}
|
|
|
|
export interface AutoWorkflow {
|
|
id: string;
|
|
mandateId: string;
|
|
featureInstanceId: string;
|
|
label: string;
|
|
description?: string;
|
|
tags?: string[];
|
|
isTemplate: boolean;
|
|
templateSourceId?: string;
|
|
templateScope?: AutoTemplateScope;
|
|
sharedReadOnly?: boolean;
|
|
currentVersionId?: string;
|
|
active: boolean;
|
|
eventId?: string;
|
|
notifyOnFailure?: boolean;
|
|
graph: Automation2Graph;
|
|
invocations?: WorkflowEntryPoint[];
|
|
sysCreatedBy?: string;
|
|
sysCreatedAt?: number;
|
|
sysModifiedBy?: string;
|
|
sysModifiedAt?: number;
|
|
}
|
|
|
|
export interface AutoTask {
|
|
id: string;
|
|
runId: string;
|
|
workflowId: string;
|
|
nodeId: string;
|
|
nodeType: string;
|
|
config: Record<string, unknown>;
|
|
assigneeId?: string;
|
|
status: AutoTaskStatus;
|
|
result?: Record<string, unknown>;
|
|
expiresAt?: number;
|
|
sysCreatedAt?: number;
|
|
}
|
|
|
|
export interface AutoStepLog {
|
|
id: string;
|
|
runId: string;
|
|
nodeId: string;
|
|
nodeType: string;
|
|
status: AutoStepStatus;
|
|
inputSnapshot?: Record<string, unknown>;
|
|
output?: Record<string, unknown>;
|
|
error?: string;
|
|
startedAt?: number;
|
|
completedAt?: number;
|
|
durationMs?: number;
|
|
tokensUsed?: number;
|
|
retryCount?: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// API FUNCTIONS
|
|
// ============================================================================
|
|
|
|
export type ApiRequestFunction = (options: ApiRequestOptions<any>) => Promise<any>;
|
|
|
|
/**
|
|
* Fetch node types for the flow builder (backend-driven).
|
|
* GET /api/workflows/{instanceId}/node-types?language=de
|
|
*/
|
|
export async function fetchNodeTypes(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
language = 'de'
|
|
): Promise<NodeTypesResponse> {
|
|
console.log(`${LOG} fetchNodeTypes: instanceId=${instanceId} language=${language}`);
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/node-types`,
|
|
method: 'get',
|
|
params: { language },
|
|
});
|
|
const nodeTypes = data?.nodeTypes ?? [];
|
|
const categories = data?.categories ?? [];
|
|
console.log(`${LOG} fetchNodeTypes response: ${nodeTypes.length} nodeTypes, ${categories.length} categories`);
|
|
return { nodeTypes, categories };
|
|
}
|
|
|
|
/**
|
|
* Execute an automation2 graph.
|
|
* POST /api/workflows/{instanceId}/execute
|
|
*/
|
|
export interface ExecuteGraphOptions {
|
|
/** Use a configured start on the saved workflow */
|
|
entryPointId?: string;
|
|
/** Full run envelope (overrides entry point mapping) */
|
|
runEnvelope?: Record<string, unknown>;
|
|
/** Merged into envelope.payload */
|
|
payload?: Record<string, unknown>;
|
|
}
|
|
|
|
export async function executeGraph(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
graph: Automation2Graph,
|
|
workflowId?: string,
|
|
options?: ExecuteGraphOptions
|
|
): Promise<ExecuteGraphResponse> {
|
|
console.log(
|
|
`${LOG} executeGraph request: instanceId=${instanceId} workflowId=${workflowId} nodes=${graph.nodes.length} connections=${graph.connections.length}`,
|
|
{ nodes: graph.nodes, connections: graph.connections, options }
|
|
);
|
|
const start = performance.now();
|
|
try {
|
|
const data: Record<string, unknown> = { graph, workflowId };
|
|
if (options?.entryPointId) data.entryPointId = options.entryPointId;
|
|
if (options?.runEnvelope) data.runEnvelope = options.runEnvelope;
|
|
if (options?.payload && Object.keys(options.payload).length > 0) data.payload = options.payload;
|
|
const result = await request({
|
|
url: `/api/workflows/${instanceId}/execute`,
|
|
method: 'post',
|
|
data,
|
|
});
|
|
const ms = Math.round(performance.now() - start);
|
|
console.log(
|
|
`${LOG} executeGraph response (${ms}ms): success=${result?.success} error=${result?.error ?? 'none'} nodeOutputs_keys=${Object.keys(result?.nodeOutputs ?? {}).join(',')} failedNode=${result?.failedNode ?? '-'}`,
|
|
result
|
|
);
|
|
return result;
|
|
} catch (err) {
|
|
const ms = Math.round(performance.now() - start);
|
|
console.error(
|
|
`${LOG} executeGraph FAILED (${ms}ms): instanceId=${instanceId}`,
|
|
err
|
|
);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Workflows CRUD
|
|
// -------------------------------------------------------------------------
|
|
|
|
export async function fetchWorkflows(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
params?: { active?: boolean; pagination?: any }
|
|
): Promise<Automation2Workflow[] | { items: Automation2Workflow[]; pagination: any }> {
|
|
const queryParams: Record<string, any> = {};
|
|
if (params?.active !== undefined) queryParams.active = params.active;
|
|
if (params?.pagination) queryParams.pagination = JSON.stringify(params.pagination);
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/workflows`,
|
|
method: 'get',
|
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
});
|
|
if (data?.items && data?.pagination) return data;
|
|
return data?.workflows ?? [];
|
|
}
|
|
|
|
export async function fetchWorkflow(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string
|
|
): Promise<Automation2Workflow> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/workflows/${workflowId}`,
|
|
method: 'get',
|
|
});
|
|
}
|
|
|
|
export async function createWorkflow(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
body: { label: string; graph: Automation2Graph; invocations?: WorkflowEntryPoint[] }
|
|
): Promise<Automation2Workflow> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/workflows`,
|
|
method: 'post',
|
|
data: body,
|
|
});
|
|
}
|
|
|
|
export async function updateWorkflow(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string,
|
|
body: {
|
|
label?: string;
|
|
graph?: Automation2Graph;
|
|
invocations?: WorkflowEntryPoint[];
|
|
active?: boolean;
|
|
notifyOnFailure?: boolean;
|
|
}
|
|
): Promise<Automation2Workflow> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/workflows/${workflowId}`,
|
|
method: 'put',
|
|
data: body,
|
|
});
|
|
}
|
|
|
|
export async function deleteWorkflow(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string
|
|
): Promise<void> {
|
|
await request({
|
|
url: `/api/workflows/${instanceId}/workflows/${workflowId}`,
|
|
method: 'delete',
|
|
});
|
|
}
|
|
|
|
/** Delete by workflow ID only (Automations dashboard / orphan rows without featureInstanceId). */
|
|
export async function deleteSystemWorkflow(
|
|
request: ApiRequestFunction,
|
|
workflowId: string,
|
|
): Promise<void> {
|
|
await request({
|
|
url: `/api/system/workflow-runs/workflows/${workflowId}`,
|
|
method: 'delete',
|
|
});
|
|
}
|
|
|
|
export interface Automation2Run {
|
|
id: string;
|
|
workflowId: string;
|
|
status: string;
|
|
nodeOutputs?: Record<string, unknown>;
|
|
currentNodeId?: string;
|
|
}
|
|
|
|
export async function fetchWorkflowRuns(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string
|
|
): Promise<Automation2Run[]> {
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/workflows/${workflowId}/runs`,
|
|
method: 'get',
|
|
});
|
|
return data?.runs ?? [];
|
|
}
|
|
|
|
export interface CompletedRun extends Automation2Run {
|
|
workflowLabel?: string;
|
|
sysModifiedAt?: number;
|
|
sysCreatedAt?: number;
|
|
}
|
|
|
|
export async function fetchCompletedRuns(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
limit = 20
|
|
): Promise<CompletedRun[]> {
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/runs/completed`,
|
|
method: 'get',
|
|
params: { limit },
|
|
});
|
|
return data?.runs ?? [];
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Tasks
|
|
// -------------------------------------------------------------------------
|
|
|
|
export interface Automation2Task {
|
|
id: string;
|
|
runId: string;
|
|
workflowId: string;
|
|
nodeId: string;
|
|
nodeType: string;
|
|
config: Record<string, unknown>;
|
|
status: string;
|
|
result?: Record<string, unknown>;
|
|
/** Workflow label (enriched by API) */
|
|
workflowLabel?: string;
|
|
/** Unix timestamp ms (from sysCreatedAt) */
|
|
createdAt?: number;
|
|
/** Optional due date - configurable in future */
|
|
dueAt?: number;
|
|
}
|
|
|
|
export async function fetchTasks(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
params?: { workflowId?: string; status?: string }
|
|
): Promise<Automation2Task[]> {
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/tasks`,
|
|
method: 'get',
|
|
params,
|
|
});
|
|
return data?.tasks ?? [];
|
|
}
|
|
|
|
export async function completeTask(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
taskId: string,
|
|
result: Record<string, unknown>
|
|
): Promise<ExecuteGraphResponse> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/tasks/${taskId}/complete`,
|
|
method: 'post',
|
|
data: { result },
|
|
});
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Versions (AutoVersion Lifecycle)
|
|
// -------------------------------------------------------------------------
|
|
|
|
export async function fetchVersions(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string
|
|
): Promise<AutoVersion[]> {
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/workflows/${workflowId}/versions`,
|
|
method: 'get',
|
|
});
|
|
return data?.versions ?? [];
|
|
}
|
|
|
|
export async function createDraftVersion(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string
|
|
): Promise<AutoVersion> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/workflows/${workflowId}/versions/draft`,
|
|
method: 'post',
|
|
});
|
|
}
|
|
|
|
export async function publishVersion(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
versionId: string
|
|
): Promise<AutoVersion> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/versions/${versionId}/publish`,
|
|
method: 'post',
|
|
});
|
|
}
|
|
|
|
export async function unpublishVersion(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
versionId: string
|
|
): Promise<AutoVersion> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/versions/${versionId}/unpublish`,
|
|
method: 'post',
|
|
});
|
|
}
|
|
|
|
export async function archiveVersion(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
versionId: string
|
|
): Promise<AutoVersion> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/versions/${versionId}/archive`,
|
|
method: 'post',
|
|
});
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Templates
|
|
// -------------------------------------------------------------------------
|
|
|
|
export interface AutoWorkflowTemplate extends Automation2Workflow {
|
|
isTemplate: boolean;
|
|
templateScope?: AutoTemplateScope;
|
|
templateSourceId?: string;
|
|
sharedReadOnly?: boolean;
|
|
}
|
|
|
|
export async function fetchTemplates(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
scope?: AutoTemplateScope,
|
|
pagination?: any
|
|
): Promise<AutoWorkflowTemplate[] | { items: AutoWorkflowTemplate[]; pagination: any }> {
|
|
const queryParams: Record<string, any> = {};
|
|
if (scope) queryParams.scope = scope;
|
|
if (pagination) queryParams.pagination = JSON.stringify(pagination);
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/templates`,
|
|
method: 'get',
|
|
params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
|
});
|
|
if (data?.items && data?.pagination) return data;
|
|
return data?.templates ?? [];
|
|
}
|
|
|
|
export async function createTemplateFromWorkflow(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
workflowId: string,
|
|
scope: AutoTemplateScope = 'user'
|
|
): Promise<AutoWorkflowTemplate> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/templates/from-workflow`,
|
|
method: 'post',
|
|
data: { workflowId, scope },
|
|
});
|
|
}
|
|
|
|
export async function copyTemplate(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
templateId: string
|
|
): Promise<Automation2Workflow> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/templates/${templateId}/copy`,
|
|
method: 'post',
|
|
});
|
|
}
|
|
|
|
export async function shareTemplate(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
templateId: string,
|
|
scope: AutoTemplateScope
|
|
): Promise<AutoWorkflowTemplate> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/templates/${templateId}/share`,
|
|
method: 'post',
|
|
data: { scope },
|
|
});
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Connections and Browse (for Email/SharePoint node config)
|
|
// -------------------------------------------------------------------------
|
|
|
|
export interface UserConnection {
|
|
id: string;
|
|
authority: string;
|
|
externalUsername?: string;
|
|
externalEmail?: string;
|
|
status: string;
|
|
}
|
|
|
|
export async function fetchConnections(
|
|
request: ApiRequestFunction,
|
|
instanceId: string
|
|
): Promise<UserConnection[]> {
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/connections`,
|
|
method: 'get',
|
|
});
|
|
return data?.connections ?? [];
|
|
}
|
|
|
|
export interface ConnectionService {
|
|
service: string;
|
|
label: string;
|
|
icon: string;
|
|
}
|
|
|
|
export async function fetchConnectionServices(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
connectionId: string
|
|
): Promise<ConnectionService[]> {
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/connections/${connectionId}/services`,
|
|
method: 'get',
|
|
});
|
|
return data?.services ?? [];
|
|
}
|
|
|
|
export interface BrowseEntry {
|
|
name: string;
|
|
path: string;
|
|
isFolder: boolean;
|
|
size?: number;
|
|
mimeType?: string;
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
|
|
export async function fetchBrowse(
|
|
request: ApiRequestFunction,
|
|
instanceId: string,
|
|
connectionId: string,
|
|
service: string,
|
|
path = '/'
|
|
): Promise<{ items: BrowseEntry[]; path: string; service: string }> {
|
|
const data = await request({
|
|
url: `/api/workflows/${instanceId}/connections/${connectionId}/browse`,
|
|
method: 'get',
|
|
params: { service, path },
|
|
});
|
|
return { items: data?.items ?? [], path: data?.path ?? path, service: data?.service ?? service };
|
|
}
|
|
|
|
/** ClickUp GET /task/{taskId} — list.id for resolving list-scoped fields when only task id is known. */
|
|
export async function fetchClickupTask(
|
|
request: ApiRequestFunction,
|
|
connectionId: string,
|
|
taskId: string
|
|
): Promise<Record<string, unknown>> {
|
|
const data = await request({
|
|
url: `/api/clickup/${connectionId}/tasks/${encodeURIComponent(taskId)}`,
|
|
method: 'get',
|
|
});
|
|
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
|
|
}
|
|
|
|
/** ClickUp list metadata (statuses, etc.) — GET /api/clickup/{connectionId}/lists/{listId}. */
|
|
export async function fetchClickupList(
|
|
request: ApiRequestFunction,
|
|
connectionId: string,
|
|
listId: string
|
|
): Promise<Record<string, unknown>> {
|
|
const data = await request({
|
|
url: `/api/clickup/${connectionId}/lists/${listId}`,
|
|
method: 'get',
|
|
});
|
|
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
|
|
}
|
|
|
|
/** ClickUp workspace/team (members for assignees) — GET /api/clickup/{connectionId}/teams/{teamId}. */
|
|
export async function fetchClickupTeam(
|
|
request: ApiRequestFunction,
|
|
connectionId: string,
|
|
teamId: string
|
|
): Promise<Record<string, unknown>> {
|
|
const data = await request({
|
|
url: `/api/clickup/${connectionId}/teams/${teamId}`,
|
|
method: 'get',
|
|
});
|
|
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
|
|
}
|
|
|
|
/** ClickUp list custom fields (GET /api/clickup/{connectionId}/lists/{listId}/fields). */
|
|
export async function fetchClickupListFields(
|
|
request: ApiRequestFunction,
|
|
connectionId: string,
|
|
listId: string
|
|
): Promise<{ fields?: unknown[] } & Record<string, unknown>> {
|
|
const data = await request({
|
|
url: `/api/clickup/${connectionId}/lists/${listId}/fields`,
|
|
method: 'get',
|
|
});
|
|
return (data && typeof data === 'object' ? data : {}) as { fields?: unknown[] } & Record<string, unknown>;
|
|
}
|
|
|
|
/** ClickUp GET /list/{id}/task page (tasks in a list for relationship dropdowns). */
|
|
export interface ClickupListTaskItem {
|
|
id?: string;
|
|
name?: string;
|
|
}
|
|
|
|
export async function fetchClickupListTasks(
|
|
request: ApiRequestFunction,
|
|
connectionId: string,
|
|
listId: string,
|
|
options?: { page?: number; includeClosed?: boolean }
|
|
): Promise<
|
|
{ tasks?: ClickupListTaskItem[]; last_page?: boolean } & Record<string, unknown>
|
|
> {
|
|
const data = await request({
|
|
url: `/api/clickup/${connectionId}/lists/${listId}/tasks`,
|
|
method: 'get',
|
|
params: {
|
|
page: options?.page ?? 0,
|
|
include_closed: options?.includeClosed ?? false,
|
|
},
|
|
});
|
|
return (data && typeof data === 'object' ? data : {}) as {
|
|
tasks?: ClickupListTaskItem[];
|
|
last_page?: boolean;
|
|
} & Record<string, unknown>;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Monitoring / Metrics
|
|
// -------------------------------------------------------------------------
|
|
|
|
export interface WorkflowMetrics {
|
|
workflowCount: number;
|
|
activeWorkflows: number;
|
|
totalRuns: number;
|
|
runsByStatus: Record<string, number>;
|
|
totalTasks: number;
|
|
tasksByStatus: Record<string, number>;
|
|
totalTokens: number;
|
|
totalCredits: number;
|
|
}
|
|
|
|
export async function fetchMetrics(
|
|
request: ApiRequestFunction,
|
|
instanceId: string
|
|
): Promise<WorkflowMetrics> {
|
|
return await request({
|
|
url: `/api/workflows/${instanceId}/metrics`,
|
|
method: 'get',
|
|
});
|
|
}
|
|
|
|
/** Paginated tasks in a list — for ClickUp relationship dropdowns and input.form „ClickUp-Aufgabe“. */
|
|
export async function loadClickupListTasksForDropdown(
|
|
request: ApiRequestFunction,
|
|
connectionId: string,
|
|
listId: string
|
|
): Promise<Array<{ id: string; name: string }>> {
|
|
const acc: Array<{ id: string; name: string }> = [];
|
|
const seen = new Set<string>();
|
|
const maxPages = 12;
|
|
const pageSizeHint = 100;
|
|
for (let page = 0; page < maxPages; page++) {
|
|
const data = await fetchClickupListTasks(request, connectionId, listId, {
|
|
page,
|
|
includeClosed: false,
|
|
});
|
|
if (data && typeof data === 'object' && 'error' in data && (data as { error?: unknown }).error) {
|
|
const err = (data as { error?: unknown }).error;
|
|
const body = (data as { body?: string }).body;
|
|
throw new Error(
|
|
typeof err === 'string' ? err + (body ? `: ${body.slice(0, 200)}` : '') : 'ClickUp API error'
|
|
);
|
|
}
|
|
const tasks = Array.isArray(data.tasks) ? data.tasks : [];
|
|
for (const t of tasks) {
|
|
const id = t?.id != null ? String(t.id) : '';
|
|
if (!id || seen.has(id)) continue;
|
|
seen.add(id);
|
|
acc.push({ id, name: String(t.name ?? id) });
|
|
}
|
|
const rawLast = (data as Record<string, unknown>).last_page;
|
|
const last =
|
|
rawLast === true ||
|
|
rawLast === 'true' ||
|
|
tasks.length === 0 ||
|
|
tasks.length < pageSizeHint;
|
|
if (last) break;
|
|
}
|
|
acc.sort((a, b) => a.name.localeCompare(b.name, 'de'));
|
|
return acc;
|
|
}
|