All files / components/macros MacroTestDialog.tsx

76.47% Statements 26/34
90.9% Branches 20/22
57.14% Functions 4/7
78.78% Lines 26/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185                                                  83x 83x 83x 83x 83x   83x 27x 9x 9x 9x 9x         9x 9x       83x           83x 3x   3x 3x 3x   3x 3x     2x   1x   3x       83x                                                           2x                                                                                                                                                                      
/**
 * Macro test dialog component
 */
 
import { useState, useEffect } from 'react';
import { AppDialog } from '@/components/common/AppDialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Play, CheckCircle2, AlertCircle, Clock, Hash } from 'lucide-react';
import type { Macro, MacroTestResponse } from '@/types/macro';
import { testMacro } from '@/services/macros.service';
import { handleApiError } from '@/lib/api';
 
interface MacroTestDialogProps {
    open: boolean;
    onOpenChange: (open: boolean) => void;
    macro: Macro | null;
}
 
export default function MacroTestDialog({
    open,
    onOpenChange,
    macro,
}: MacroTestDialogProps) {
    const [paramValues, setParamValues] = useState<unknown[]>([]);
    const [paramNames, setParamNames] = useState<string[]>([]);
    const [testResult, setTestResult] = useState<MacroTestResponse | null>(null);
    const [isTesting, setIsTesting] = useState(false);
    const [error, setError] = useState<string | null>(null);
 
    useEffect(() => {
        if (open && macro) {
            try {
                const names = JSON.parse(macro.parameters);
                setParamNames(names);
                setParamValues(new Array(names.length).fill(''));
            } catch {
                setParamNames([]);
                setParamValues([]);
            }
            setTestResult(null);
            setError(null);
        }
    }, [open, macro]);
 
    const handleParamChange = (index: number, value: string) => {
        const newValues = [...paramValues];
        newValues[index] = value;
        setParamValues(newValues);
    };
 
    const handleTest = async () => {
        Iif (!macro) return;
 
        setIsTesting(true);
        setError(null);
        setTestResult(null);
 
        try {
            const result = await testMacro(macro.id, {
                parameters: paramValues,
            });
            setTestResult(result);
        } catch (err) {
            setError(handleApiError(err));
        } finally {
            setIsTesting(false);
        }
    };
 
    return (
        <AppDialog
            open={open}
            onOpenChange={onOpenChange}
            title={`Test Macro: @${macro?.name}`}
            description="Execute the macro in a transactional test mode. Changes will be rolled back."
            className="max-w-2xl"
            footer={
                <Button variant="outline" onClick={() => onOpenChange(false)} disabled={isTesting}>
                    Close
                </Button>
            }
        >
            <div className="space-y-6">
                {/* SQL Preview */}
                <div className="space-y-2">
                    <Label className="text-xs uppercase text-muted-foreground font-semibold">SQL Query</Label>
                    <div className="bg-muted p-3 rounded-md font-mono text-sm overflow-x-auto whitespace-pre">
                        {macro?.sql_query}
                    </div>
                </div>
 
                {/* Parameters Input */}
                <div className="space-y-4">
                    <Label className="text-xs uppercase text-muted-foreground font-semibold">Test Parameters</Label>
                    {paramNames.length === 0 ? (
                        <p className="text-sm text-muted-foreground italic">No parameters required for this macro.</p>
                    ) : (
                        <div className="grid gap-4">
                            {paramNames.map((name, index) => (
                                <div key={index} className="space-y-2">
                                    <Label htmlFor={`param-${index}`} className="text-sm font-medium">
                                        {name}
                                    </Label>
                                    <Input
                                        id={`param-${index}`}
                                        value={paramValues[index] as string}
                                        onChange={(e) => handleParamChange(index, e.target.value)}
                                        placeholder={`Value for ${name}`}
                                        disabled={isTesting}
                                    />
                                </div>
                            ))}
                        </div>
                    )}
                </div>
 
                {/* Actions */}
                <Button
                    onClick={handleTest}
                    className="w-full gap-2"
                    disabled={isTesting || !macro}
                >
                    {isTesting ? (
                        <Play className="h-4 w-4 animate-pulse" />
                    ) : (
                        <Play className="h-4 w-4" />
                    )}
                    {isTesting ? 'Executing...' : 'Run Test'}
                </Button>
 
                {/* Results */}
                {testResult && (
                    <div className="space-y-4 p-4 border rounded-lg bg-green-50/30 border-green-100">
                        <div className="flex items-center gap-2 text-green-700 font-semibold">
                            <CheckCircle2 className="h-5 w-5" />
                            <span>Execution Successful</span>
                        </div>
 
                        <div className="grid grid-cols-2 gap-4">
                            <div className="space-y-1">
                                <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
                                    <Hash className="h-3 w-3" />
                                    Result
                                </div>
                                <div className="font-mono text-sm break-all font-semibold">
                                    {testResult.result ?? 'null'}
                                </div>
                            </div>
                            <div className="space-y-1">
                                <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
                                    <Clock className="h-3 w-3" />
                                    Execution Time
                                </div>
                                <div className="text-sm font-semibold">
                                    {testResult.execution_time.toFixed(2)} ms
                                </div>
                            </div>
                        </div>
 
                        {testResult.rows_affected !== undefined && (
                            <div className="text-xs text-muted-foreground mt-2 italic px-1">
                                Note: This was a read-only test. Any data modifications were rolled back.
                            </div>
                        )}
                    </div>
                )}
 
                {error && (
                    <div className="space-y-3 p-4 border rounded-lg bg-destructive/5 border-destructive/10">
                        <div className="flex items-center gap-2 text-destructive font-semibold">
                            <AlertCircle className="h-5 w-5" />
                            <span>Execution Failed</span>
                        </div>
                        <div className="text-sm text-muted-foreground whitespace-pre-wrap font-mono p-2 bg-destructive/5 rounded">
                            {error}
                        </div>
                    </div>
                )}
            </div>
        </AppDialog>
    );
}