Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
0453618
feat: added draft catalog step
alvarobernal2412 Mar 25, 2025
c73cd63
feat(dashboard): added empty dashboard form
alvarobernal2412 Apr 17, 2025
0493542
Merge branch 'multi-step-form' into enhance-dashboard-module
alvarobernal2412 Apr 19, 2025
dd52840
feat(catalog): enhanced controls creation step
alvarobernal2412 Apr 19, 2025
0014d7c
feat(panel): added panel creation form
alvarobernal2412 Apr 21, 2025
18d34a9
feat(multi-step): added catalog recovery
alvarobernal2412 Apr 21, 2025
97b6a59
refactor: rewrite calendar component for react-day-picker v9
ferferga Apr 21, 2025
80ac95c
Merge remote-tracking branch 'origin/fix-reactdarypicker-v9-shadcn' i…
alvarobernal2412 Apr 21, 2025
0c910bd
Merge remote-tracking branch 'origin' into enhance-dashboard-module
alvarobernal2412 Apr 21, 2025
49bceef
Merge branch 'main' into enhance-dashboard-module
alvarobernal2412 May 14, 2025
2368a76
feat(catalog): added edit functionality
alvarobernal2412 May 14, 2025
fa2c653
refactor(dashboard): simplified panel model
alvarobernal2412 May 14, 2025
49498c3
feat(panel): enhanced SQL query builder and form UI
alvarobernal2412 May 19, 2025
084da52
feat(panel): improved panel preview functionality
alvarobernal2412 May 19, 2025
277b038
feat(dashboard): added new panel types and restructure options
alvarobernal2412 May 19, 2025
8b5cc56
refactor(dashboard): clean up code and improve type handling
alvarobernal2412 May 19, 2025
e45c46c
feat(dashboard): simplify dashboard configuration UI
alvarobernal2412 May 19, 2025
5a48271
feat(dashboard): integrated panel creation in multi-step-form
alvarobernal2412 May 20, 2025
dd18dd3
feat: improve dashboard draft handling
alvarobernal2412 May 21, 2025
a14a0af
fix: enhance control error handling
alvarobernal2412 May 21, 2025
e275b27
fix: improve panel handling
alvarobernal2412 May 21, 2025
f5b9e72
Merge branch 'main' into enhance-dashboard-module
alvarobernal2412 Jun 25, 2025
6abbf60
chore(deps): update npm (development)
renovate[bot] Jun 25, 2025
8feb351
feat: added wip mashup testing button
tirirote Jun 26, 2025
72ea5e2
feat: added wip mashup testing button
tirirote Jun 26, 2025
32f15f2
Merge branch 'feature/mashup-test-button' into feature/mashup-test-b…
tirirote Jun 27, 2025
582884c
feat: added full wip testing button
tirirote Jul 1, 2025
0105b78
wip(panels): added panel card
alvarobernal2412 Jul 3, 2025
41e1023
feat: added wip changes for the testing button
tirirote Jul 21, 2025
9f671eb
feat: replace modal mashup testing with dedicated page
alvarobernal2412 Jul 22, 2025
94916c6
Merge branch 'enhance-dashboard-module' into feature/mashup-test-button
alvarobernal2412 Jul 22, 2025
4d898d2
feat: add IndexedDB utility for mashup test persistence
alvarobernal2412 Jul 22, 2025
e714940
feat: add custom hook for mashup test management
alvarobernal2412 Jul 22, 2025
ba5f2d0
feat: add reusable components for test results display
alvarobernal2412 Jul 22, 2025
d6af37c
refactor: update control forms to integrate with test persistence
alvarobernal2412 Jul 22, 2025
a65edb4
feat: enhance mashup pages with persistent test results
alvarobernal2412 Jul 22, 2025
73d7681
Merge branch 'main' into feature/mashup-test-button
alvarobernal2412 Jul 22, 2025
8087471
feat: updated forms input feedback
tirirote Jul 23, 2025
f4c5254
fix: fixed input feedback for catalog wizard
tirirote Jul 23, 2025
097291d
chore: minor updates on the UI
tirirote Jul 23, 2025
4fcb18d
fix: unified ui styles and layout
tirirote Jul 23, 2025
ada26d7
fix: minor ui bug fixe
tirirote Jul 23, 2025
cfacf00
chore: improved ui feedback
tirirote Jul 24, 2025
dc9b39f
chore: reverted branch to previous commit
tirirote Jul 24, 2025
f95bfdf
Merge branch 'feature/mashup-test-button' of https://github.com/statu…
tirirote Jul 29, 2025
7f2049e
Revert "chore: reverted branch to previous commit"
tirirote Jul 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
539 changes: 277 additions & 262 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
},
"dependencies": {
"@hookform/resolvers": "5.1.1",
"@radix-ui/react-popover": "^1.0.0",
"@tanstack/react-table": "8.21.3",
"ahooks": "3.8.5",
"axios": "1.10.0",
Expand All @@ -47,6 +48,7 @@
"react-router-dom": "7.6.2",
"sonner": "2.0.5",
"tailwind-merge": "3.3.1",
"uuid": "11.1.0",
"zod": "3.25.67"
},
"devDependencies": {
Expand All @@ -55,20 +57,20 @@
"@eslint/js": "9.29.0",
"@eslint/json": "0.12.0",
"@stylistic/eslint-plugin-js": "4.4.1",
"@types/node": "24.0.3",
"@types/node": "24.0.4",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"@unocss/eslint-config": "66.2.3",
"@unocss/eslint-config": "66.3.1",
"@vitejs/plugin-react-swc": "3.10.2",
"browserslist": "4.25.0",
"browserslist": "4.25.1",
"eslint": "9.29.0",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "5.2.0",
"eslint-plugin-react-refresh": "0.4.20",
"globals": "16.2.0",
"husky": "9.1.7",
"lightningcss": "1.30.1",
"unocss": "66.2.3",
"unocss": "66.3.1",
"unocss-preset-animations": "1.2.1",
"vite": "6.3.5"
}
Expand Down
212 changes: 212 additions & 0 deletions src/components/mashups/ControlCreationAndTestView.jsx
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is a mix between a component and a page, a component must be reusable and a page must include only its features. E.g. The Mashup Page must only include the fetch, see (redirect to Details page), test mashup (redirect to Test Mashup Page) and delete features.

Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Button } from '@/components/ui/button';
import { Loader2, ExternalLink } from 'lucide-react'; // Importa ExternalLink
import { toast } from 'sonner';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';

import { NewControlForm } from '@/forms/control/new/form'; // Tu formulario de creación de control
import { executeNodeRedMashup, getAllNodeRedFlows, createDraftCatalog } from '@/services/mashups'; // Servicios necesarios

export function ControlCreationAndTestView({ mashup, isOpen, onClose }) {
const [draftCatalogId, setDraftCatalogId] = useState(null);
const [isFormCreating, setIsFormCreating] = useState(true);
const [loadingInitialSetup, setLoadingInitialSetup] = useState(false);
const [loadingTestExecution, setLoadingTestExecution] = useState(false);
const [testResults, setTestResults] = useState(null);
const [mashupDetails, setMashupDetails] = useState(null);

// 1. Efecto para inicializar el catálogo borrador y obtener los detalles del mashup cuando la vista se abre
useEffect(() => {
if (isOpen && (!draftCatalogId || !mashupDetails)) { // Solo si está abierta y faltan datos
const initializeEnvironment = async () => {
setLoadingInitialSetup(true);
try {
// Crear catálogo borrador
if (!draftCatalogId) {
const newCatalog = await createDraftCatalog();
if (!newCatalog || !newCatalog.id) {
throw new Error('No se pudo crear el catálogo borrador o falta el ID.');
}
setDraftCatalogId(newCatalog.id);
toast.success(`Configuración de prueba: Catálogo borrador "${newCatalog.name}" listo.`, { duration: 3000 });
}

// Obtener detalles completos del mashup
if (mashup && mashup.id && !mashupDetails) {
const allMashupsResponse = await getAllNodeRedFlows();
const foundMashup = allMashupsResponse.data.find(m => m.id === mashup.id);
if (foundMashup) {
setMashupDetails(foundMashup);
} else {
toast.error('No se encontraron detalles del Mashup proporcionado. Asegúrate de que existe.', { duration: 5000 });
onClose(); // Cerrar si no se encuentra el mashup
return; // Salir para evitar más errores
}
}

} catch (error) {
console.error('Error al configurar el entorno de prueba:', error);
toast.error(`Error al configurar el entorno de prueba: ${error.message || 'Error desconocido'}`, { duration: 5000 });
onClose(); // Cerrar la vista si falla la inicialización crítica
} finally {
setLoadingInitialSetup(false);
}
};
initializeEnvironment();
}
}, [isOpen, draftCatalogId, mashup, mashupDetails, onClose]);


// Callback que se ejecuta cuando el control es creado exitosamente en el formulario
const handleControlCreated = useCallback(async (controlId, formMashupId) => {
console.log('[ControlCreationAndTestView] Mashup details: ', JSON.stringify(mashupDetails, null, 2));
console.log('[ControlCreationAndTestView] handleControlCreated llamado. controlId:', controlId, 'formMashupId:', formMashupId);
setIsFormCreating(false); // Cambiamos a la fase de visualización de resultados
setTestResults(null); // Limpiamos resultados anteriores

// Aseguramos que tenemos los detalles completos del mashup antes de ejecutar
if (!mashupDetails || mashupDetails.id !== formMashupId || !mashupDetails.endpoint) {
toast.error('No se pudo obtener la URL del Mashup para la ejecución. Inténtalo de nuevo.');
console.error('Error: mashupDetails no válido o URL faltante para la ejecución del test.');
setLoadingTestExecution(false); // Asegúrate de que el spinner se desactive
return;
}

setLoadingTestExecution(true);
toast.loading('Ejecutando prueba del mashup de Node-RED...', { id: 'mashup-test', duration: 0 }); // Toast persistente

try {
// Prepara el payload para el mashup de Node-RED.
// Puedes ajustar esto según lo que tu Node-RED espere.
const payloadForMashup = {
payload: `Test execution for Control ID: ${controlId} - ${new Date().toLocaleString()}`,
controlId: controlId,
mashupId: formMashupId,
catalogId: draftCatalogId,
// Puedes añadir aquí otros parámetros relevantes para tu test
};

console.log('[ControlCreationAndTestView] Ejecutando mashup con URL:', mashupDetails.endpoint, 'y payload:', payloadForMashup);
const mashupResult = await executeNodeRedMashup(mashupDetails.endpoint, payloadForMashup);

setTestResults(mashupResult);
toast.success('¡Mashup ejecutado exitosamente!', {
id: 'mashup-test',
description: (
<div className="mt-2 text-xs text-wrap break-all max-h-40 overflow-y-auto">
<h4 className="font-semibold mb-1">Resultados:</h4>
<pre className="whitespace-pre-wrap">{JSON.stringify(mashupResult, null, 2)}</pre>
</div>
),
duration: 8000 // Deja el toast más tiempo para leer los resultados
});

} catch (error) {
console.error('Error durante la ejecución de la prueba del mashup de Node-RED:', error);
const errorMessage = error.response?.data?.message || error.message || 'Error desconocido';
toast.error(`La prueba del mashup falló: ${errorMessage}`, { id: 'mashup-test', duration: 8000 });
} finally {
setLoadingTestExecution(false);
console.log('[ControlCreationAndTestView] Ejecución del mashup finalizada. loadingTestExecution es false.');
}
}, [mashupDetails, draftCatalogId]); // Dependencias del useCallback

// Callback para reiniciar la vista y permitir la creación de un nuevo control
const handleReset = useCallback(() => {
setDraftCatalogId(null); // Esto forzará la creación de un nuevo catálogo borrador
setIsFormCreating(true);
setTestResults(null);
setLoadingInitialSetup(false); // Asegúrate de que se reinicie para re-ejecutar el efecto de inicialización
setMashupDetails(null); // Reinicia también los detalles del mashup
toast.info('Vista de prueba reiniciada. Lista para la creación de un nuevo control.');
}, []);

// Renderizado del Diálogo Principal
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent
className="max-w-[800px] w-full p-6 max-h-[90vh] overflow-y-auto"
aria-describedby="control-creation-dialog-description"
>
<DialogHeader>
<DialogTitle className="text-xl font-bold">
{isFormCreating ? `Crear Control para Mashup: ${mashup?.name || 'Cargando...'}` : `Testeo de Mashup: ${mashup?.name || 'Cargando...'}`}
</DialogTitle>
<DialogDescription id="control-creation-dialog-description">
{isFormCreating ? 'Crea un nuevo control asociado a un catálogo borrador para iniciar el test.' : 'Control creado. Ejecutando o mostrando resultados de la prueba del mashup.'}
</DialogDescription>
</DialogHeader>

{loadingInitialSetup ? (
<div className="flex justify-center items-center h-40">
<Loader2 className="mr-2 h-6 w-6 animate-spin" />
Preparando entorno de prueba y cargando detalles del Mashup...
</div>
) : (
<>
{isFormCreating ? (
// Solo renderizamos el formulario si el draftCatalogId y mashupDetails están disponibles
draftCatalogId && mashupDetails ? (
<NewControlForm
catalogId={draftCatalogId}
onClose={onClose}
onSuccess={handleControlCreated}
mashupIdPreselected={mashupDetails.id} // Pasa el ID del mashup completo
/>
) : (
<div className="flex justify-center items-center h-40 text-red-600">
<p>No se pudo cargar el entorno de prueba. Intenta reiniciar.</p>
</div>
)
) : (
// Sección para mostrar los resultados de la prueba
<div className="space-y-4 pt-4">
<h3 className="text-lg font-medium border-b pb-2 mb-2">Resultados del Test de Mashup</h3>
{loadingTestExecution ? (
<div className="flex justify-center items-center py-4">
<Loader2 className="mr-2 h-5 w-5 animate-spin" /> Ejecutando prueba...
</div>
) : testResults ? (
<div>
<h4 className="text-md font-medium mb-2">Respuesta del Mashup:</h4>
<pre className="bg-gray-100 p-3 rounded-md text-sm whitespace-pre-wrap max-h-[300px] overflow-auto">
{typeof testResults === 'object' ? JSON.stringify(testResults, null, 2) : String(testResults)}
</pre>
{/* Botón para ver el Mashup en Node-RED */}
{mashupDetails?.id && (
<div className="mt-4 flex justify-end">
<Button
variant="outline"
onClick={() => window.open(`/red#flow/${mashupDetails.id}`, '_blank')}
>
<ExternalLink className="mr-2 h-4 w-4" /> Ver Mashup en Node-RED
</Button>
</div>
)}
</div>
) : (
<p>La prueba del mashup ha finalizado o no se obtuvieron resultados.</p>
)}

<div className="flex justify-end space-x-2 mt-4">
<Button variant="outline" onClick={handleReset} disabled={loadingTestExecution}>
Crear Nuevo Control / Reiniciar Prueba
</Button>
<Button variant="outline" onClick={onClose} disabled={loadingTestExecution}>
Cerrar Vista de Prueba
</Button>
</div>
</div>
)}
</>
)}
</DialogContent>
</Dialog>
);
}
85 changes: 85 additions & 0 deletions src/components/ui/combobox.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// src/components/ui/combobox.jsx
import * as React from "react"
import { Check, ChevronsUpDown } from "lucide-react"

import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"

/**
* A reusable ComboBox component for selecting from a list of options with search functionality.
*
* @param {object} props - The component props.
* @param {Array<object>} props.options - An array of objects, where each object has `value` and `label` properties.
* @param {string} props.value - The current selected value.
* @param {function} props.onValueChange - Callback function when the value changes.
* @param {string} [props.placeholder="Select option..."] - Placeholder text for the input.
* @param {string} [props.className=""] - Optional CSS class names for the component.
* @param {boolean} [props.disabled=false] - If true, the combobox will be disabled.
*/
export function ComboBox({ options, value, onValueChange, placeholder = "Select option...", className = "", disabled = false }) {
const [open, setOpen] = React.useState(false)

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className={cn("w-full justify-between", className)}
disabled={disabled}
>
{value
? options.find((option) => option.value === value)?.label
: placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[--radix-popover-trigger-width] p-0">
<Command>
<CommandInput placeholder={placeholder} />
<CommandList>
<CommandEmpty>No option found.</CommandEmpty>
<CommandGroup>
{options.map((option) => (
<CommandItem
key={option.value}
value={option.label} // Use label for search, value for actual selection
onSelect={(currentLabel) => {
// Find the option by its label
const selectedOption = options.find(
(opt) => opt.label.toLowerCase() === currentLabel.toLowerCase()
);
onValueChange(selectedOption ? selectedOption.value : "");
setOpen(false);
}}
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === option.value ? "opacity-100" : "opacity-0"
)}
/>
{option.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)
}
Loading