198 lines
6.9 KiB
JavaScript
198 lines
6.9 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { toast } from 'react-toastify';
|
|
import nodeService from '../../services/nodes';
|
|
import Modal from '../common/Modal';
|
|
import { PlayIcon, ArrowPathIcon } from '@heroicons/react/24/outline';
|
|
|
|
const NodeTester = ({ node, onClose }) => {
|
|
const [inputData, setInputData] = useState('{}');
|
|
const [testResults, setTestResults] = useState(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [inputError, setInputError] = useState(null);
|
|
|
|
// Test node configuration with provided input
|
|
const handleTestNode = async () => {
|
|
// Validate JSON input
|
|
try {
|
|
JSON.parse(inputData);
|
|
setInputError(null);
|
|
} catch (error) {
|
|
setInputError('Invalid JSON format');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setLoading(true);
|
|
const parsedInput = JSON.parse(inputData);
|
|
|
|
const response = await nodeService.testNodeConfig(
|
|
node.data.nodeType,
|
|
node.data.config || {},
|
|
parsedInput
|
|
);
|
|
|
|
setTestResults(response.data);
|
|
toast.success('Node test completed successfully');
|
|
} catch (error) {
|
|
console.error('Error testing node:', error);
|
|
toast.error('Failed to test node configuration');
|
|
|
|
// If we have error details from the API
|
|
if (error.response?.data?.error) {
|
|
setTestResults({
|
|
success: false,
|
|
error: error.response.data.error,
|
|
output: null
|
|
});
|
|
} else {
|
|
setTestResults({
|
|
success: false,
|
|
error: error.message || 'An unknown error occurred',
|
|
output: null
|
|
});
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// Format JSON for display
|
|
const formatJSON = (json) => {
|
|
try {
|
|
if (typeof json === 'string') {
|
|
return JSON.stringify(JSON.parse(json), null, 2);
|
|
}
|
|
return JSON.stringify(json, null, 2);
|
|
} catch (error) {
|
|
return json;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white shadow-xl rounded-lg overflow-hidden max-w-4xl w-full">
|
|
<div className="px-4 py-5 sm:px-6 border-b border-gray-200">
|
|
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
|
Test Node: {node.data.label}
|
|
</h3>
|
|
<p className="mt-1 text-sm text-gray-500">
|
|
Test this node with sample input data
|
|
</p>
|
|
</div>
|
|
|
|
<div className="px-4 py-5 sm:p-6">
|
|
<div className="space-y-6">
|
|
{/* Input section */}
|
|
<div>
|
|
<label htmlFor="input-data" className="block text-sm font-medium text-gray-700">
|
|
Input Data (JSON)
|
|
</label>
|
|
<div className="mt-1">
|
|
<textarea
|
|
id="input-data"
|
|
name="input-data"
|
|
rows={5}
|
|
className={`shadow-sm block w-full focus:ring-primary-500 focus:border-primary-500 sm:text-sm border-gray-300 rounded-md font-mono ${
|
|
inputError ? 'border-red-300' : ''
|
|
}`}
|
|
value={inputData}
|
|
onChange={(e) => setInputData(e.target.value)}
|
|
placeholder='{"key": "value"}'
|
|
/>
|
|
{inputError && (
|
|
<p className="mt-1 text-sm text-red-600">{inputError}</p>
|
|
)}
|
|
</div>
|
|
<p className="mt-2 text-sm text-gray-500">
|
|
Enter sample input data in JSON format to test this node
|
|
</p>
|
|
</div>
|
|
|
|
{/* Test button */}
|
|
<div>
|
|
<button
|
|
type="button"
|
|
onClick={handleTestNode}
|
|
disabled={loading}
|
|
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:opacity-50"
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<ArrowPathIcon className="h-5 w-5 mr-2 animate-spin" />
|
|
Testing...
|
|
</>
|
|
) : (
|
|
<>
|
|
<PlayIcon className="h-5 w-5 mr-2" />
|
|
Run Test
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Results section */}
|
|
{testResults && (
|
|
<div className="border rounded-md overflow-hidden">
|
|
<div className="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
|
<h4 className="text-sm font-medium text-gray-900">Test Results</h4>
|
|
</div>
|
|
<div className="px-4 py-3">
|
|
<div className="space-y-4">
|
|
{/* Status */}
|
|
<div>
|
|
<div className="text-sm font-medium text-gray-700">Status:</div>
|
|
<div className={`mt-1 text-sm ${testResults.success ? 'text-green-600' : 'text-red-600'}`}>
|
|
{testResults.success ? 'Success' : 'Error'}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Error (if any) */}
|
|
{testResults.error && (
|
|
<div>
|
|
<div className="text-sm font-medium text-gray-700">Error:</div>
|
|
<div className="mt-1 bg-red-50 text-red-700 p-3 rounded text-sm font-mono overflow-auto">
|
|
{testResults.error}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Output */}
|
|
{testResults.output !== null && (
|
|
<div>
|
|
<div className="text-sm font-medium text-gray-700">Output:</div>
|
|
<div className="mt-1 bg-gray-50 p-3 rounded text-sm font-mono overflow-auto">
|
|
<pre>{formatJSON(testResults.output)}</pre>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Execution time */}
|
|
{testResults.execution_time && (
|
|
<div>
|
|
<div className="text-sm font-medium text-gray-700">Execution Time:</div>
|
|
<div className="mt-1 text-sm text-gray-600">
|
|
{testResults.execution_time} ms
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
|
<button
|
|
type="button"
|
|
onClick={onClose}
|
|
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default NodeTester;
|