import React, { useState, useEffect } from 'react'; import type { TestConfig, TestFieldSchema, TestFunctionSchema } from '../types'; import { formatFieldName, getTypeHint, getDefaultValueForType, generateDefaultFromSchema } from '../utils/formatting'; // Minimum query length to trigger search dropdown options const MIN_SEARCH_QUERY_LENGTH_FOR_DROPDOWN = 2; interface TestModalProps { isOpen: boolean; functionName: string | null; testConfig: TestConfig | null; onClose: () => void; } export const TestModal: React.FC = ({ isOpen, functionName, testConfig, onClose }) => { const [schema, setSchema] = useState(null); const [loading, setLoading] = useState(false); const [testResults, setTestResults] = useState(null); const [executing, setExecuting] = useState(false); const [resultView, setResultView] = useState<'table' | 'json'>('table'); const [formData, setFormData] = useState({}); const [env, setEnv] = useState('dev'); const [accessGroups, setAccessGroups] = useState([]); const [fieldOptions, setFieldOptions] = useState>({}); useEffect(() => { if (testConfig?.accessGroups) { setAccessGroups(testConfig.accessGroups); } }, [testConfig]); useEffect(() => { if (!isOpen || !functionName) { setSchema(null); setTestResults(null); setFormData({}); setFieldOptions({}); return; } const loadSchema = async () => { setLoading(true); try { const res = await fetch(`/api/function/${functionName}/schema`); const data = await res.json(); if (data.error) { throw new Error(data.error); } setSchema(data); // Initialize form data with defaults for user context const initialData: any = {}; data.schema.forEach((field: TestFieldSchema) => { if (field.isUserContext && field.innerSchema) { initialData[field.name] = generateDefaultFromSchema(field.innerSchema); } }); setFormData(initialData); } catch (err) { console.error('Failed to load schema:', err); } finally { setLoading(false); } }; loadSchema(); }, [isOpen, functionName]); if (!isOpen) return null; const handleBackdropClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { onClose(); } }; const handleExecute = async () => { if (!schema) return; setExecuting(true); try { const inputs: any = {}; const mockUserContext: any = {}; schema.schema.forEach((field: TestFieldSchema) => { const value = formData[field.name]; if (value !== undefined && value !== '') { if (field.isUserContext || field.isOrganizationContext) { mockUserContext[field.name] = value; } else { inputs[field.name] = value; } } }); const res = await fetch(`/api/test/${functionName}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ env, accessGroups, mockUserContext, inputs }), }); const result = await res.json(); setTestResults(result); setResultView('table'); } catch (err) { setTestResults({ success: false, error: (err as Error).message, }); } finally { setExecuting(false); } }; const loadFieldOptions = async (fieldName: string, sourceFunctionName: string) => { try { const res = await fetch(`/api/function/${sourceFunctionName}/options`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ env, accessGroups }), }); const data = await res.json(); if (data.error) throw new Error(data.error); setFieldOptions(prev => ({ ...prev, [fieldName]: data.options || [] })); } catch (err) { console.error('Failed to load options:', err); } }; const searchFieldOptions = async (fieldName: string, sourceFunctionName: string, query: string) => { if (query.length < MIN_SEARCH_QUERY_LENGTH_FOR_DROPDOWN) { setFieldOptions(prev => ({ ...prev, [fieldName]: [] })); return; } try { const res = await fetch(`/api/function/${sourceFunctionName}/options`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ env, accessGroups, query }), }); const data = await res.json(); if (data.error) throw new Error(data.error); setFieldOptions(prev => ({ ...prev, [fieldName]: data.options || [] })); } catch (err) { console.error('Failed to search options:', err); } }; return (
Test: {functionName} Test Mode
{loading && (
Loading...
)} {!loading && schema && ( <> {schema.description && (

{schema.description}

)} {/* Test Context Section */}
Test Context
{(testConfig?.accessGroups || []).map(g => ( ))} {(!testConfig?.accessGroups || testConfig.accessGroups.length === 0) && ( No access groups defined )}
{/* Function Inputs */} {/* Execute Button */} {/* Test Results */} {testResults && ( )} )}
); }; // Test Fields Component const TestFields: React.FC<{ schema: TestFunctionSchema; formData: any; setFormData: (data: any) => void; fieldOptions: Record; loadFieldOptions: (fieldName: string, sourceFunctionName: string) => void; searchFieldOptions: (fieldName: string, sourceFunctionName: string, query: string) => void; }> = ({ schema, formData, setFormData, fieldOptions, loadFieldOptions, searchFieldOptions }) => { const regularFields = schema.schema.filter(f => !f.isUserContext && !f.isOrganizationContext && !f.fieldFrom); const fieldFromFields = schema.schema.filter(f => f.fieldFrom); const userContextFields = schema.schema.filter(f => f.isUserContext); const orgContextFields = schema.schema.filter(f => f.isOrganizationContext); const updateField = (name: string, value: any) => { setFormData((prev: any) => ({ ...prev, [name]: value })); }; return ( <> {(regularFields.length > 0 || fieldFromFields.length > 0) && (
Function Inputs
{regularFields.map(field => ( updateField(field.name, value)} /> ))} {fieldFromFields.map(field => ( updateField(field.name, value)} options={fieldOptions[field.name] || []} onLoadOptions={loadFieldOptions} onSearchOptions={searchFieldOptions} /> ))}
)} {userContextFields.length > 0 && ( )} {orgContextFields.length > 0 && ( )} ); }; // Individual Form Field const FormField: React.FC<{ field: TestFieldSchema; value: any; onChange: (value: any) => void; }> = ({ field, value, onChange }) => { const inputId = `test-input-${field.name}`; const displayName = formatFieldName(field.name); const typeHint = getTypeHint(field.schema); if (field.schema.type === 'boolean') { return (
onChange(e.target.checked)} />
); } if (field.schema.enum) { return (
); } if (field.schema.type === 'number' || field.schema.type === 'integer') { return (
onChange(e.target.value ? Number(e.target.value) : undefined)} placeholder={`Enter a ${field.schema.type}...`} /> {typeHint &&
{typeHint}
}
); } if (field.schema.type === 'object' || field.schema.type === 'array') { return (