664 lines
20 KiB
TypeScript
664 lines
20 KiB
TypeScript
import { APP_ID } from "../config";
|
|
import { resolveConfigValue, basePath } from "../runtimeConfig";
|
|
|
|
const TOKEN_KEY = "p2p_token";
|
|
|
|
function baseUrl(): string {
|
|
return resolveConfigValue("VITE_ZINO_API_URL", "https://studio.getzino.in").replace(/\/+$/, "");
|
|
}
|
|
|
|
function headers(): Record<string, string> {
|
|
const h: Record<string, string> = { "Content-Type": "application/json" };
|
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
return h;
|
|
}
|
|
|
|
// Shared response handling — 401 bounces to login, error bodies are unwrapped
|
|
// from { error }. Used by both the JSON and multipart request paths.
|
|
async function parseResponse<T>(res: Response): Promise<T> {
|
|
if (res.status === 401) {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
localStorage.removeItem("p2p_user");
|
|
window.location.href = `${basePath()}login`;
|
|
throw new Error("Unauthorized");
|
|
}
|
|
if (!res.ok) {
|
|
let msg = res.statusText;
|
|
try { const e = await res.json(); if (e.error) msg = e.error; } catch {}
|
|
// Status rides along so callers can special-case it (403 -> no permission).
|
|
throw Object.assign(new Error(msg), { status: res.status });
|
|
}
|
|
if (res.status === 204) return undefined as T;
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
const res = await fetch(`${baseUrl()}${path}`, {
|
|
method,
|
|
headers: headers(),
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
return parseResponse<T>(res);
|
|
}
|
|
|
|
// Multipart POST for the field-scoped core-service endpoints (/upload,
|
|
// /ocr-extract). Content-Type is deliberately omitted so the browser sets the
|
|
// multipart boundary itself.
|
|
async function postFile<T>(path: string, file: File, ctx: FieldFileContext): Promise<T> {
|
|
const form = new FormData();
|
|
form.append("file", file);
|
|
form.append("workflow_uuid", ctx.workflowUuid);
|
|
form.append("activity_id", ctx.activityId);
|
|
form.append("field_id", ctx.fieldId);
|
|
if (ctx.columnId) form.append("column_id", ctx.columnId);
|
|
if (ctx.versionUuid) form.append("version_uuid", ctx.versionUuid);
|
|
if (ctx.instanceId) form.append("instance_id", String(ctx.instanceId));
|
|
|
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
const res = await fetch(`${baseUrl()}${path}`, {
|
|
method: "POST",
|
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
body: form,
|
|
});
|
|
return parseResponse<T>(res);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Header
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface HeaderConfig {
|
|
slots: { left: string[]; right: string[]; center: string[] };
|
|
layout: { type: string; height: string; position: string; full_width: boolean };
|
|
components: {
|
|
nav?: { items: NavItem[] };
|
|
logo?: { text?: string; image_url?: string; display_type: string };
|
|
profile?: { shape: string; show_name: boolean };
|
|
};
|
|
}
|
|
|
|
export interface NavItem {
|
|
id: string;
|
|
icon: string;
|
|
type: string;
|
|
label: string;
|
|
screen_uuid: string;
|
|
}
|
|
|
|
export async function getHeaderConfig(deviceType = "desktop"): Promise<HeaderConfig> {
|
|
const raw = await request<any>("GET", `/app/${APP_ID}/view/header/${deviceType}`);
|
|
return raw.config ?? raw;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Screens
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface ScreenListItem {
|
|
id: number;
|
|
source_screen_id: number;
|
|
screen_name: string;
|
|
device: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface ScreenLayout {
|
|
id: number;
|
|
source_screen_id: number;
|
|
screen_name: string;
|
|
layout: LayoutElement[];
|
|
}
|
|
|
|
export interface LayoutElement {
|
|
type: string;
|
|
style?: Record<string, string>;
|
|
config: Record<string, any>;
|
|
classes?: string[];
|
|
}
|
|
|
|
export function getScreens(deviceType = "desktop"): Promise<ScreenListItem[]> {
|
|
return request("GET", `/app/${APP_ID}/view/screens?device_type=${deviceType}`);
|
|
}
|
|
|
|
export function getScreen(idOrUuid: number | string): Promise<ScreenLayout> {
|
|
return request("GET", `/app/${APP_ID}/view/screens/${idOrUuid}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RV Screens
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface RVScreen {
|
|
id: number;
|
|
source_screen_id: number;
|
|
screen_name: string;
|
|
rv_template_uid: string;
|
|
layout?: any;
|
|
}
|
|
|
|
// Accepts either the numeric source_screen_id OR the source_uuid — both
|
|
// are accepted by the view-service's rv-screens endpoint.
|
|
export function getRVScreen(idOrUuid: number | string): Promise<RVScreen> {
|
|
return request("GET", `/app/${APP_ID}/view/rv-screens/${idOrUuid}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Record View (POST-based)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface SearchQuery {
|
|
page?: number;
|
|
limit?: number;
|
|
sort_by?: string;
|
|
sort_dir?: "asc" | "desc";
|
|
search?: string;
|
|
filters?: Array<{ field_key: string; value: string; data_type?: string }>;
|
|
}
|
|
|
|
export interface RecordViewField {
|
|
field_key: string;
|
|
output_label: string;
|
|
data_type: string;
|
|
is_filter: boolean;
|
|
is_search: boolean;
|
|
type?: string;
|
|
activity_id?: string;
|
|
}
|
|
|
|
export interface TileValue {
|
|
tile_uid: string;
|
|
key: string;
|
|
value: unknown;
|
|
}
|
|
|
|
export interface ChartDataRow {
|
|
dimension: unknown;
|
|
series?: string;
|
|
value: unknown;
|
|
}
|
|
|
|
export interface ChartDataResponse {
|
|
chart_uid: string;
|
|
key: string;
|
|
rows: ChartDataRow[];
|
|
}
|
|
|
|
export interface RecordViewResponse {
|
|
config: { fields: RecordViewField[] };
|
|
data: Record<string, unknown>[];
|
|
pagination?: {
|
|
page: number;
|
|
limit: number;
|
|
total_count: number;
|
|
total_pages: number;
|
|
};
|
|
tile_values?: TileValue[];
|
|
chart_data?: ChartDataResponse[];
|
|
}
|
|
|
|
export function getRecordView(
|
|
rvTemplateUid: string,
|
|
rvScreenId: string | number,
|
|
query: SearchQuery = {},
|
|
): Promise<RecordViewResponse> {
|
|
return request("POST", `/app/${APP_ID}/view/recordview`, {
|
|
rv_template_uid: rvTemplateUid,
|
|
rv_screen_id: String(rvScreenId),
|
|
search_query: {
|
|
page: query.page || 1,
|
|
limit: query.limit || 50,
|
|
sort_by: query.sort_by || "",
|
|
sort_dir: query.sort_dir || "desc",
|
|
search: query.search || "",
|
|
filters: query.filters || [],
|
|
},
|
|
});
|
|
}
|
|
|
|
export function getRecordViewLegacy(
|
|
rvUid: string,
|
|
query: SearchQuery = {},
|
|
): Promise<RecordViewResponse> {
|
|
const qs = new URLSearchParams();
|
|
qs.set("rv_id", rvUid);
|
|
if (query.page) qs.set("page", String(query.page));
|
|
if (query.limit) qs.set("limit", String(query.limit));
|
|
if (query.sort_by) qs.set("sort_by", query.sort_by);
|
|
if (query.sort_dir) qs.set("sort_dir", query.sort_dir);
|
|
if (query.search) qs.set("search", query.search);
|
|
if (query.filters) {
|
|
for (const f of query.filters) {
|
|
qs.set(`filter.${f.field_key}`, f.value);
|
|
}
|
|
}
|
|
return request("GET", `/recordview?${qs.toString()}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// DV Screens
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface DVScreen {
|
|
id: number;
|
|
source_screen_id: number;
|
|
screen_name: string;
|
|
dv_template_uid: string;
|
|
layout?: any;
|
|
}
|
|
|
|
export function getDVScreen(idOrUuid: number | string): Promise<DVScreen> {
|
|
return request("GET", `/app/${APP_ID}/view/dv-screens/${idOrUuid}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Detail View
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface DetailViewResponse {
|
|
config: { fields: Array<{ field_key: string; output_label: string; data_type: string }> };
|
|
data: Record<string, unknown>;
|
|
}
|
|
|
|
export function getDetailView(dvSourceIdOrUid: number | string, instanceId: string): Promise<DetailViewResponse> {
|
|
return request(
|
|
"GET",
|
|
`/app/${APP_ID}/view/detailview/${encodeURIComponent(String(dvSourceIdOrUid))}?instance_id=${encodeURIComponent(instanceId)}`,
|
|
);
|
|
}
|
|
|
|
export function getDetailViewLegacy(dvUid: string, instanceId: string): Promise<DetailViewResponse> {
|
|
return request(
|
|
"GET",
|
|
`/detailview?dv_id=${encodeURIComponent(dvUid)}&instance_id=${encodeURIComponent(instanceId)}`,
|
|
);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Form Screens
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// One key the vision model must return, as configured in studio's OCR modal.
|
|
export interface OcrExtractionField {
|
|
key: string;
|
|
label: string;
|
|
data_type?: string;
|
|
description?: string;
|
|
}
|
|
|
|
// extraction_key → the form field that receives the extracted value.
|
|
export interface OcrFieldMapping {
|
|
extraction_key: string;
|
|
target_field: string;
|
|
}
|
|
|
|
// Studio's ocr_config block. `prompt` / `extraction_fields` are resolved
|
|
// server-side by /ocr-extract and are present here only for picker UX; the
|
|
// client genuinely needs `field_mappings` to know where to write the results.
|
|
export interface OcrConfig {
|
|
prompt?: string;
|
|
template?: string;
|
|
allowed_types?: string[] | string;
|
|
max_size_mb?: number | string;
|
|
placeholder?: string;
|
|
extraction_fields?: OcrExtractionField[];
|
|
field_mappings?: OcrFieldMapping[];
|
|
}
|
|
|
|
export interface FormScreenField {
|
|
id: string;
|
|
uid: string;
|
|
name: string;
|
|
type: string;
|
|
data_type: string;
|
|
mandatory: boolean;
|
|
value?: any;
|
|
properties?: {
|
|
options?: Array<{ label: string; value: string }>;
|
|
country_code?: string;
|
|
placeholder?: string;
|
|
multiple?: boolean;
|
|
ocr_config?: OcrConfig;
|
|
// Some studio configs put the OCR keys flat on properties instead of
|
|
// nested under ocr_config — mirrored from FFOcrField's resolveOcrConfig.
|
|
allowed_types?: string[] | string;
|
|
max_size_mb?: number | string;
|
|
extraction_fields?: OcrExtractionField[];
|
|
field_mappings?: OcrFieldMapping[];
|
|
};
|
|
}
|
|
|
|
export interface FormScreenResponse {
|
|
id: number;
|
|
activity_uid: string;
|
|
activity_name: string;
|
|
device_type: string;
|
|
fields: FormScreenField[];
|
|
grid_config: Array<{ i: string; x: number; y: number; w: number; h: number }>;
|
|
layout: any[];
|
|
// Identifiers the field-scoped core endpoints (/upload, /ocr-extract) need
|
|
// so they can resolve the field's config + RBAC server-side.
|
|
workflow_uuid?: string;
|
|
version_uuid?: string;
|
|
}
|
|
|
|
export function getFormScreen(
|
|
activityId: string,
|
|
instanceId?: string,
|
|
deviceType = "desktop",
|
|
): Promise<FormScreenResponse> {
|
|
const numericInstanceId = instanceId ? Number(instanceId) : undefined;
|
|
return request("POST", `/app/${APP_ID}/view/form-screens`, {
|
|
activity_id: activityId,
|
|
device_type: deviceType,
|
|
...(numericInstanceId && !Number.isNaN(numericInstanceId)
|
|
? { instance_id: numericInstanceId }
|
|
: {}),
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Field files & OCR (core-service)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Identifiers sent alongside every field-scoped file upload. The backend uses
|
|
// these to resolve the field's deployed allowed_types / max_size_mb / ocr_config
|
|
// and to run activity RBAC — anything the client sends about that config is
|
|
// ignored, so this trio is mandatory.
|
|
export interface FieldFileContext {
|
|
workflowUuid: string;
|
|
activityId: string;
|
|
fieldId: string;
|
|
columnId?: string;
|
|
versionUuid?: string;
|
|
instanceId?: string;
|
|
}
|
|
|
|
// What /upload returns and what gets stored in instance data. `uuid` is the
|
|
// stable app-scoped handle; blob_path is kept for the legacy serve fallback.
|
|
export interface FileMeta {
|
|
uuid: string;
|
|
blob_path: string;
|
|
original_name: string;
|
|
mime_type: string;
|
|
size_bytes: number;
|
|
}
|
|
|
|
export interface OcrExtractResponse {
|
|
// Keyed by the studio-configured extraction_fields[].key. A key whose value
|
|
// wasn't found in the document comes back as null.
|
|
extracted: Record<string, unknown>;
|
|
raw?: string;
|
|
// Set when the model's reply wasn't parseable JSON — `raw` still holds the text.
|
|
parse_error?: string;
|
|
}
|
|
|
|
// Stores a form-field file and returns the metadata to submit in its place.
|
|
export function uploadFieldFile(file: File, ctx: FieldFileContext): Promise<FileMeta> {
|
|
return postFile<FileMeta>(`/app/${APP_ID}/upload`, file, ctx);
|
|
}
|
|
|
|
// Runs vision OCR over an uploaded document. The prompt and extraction schema
|
|
// live in the deployed ocr_config, so only the file + identifiers go over the wire.
|
|
export function extractOcr(file: File, ctx: FieldFileContext): Promise<OcrExtractResponse> {
|
|
return postFile<OcrExtractResponse>(`/app/${APP_ID}/ocr-extract`, file, ctx);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Workflow Actions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface WorkflowResponse {
|
|
success: boolean;
|
|
status_code?: number;
|
|
message?: string;
|
|
data?: Record<string, unknown>;
|
|
instance_id?: string;
|
|
}
|
|
|
|
// Core-service expects the workflow UUID as `workflow_uuid` — the numeric
|
|
// `workflow_id` field was removed when the clone-portable identifier rolled out.
|
|
export function startWorkflow(
|
|
workflowUuid: string,
|
|
activityId: string,
|
|
data?: Record<string, unknown>,
|
|
): Promise<WorkflowResponse> {
|
|
return request("POST", `/app/${APP_ID}/start`, {
|
|
workflow_uuid: workflowUuid,
|
|
activity_id: activityId,
|
|
data,
|
|
});
|
|
}
|
|
|
|
export function performActivity(
|
|
workflowUuid: string,
|
|
instanceId: string,
|
|
activityId: string,
|
|
data?: Record<string, unknown>,
|
|
): Promise<WorkflowResponse> {
|
|
const numericInstanceId = Number(instanceId);
|
|
return request("POST", `/app/${APP_ID}/activity`, {
|
|
workflow_uuid: workflowUuid,
|
|
instance_id: Number.isNaN(numericInstanceId) ? instanceId : numericInstanceId,
|
|
activity_id: activityId,
|
|
data,
|
|
});
|
|
}
|
|
|
|
export function submitForm(
|
|
workflowUuid: string,
|
|
activityId: string,
|
|
formData: Record<string, unknown>,
|
|
instanceId?: string,
|
|
deviceType = "desktop",
|
|
): Promise<WorkflowResponse> {
|
|
const numericInstanceId = instanceId ? Number(instanceId) : undefined;
|
|
return request("POST", `/app/${APP_ID}/form/submit`, {
|
|
workflow_uuid: workflowUuid,
|
|
activity_id: activityId,
|
|
device_type: deviceType,
|
|
form_data: formData,
|
|
...(numericInstanceId && !Number.isNaN(numericInstanceId)
|
|
? { instance_id: numericInstanceId }
|
|
: {}),
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Instance
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface InstanceResponse {
|
|
instance_id: string;
|
|
workflow_id: string;
|
|
current_state_id: string;
|
|
current_state_name: string;
|
|
data: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export function getInstance(workflowUuid: string, instanceId: string): Promise<InstanceResponse> {
|
|
return request("POST", `/app/${APP_ID}/instance`, {
|
|
workflow_uuid: workflowUuid,
|
|
instance_id: Number(instanceId),
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Audit
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface AuditEntry {
|
|
id: number;
|
|
user_id: string;
|
|
user_roles: string[];
|
|
user_groups?: string[];
|
|
activity_id: string;
|
|
data: Record<string, unknown>;
|
|
/** State UUID the instance landed in after this activity. */
|
|
execution_state: string;
|
|
created_at: string;
|
|
// Optional human-readable summary line. Populated by useInstanceMeta when
|
|
// the audit is synthesised from instance.data._activities.
|
|
context?: string;
|
|
}
|
|
|
|
export function getAuditLog(instanceId: string): Promise<AuditEntry[]> {
|
|
return request("GET", `/app/${APP_ID}/view/audit?instance_id=${encodeURIComponent(instanceId)}`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// AI Employee monitoring (ai-employee-service)
|
|
// ---------------------------------------------------------------------------
|
|
// These are the same endpoints the studio builder's "AI Employee → Logs" tab
|
|
// uses. They are NOT app-scoped: the ingress maps studio.getzino.in/monitor
|
|
// straight to ai-employee-service:8090, so the path has no /app/{id} prefix.
|
|
// The app's own JWT is accepted.
|
|
|
|
/** One reasoning pass the employee took, as stored in aiemployee.tbl_ai_decisions. */
|
|
export interface AIDecision {
|
|
id: number;
|
|
ai_user_id: string;
|
|
instance_id: string;
|
|
/** The activity the employee SUBMITTED (its chosen next step), not the one it read. */
|
|
activity_id: string;
|
|
/** State the instance was in when the employee looked at it. */
|
|
instance_state?: string;
|
|
status: "submitted" | "failed" | "error" | "escalated" | "submitting" | string;
|
|
confidence?: number;
|
|
model?: string;
|
|
created_at: string;
|
|
/** Opening plan the model wrote before acting. */
|
|
plan?: string;
|
|
/** Why it chose this action. */
|
|
reasoning?: string;
|
|
/** Self-check pass. */
|
|
reflexion?: {
|
|
concerns?: string;
|
|
should_escalate?: boolean;
|
|
adjusted_confidence?: number;
|
|
} | null;
|
|
knowledge_sources?: string[] | null;
|
|
/** The payload it submitted with the activity. */
|
|
data?: Record<string, unknown>;
|
|
error_message?: string;
|
|
cost?: number;
|
|
llm_calls?: number;
|
|
duration_ms?: number;
|
|
}
|
|
|
|
export interface AIEscalation {
|
|
id: number;
|
|
instance_id: string;
|
|
recommended_action?: string;
|
|
reasoning?: string;
|
|
reason?: string;
|
|
status?: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface AIInstanceLog {
|
|
instance_id: string;
|
|
decisions: AIDecision[];
|
|
escalations: AIEscalation[];
|
|
}
|
|
|
|
/** One step inside a run — a tool call, an LLM call, a context gather, etc. */
|
|
export interface AITraceEvent {
|
|
seq: number;
|
|
parent_seq?: number;
|
|
type: string;
|
|
name: string;
|
|
status: string;
|
|
started_at: string;
|
|
ended_at?: string;
|
|
duration_ms?: number;
|
|
input?: unknown;
|
|
output?: unknown;
|
|
tokens_in?: number;
|
|
tokens_out?: number;
|
|
cost?: number;
|
|
error?: string;
|
|
decision_id?: number;
|
|
}
|
|
|
|
/** One task the employee processed end-to-end. */
|
|
export interface AITrace {
|
|
trace_id: string;
|
|
events: AITraceEvent[];
|
|
}
|
|
|
|
export interface AIInstanceTrace {
|
|
instance_id: string;
|
|
traces: AITrace[];
|
|
}
|
|
|
|
export function getAIInstanceLog(instanceId: string): Promise<AIInstanceLog> {
|
|
return request("GET", `/monitor/instances/${encodeURIComponent(instanceId)}/log`);
|
|
}
|
|
|
|
export function getAIInstanceTrace(instanceId: string): Promise<AIInstanceTrace> {
|
|
return request("GET", `/monitor/instances/${encodeURIComponent(instanceId)}/trace`);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Field-file download
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Fetches a stored form-field file (e.g. the uploaded invoice PDF) as a Blob.
|
|
* `blobPath` comes from the FileMeta the upload returned and is echoed back in
|
|
* the activity's submitted data. Returns a Blob so the caller can either build
|
|
* an object URL for inline preview or trigger a download — the endpoint sets
|
|
* Content-Disposition: attachment, so a plain link would always download.
|
|
*/
|
|
export async function downloadFieldFile(blobPath: string, originalName?: string): Promise<Blob> {
|
|
const params = new URLSearchParams({ path: blobPath });
|
|
if (originalName) params.set("name", originalName);
|
|
const token = localStorage.getItem(TOKEN_KEY);
|
|
const res = await fetch(`${baseUrl()}/app/${APP_ID}/download?${params.toString()}`, {
|
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
});
|
|
if (!res.ok) {
|
|
if (res.status === 401) {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
window.location.href = `${basePath()}login`;
|
|
}
|
|
throw new Error(`Could not load the document (${res.status})`);
|
|
}
|
|
return res.blob();
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// RDBMS lookup records — used by the demo calendar to pull rows from the
|
|
// `mahindra_demo` schema via the lookup-field config on the INIT activity.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface RdbmsLookupResponse {
|
|
records: Record<string, unknown>[];
|
|
total: number;
|
|
limit: number;
|
|
offset: number;
|
|
}
|
|
|
|
export interface RdbmsLookupQuery {
|
|
workflowId: number;
|
|
activityId: string;
|
|
fieldId: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
search?: string;
|
|
}
|
|
|
|
export function fetchRdbmsLookupRecords(templateUuid: string, q: RdbmsLookupQuery): Promise<RdbmsLookupResponse> {
|
|
return request("POST", `/app/${APP_ID}/rdbms-templates/${encodeURIComponent(templateUuid)}/records`, {
|
|
workflow_id: q.workflowId,
|
|
activity_id: q.activityId,
|
|
field_id: q.fieldId,
|
|
limit: q.limit ?? 1000,
|
|
offset: q.offset ?? 0,
|
|
search: q.search ?? "",
|
|
});
|
|
}
|