rordenerena / Permisador ZM

// ==UserScript==
// @name         Permisador ZM
// @namespace    http://zengarden/zm
// @version      1.1.0.20190705
// @description  Genera un Report a partir de la edición actual y permite asignar permisos de forma global
// @author       Dani DAN<danidan@danidan.com>
// @match        http://zengarden/zm/*/process.php?idproceso=12*
// @license      MIT
// @grant        none
// ==/UserScript==

var $ = window.jQuery;
var arbol = {};
var listaIdNiveles = [];
var nombreFichero = "";
var idUser = 0;
var TREE_GENERATION_TIMEOUT = 40000;
var NOMBRE_VERSION_ESPECIAL = "";
var baseUrl = "http://zengarden/zm/p/";
var arrayEtapas = ["preparacion_y_planificacion", "pruebas_funcionales", "pruebas_de_regresion", "analisis_de_datos_y_documentacion", "reuniones_y_consultas", "pruebas_de_interfaz", "pruebas_de_robustez", "pruebas_de_exploracion", "pruebas_de_integracion_del_sistema", "pruebas_de_humo" ];
var arrayTareas = ["ejecucion_de_pruebas", "diseno_de_pruebas", "analisis_de_incidencias"];
var arrayEmpleados = [
	{ "value": "65", "text": "Daniel García García"},
	{ "value": "113", "text": "Víctor Vaquerizo Segovia"},
	{ "value": "116", "text": "Pedro García de la Cruz"},
	{ "value": "146", "text": "Virginia Ruiz Núñez"},
	{ "value": "167", "text": "Emilio José Fragua Castellanos"},
	{ "value": "172", "text": "Víctor González Breña"},
	{ "value": "205", "text": "Lara Resuela Paniagua"},
    { "value": "250", "text": "Dídimo Javier Negro Castellanos"},
/*  { "value": "106", "text": "Cristina Bajo Sánchez"},
    { "value": "173", "text": "Carmen María García Sánchez Majano"},
    { "value": "45", "text": "Elena García Fernández"},
    { "value": "188", "text": "Enrique López Serrano"},
    { "value": "109", "text": "Francisco Javier Fuentes Jiménez"},
    { "value": "211", "text": "Jesús Javier Rivas Morales"},
    { "value": "165", "text": "Pablo Fernández Sanchís"} */
];

function generarSeleccionEmpleados() {

	var div = $('<div/>').attr('id', 'capaEmpleadosPermisos');
    div.attr('idNivel', -1);
	div.css('background-color', '#ffffcc');
	div.css('padding', '5px');
	div.css('position', 'fixed');
	div.css('bottom', '60px');
	div.css('right', '20px');
	div.css('width', '300px');
	div.css('border', '3px solid yellow');
	div.css('z-index', '99999999');

	var ul = $('<ul/>')
	ul.css('list-style-type', 'none');
	var listaInputs = {};
	for(var i=0;i<arrayEmpleados.length;i++) {
		var li = $('<li/>');
		var input = $('<label />').html(arrayEmpleados[i].text).prepend($('<input/>').attr({ type: 'checkbox', id: arrayEmpleados[i].value, class: 'checkBoxPermisador'})).appendTo(li);
		li.appendTo(ul);
	}

	var h3 = $('<h3/>').text("Asignación de permisos").css('text-align', 'center').appendTo(div);

	var x = $('<div/>').text("X").css('position', 'absolute').css('background-color','grey').css('right', '3px').css('top', '3px').css('padding', '0px 5px').attr('id', 'btnCerrarPermisador').css('cursor', 'pointer');
	ul.appendTo(div);
	x.appendTo(div);

	var btnAsignarPermisos = $('<div/>').text("Asignar permisos").css('background-color','grey').css('width', '100%').css('text-align', 'center').css('padding', '0px 5px').attr('id', 'btnPermisar').css('cursor', 'pointer');
	btnAsignarPermisos.appendTo(div);

	div.appendTo($("body")[0]);

	// Creamos el hidden que tendrá los valores de los elementos a "permisar"
	var hidden = $('<input/>').attr('id', 'hiddenNivelesPermisador').attr('type', 'hidden');
	hidden.appendTo($("body")[0]);

	var hiddenNombres = $('<input/>').attr('id', 'hiddenNombresNivelesPermisador').attr('type', 'hidden');
	hiddenNombres.appendTo($("body")[0]);

	$("#btnCerrarPermisador").on('click', function() {
		$(".checkBoxPermisador").prop('checked', false);
		$("#capaEmpleadosPermisos").hide();
	});

	// Ejecuta la asignacion de permisos
	$("#btnPermisar").on('click', async function() {
        let idNivel = div.attr('idNivel');
		let checkBoxes = $(".checkBoxPermisador");

		$("#hiddenNivelesPermisador").val('');
		$("#cargando").css("display", 'block');

		// Almacenamos un listado de los niveles hijo en los hidden #hiddenNivelesPermisador y #hiddenNombresNivelesPermisador
		await obtenerIdsNivelesHijo(idNivel, idUser);

		// Esperamos a que finalicen las peticiones AJAX
			var idsEmpleados = [];
			var nombresEmpleados = [];
			var idsNiveles = $("#hiddenNivelesPermisador").val().split(',');
			var nombresNiveles = $("#hiddenNombresNivelesPermisador").val().split(',');

			// Obtenemos los empleados sobre los que se aplicarán los permisos
			for(var i=0;i<checkBoxes.length;i++)
			{
				if(checkBoxes[i].checked == true) {
					idsEmpleados.push(checkBoxes[i].id);
					nombresEmpleados.push(checkBoxes[i].nextSibling.textContent);
				}
			}

			// Solicitamos confirmación al usuario informándole de las personas y los niveles a modificar
			var textoConfirmacion = "Se asignarán permisos a los siguientes empleados:\n\n";
			for(let i=0;i<nombresEmpleados.length;i++) {
				textoConfirmacion = textoConfirmacion + "\t- " + nombresEmpleados[i] + "\n";
			}
			textoConfirmacion = textoConfirmacion + "\nEn los siguientes niveles: \n";
			for(let i=0; i<nombresNiveles.length;i++) {
				textoConfirmacion = textoConfirmacion + "\t- [" + idsNiveles[i] + "] " + nombresNiveles[i] + "\n";
			}

			textoConfirmacion += "\n¿Deseas continuar?";
			let resultadoConfirm = confirm(textoConfirmacion);
			if(resultadoConfirm) {
				var textoTmp = "";
				for(let i=0;i<idsNiveles.length;i++)
				{
					for(let j=0;j<idsEmpleados.length;j++) {
						var urlAsignacion= baseUrl + "ajax/asignacion.php?rol=2&empleado=" + idsEmpleados[j] + "&idnivel=" + idsNiveles[i] + "&nombre_rol=Empleado&default=0&accion=saveAsignacion";
						await getXML(urlAsignacion);
						console.log('Asignado permiso a [' + idsEmpleados[j] + '] ' + nombresEmpleados[j] + ' en nivel [' + idsNiveles[i] + '] ' + nombresNiveles[i]);
					}
				}
                $(".checkBoxPermisador").prop('checked', false);
                $("#capaEmpleadosPermisos").hide();
                alert('Hecho');
			}
            $("#cargando").css("display", 'none');
	});
    $("#capaEmpleadosPermisos").hide();
}

function mostrarSeleccionPermisos(idNivel) {
    $("#capaEmpleadosPermisos").attr('idNivel', idNivel);
    $("#capaEmpleadosPermisos").show();
}

function generarBotonPermisos(currentEdition) {
    let lc = currentEdition.firstElementChild;

    // Creamos una imagen dinámicamente y la colocamos en un punto concreto
    let img = document.createElement("img");
    img.src = "images/asignaciones.png";
    img.title = "Asignar permisos a la etapa";
    img.style.position = "relative";
    img.style.left = "120px";
    img.style.cursor = "pointer";
    img.classList = "panreport";
    lc.append(img);

    // Creamos los atributos con la información necesaria para el informe:
    // - levelid: ID del nivel de la edición
    let levelid = document.createAttribute('levelid');
    levelid.value = img.parentElement.parentElement.id.toString().split('_')[1];

    // - levelid: Texto con la edición
    let edicion = document.createAttribute('edicion');
    edicion.value = $("#" + currentEdition.id).find("td[id*='namenivel']").text();

    // Iteramos sobre los <tr> de la tabla buscando la primera fila que se encuentre
    // por encima de la actual y que tenga el atributo level=1 (es decir, el proyecto
    // al cual pertenece la edición)
    let nivel = 4;
    let hermanoAnterior = currentEdition;
    while (nivel != 1) {
        hermanoAnterior = hermanoAnterior.previousElementSibling;
        nivel = parseInt(hermanoAnterior.getAttribute("level"), 10);
    }

    // - projectid: ID del nivel del proyecto
    let projectid = document.createAttribute('projectid');
    projectid.value = hermanoAnterior.id.toString().split('_')[1];

    // - projectname: Nombre del proyecto
    let projectname = document.createAttribute('projectname');
    projectname.value = $("#" + hermanoAnterior.id).find("td[id*='namenivel']").text();

    // Asignamos los atributos a la imagen
    img.setAttributeNode(levelid);
    img.setAttributeNode(edicion);
    img.setAttributeNode(projectid);
    img.setAttributeNode(projectname);

    img.onclick = function () {
        mostrarSeleccionPermisos(levelid.value);
    };
}

// Genera un botón para generar el informe en los niveles de las ediciones (nivel 4).
// El atributo 'level' indica la profundidad de la fila (tr), siendo los siguientes niveles
// aplicables para High Level:
// - 1: Proyecto    p.ej. "TMDManager"
// - 2: Versión     p.ej. "1.3"
// - 3: Etapa       p.ej. "Pruebas"
// - 4: Edición     p.ej. "1.3.1"
// - 5: Fase        p.ej. "Pruebas Funcionales"
// - 6: Tarea       p.ej. "Ejecución de Pruebas"
function generarBotonInforme(currentEdition) {
    let lc = currentEdition.firstElementChild;

    // Creamos una imagen dinámicamente y la colocamos en un punto concreto
    let img = document.createElement("img");
    img.src = "images/info_pressed.png";
    img.title = "Generar informe de conclusión de pruebas";
    img.style.position = "relative";
    img.style.left = "120px";
    img.style.cursor = "pointer";
    img.classList = "panreport";
    lc.append(img);

    // Creamos los atributos con la información necesaria para el informe:
    // - levelid: ID del nivel de la edición
    let levelid = document.createAttribute('levelid');
    levelid.value = img.parentElement.parentElement.id.toString().split('_')[1];

    // - levelid: Texto con la edición
    let edicion = document.createAttribute('edicion');
    edicion.value = $("#" + currentEdition.id).find("td[id*='namenivel']").text();

    // Iteramos sobre los <tr> de la tabla buscando la primera fila que se encuentre
    // por encima de la actual y que tenga el atributo level=1 (es decir, el proyecto
    // al cual pertenece la edición)
    let nivel = 4;
    let hermanoAnterior = currentEdition;
    while (nivel != 1) {
        hermanoAnterior = hermanoAnterior.previousElementSibling;
        nivel = parseInt(hermanoAnterior.getAttribute("level"), 10);
    }

    // - projectid: ID del nivel del proyecto
    let projectid = document.createAttribute('projectid');
    projectid.value = hermanoAnterior.id.toString().split('_')[1];

    // - projectname: Nombre del proyecto
    let projectname = document.createAttribute('projectname');
    projectname.value = $("#" + hermanoAnterior.id).find("td[id*='namenivel']").text();

    // Asignamos los atributos a la imagen
    img.setAttributeNode(levelid);
    img.setAttributeNode(edicion);
    img.setAttributeNode(projectid);
    img.setAttributeNode(projectname);

    // En el evento onclick se generará el árbol, se esperará un tiempo prudencial y se guardará en
    // formato JSON.
    img.onclick = function () {
        $("#cargando").css("display", 'block');
        arbol = {};
        arbol.proyecto = {};
        arbol.proyecto.nombre = projectname.value;
        desplegarArbol(projectid.value, idUser, edicion.value, arbol.proyecto, 0);
        window.setTimeout(function () {
            var blob = new Blob([JSON.stringify(arbol, null, 2)], { type: "text/plain;charset=utf-8" });
            $("#cargando").css("display", 'none');
        }, TREE_GENERATION_TIMEOUT);
    };
}

// Recupera del DOM todas las filas cuyo atributo 'level' sea 4 (ediciones) y comprueba si su primer <td>
// tiene algún nodo hijo. En caso de que no tenga hijos, significará que aún no se ha creado el botón, por
// lo que lo crearemos. Si ya tiene hijos, ignoraremos ese nodo.
function comprobarEdicionesVisibles() {
    var ediciones = $("tr[level=4]");
    for (let i = 0; i < ediciones.length; i++) {
        let currentEdition = ediciones[i];
        if (currentEdition.firstChild.childElementCount == 0) {
            generarBotonInforme(currentEdition);
            generarBotonPermisos(currentEdition);
        }
    }
}

// Obtiene una promesa con el código XML de la petición
// Este método se utiliza para invocar la API de ZenManager.
function getXML(url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                resolve(xhr.response);
            } else {
                reject({
                    status: this.status,
                    statusText: xhr.statusText
                });
            }
        };
        xhr.onerror = function () {
            reject({
                status: this.status,
                statusText: xhr.statusText
            });
        };
        xhr.send();
    });
}

// Escapa caracteres especiales a la hora de realizar reemplazos de cadenas
// de texto.
function escapeRegExp(str) {
    return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}

// Sustituye todas las ocurrencias de una cáracter dentro de una cadena de texto.
function replaceAll(str, find, replace) {
    return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}

// Elimina tildes y espacios de un nombre, pasándolo además a minúsculas.
function limpiarNombre(nombre) {
    var nombreFinal = replaceAll(nombre, " ", "_");
    nombreFinal = nombreFinal.toLowerCase();
    nombreFinal = replaceAll(nombreFinal, "á", "a");
    nombreFinal = replaceAll(nombreFinal, "é", "e");
    nombreFinal = replaceAll(nombreFinal, "í", "i");
    nombreFinal = replaceAll(nombreFinal, "ó", "o");
    nombreFinal = replaceAll(nombreFinal, "ú", "u");
    nombreFinal = replaceAll(nombreFinal, "ñ", "n");

    return nombreFinal;
}

async function obtenerIdsNivelesHijo(idNivel, idEmpleado) {
    var url = baseUrl + 'ajax/levelx.php?empleado_id=' + idEmpleado + '&id=nivel_' + idNivel + '&idproceso=12&accion=getNextLevel';
    //console.log(url);
	try{
		let data = await getXML(url)
		var listaIdsTareas = [];
		if(data.length < 20) {
			return;
		}

	    let parser = new DOMParser();
	    let xmlDoc = parser.parseFromString(data, "text/xml");
	    var niveles = xmlDoc.getElementsByTagName("level");
        for (var i = 0; i < niveles.length; i++)
		{
	        var idlevel = niveles[i].getAttribute("idlevel");
	        var nombre = niveles[i].getAttribute("nombre");
			var numeroHijos = niveles[i].getAttribute("numerohijosnivel");
            if((nombre != null) && (arrayEtapas.indexOf(limpiarNombre(nombre)) > -1))
			{
                if(idlevel > 0)
				{
					if($("#hiddenNivelesPermisador").val() == '')
					{
						$("#hiddenNivelesPermisador").val(idlevel);
						$("#hiddenNombresNivelesPermisador").val(nombre);
					}
					else
					{
						$("#hiddenNivelesPermisador").val($("#hiddenNivelesPermisador").val() + "," + idlevel);
						$("#hiddenNombresNivelesPermisador").val($("#hiddenNombresNivelesPermisador").val() + "," + nombre);
					}
                }
            }
            else if(arrayTareas.indexOf(limpiarNombre(nombre)) > -1)
			{
                if(idlevel > 0)
				{
					if($("#hiddenNivelesPermisador").val() == '')
					{
						$("#hiddenNivelesPermisador").val(idlevel);
						$("#hiddenNombresNivelesPermisador").val(idlevel);
					}
					else
					{
						$("#hiddenNivelesPermisador").val($("#hiddenNivelesPermisador").val() + "," + idlevel);
						$("#hiddenNombresNivelesPermisador").val($("#hiddenNombresNivelesPermisador").val() + "," + nombre);
					}
                }
            }
			if(numeroHijos > 0)
			{
				await obtenerIdsNivelesHijo(idlevel, idEmpleado)
			}
        }
	} catch(err) {
		console.error('Error', err.message);
	}
}

// Función recursiva que genera un árbol con la información del proyecto
// Necesita los siguientes parámetros:
// - idNivelProyecto: ID del nivel del proyecto.
// - idEmpleado: ID del empleado
// - edicion: Nombre de la edición
// - arbolProyecto: Dado que la función es recursiva, será el subarbol a rellenar
// - nivelProfundidad: Nivel actual del arbol de proyecto
// El XML tiene un aspecto similar al siguiente:
/*
<root>
	<level tabla='nivel' idlevel='77762' nombrePersonalizado='1'
		idmodelo='465' principal='0' idproceso='12'
		nombre='1.3' children='1' add='0'
		nombreGenericoHijo='High Level - Etapa'
		nombreGenerico='High Level - Versión'
		asignacion='1' ejecutable='0'
		planificable='Hijos' estimable='Hijos' superuser='0'
		Responsable='6,12,13,15,25,29,154,25,154,201,6,12,13,15,25,29,154' Empleado=''
		Visitante='' Director='' Responsable_Desarrollo='201' Informado=''
		Responsable_Pruebas='' Responsable_Añadido='' Responsable_Secundario=''  canAdd='0' suplementario='1' univoco='0'
		oldmodelo='0' tieneFichero='' opcional='0'
		suplementariohijos='0' colorhijos='5,5,2,2,1,1'
		numerohijosnivel='6' tipo_estado='transitorio'
		canAddUnderEM='1'/>
    <level tabla='nivel' idlevel='77763' nombrePersonalizado='1'
    ...
</root>
*/
async function desplegarArbol(idNivelProyecto, idEmpleado, edicion, arbolProyecto, nivelProfundidad) {
    var url = baseUrl + 'ajax/levelx.php?empleado_id=' + idEmpleado + '&id=nivel_' + idNivelProyecto + '&idproceso=12&accion=getNextLevel';
	try {
		let data = await getXML(url)
	    let parser = new DOMParser();
	    let xmlDoc = parser.parseFromString(data, "text/xml");
	    var niveles = xmlDoc.getElementsByTagName("level");
	    var nombreNivel = "nodo";
	    for (var i = 0; i < niveles.length; i++) {
	        var idlevel = niveles[i].getAttribute("idlevel");
	        var nombre = niveles[i].getAttribute("nombre");
	        var numeroHijos = niveles[i].getAttribute("numerohijosnivel");
	        var ejecutable = niveles[i].getAttribute("ejecutable");
	        // Asignaremos el nombre del subárbol a partir del nivel de profundidad, siendo:
	        // 0:  version
	        // 1:  pruebas
	        // 2:  edicion
	        // 3+: nombre del propio nivel, sin tildes ni espacios y en minúsculas
	        switch (nivelProfundidad) {
	            case 0:
	                nombreNivel = "version";
                    if ((NOMBRE_VERSION_ESPECIAL.length > 0) && (nombre.indexOf(NOMBRE_VERSION_ESPECIAL) > -1)) {
                      break;
                    }
	                // Si la versión que estamos revisando no coincide con la edición
	                // de la cual queremos generar el informe, no la añadiremos al árbol
                    else if (edicion.substr(0, edicion.lastIndexOf('.')) != nombre) {
                      continue;
	                }
	                break;
	            case 1:
	                if (nombre == 'Pruebas') {
	                    nombreNivel = "pruebas";
	                }
	                else if (nombre == 'Desarrollo') {
	                    nombreNivel = 'desarrollo';
	                }
	                else {
	                    continue;
	                }
	                break;
	            case 2:
	                nombreNivel = "edicion";
	                // Si la edición que estamos revisando no coincide con aquella para
	                // la que queremos generar el informe, la ignoramos.
	                if (nombre != edicion) {
	                    continue;
	                }
	                break;
	            default:
	                nombreNivel = limpiarNombre(nombre);
	                break;
	        }

	        // Definimos el nivel actual del subárbol, el ID y el número de hijos
	        arbolProyecto.nivelProfundidad = nivelProfundidad;
	        arbolProyecto.id = idNivelProyecto;
	        arbolProyecto.numeroHijos = numeroHijos;

	        // Creamos el subárbol para el nivel actual y le asignamos nombre e id
	        arbolProyecto[nombreNivel] = {};
	        arbolProyecto[nombreNivel].id = idlevel;
	        arbolProyecto[nombreNivel].nombre = nombre;

	        // Cumplimentamos el subárbol con la información del nivel
	        await extraerInformacionNivel(idlevel, arbolProyecto[nombreNivel]);

	        // Si permite ejecuciones, extraemos las horas dedicadas a cada tarea
	        if (ejecutable == 1) {
	            await extraerEjecuciones(idlevel, arbolProyecto[nombreNivel]);
	        }

	        // Si el nivel tiene hijos, invocamos a la función de forma iterativa pasándole el
	        // subárbol como punto de partida.
	        if (numeroHijos > 0) {
	            await desplegarArbol(idlevel, idEmpleado, edicion, arbolProyecto[nombreNivel], nivelProfundidad + 1);
	        }
        }
	} catch(err) {
        console.error('Error', err.message);
/*	})
	.catch(function (err) {
	    console.error('Error', err.statusText);*/
	}
}

// Obtiene los detalles sobre un nivel concreto. Más específicamente, las estimaciones, planificaciones, estados y ejecuciones.
// El fichero XML del cual se extrae la información tiene el siguiente aspecto:
/*
        <root>
        <nivel nombre="TMDManager" nombreCompleto="TMDManager " idnivel="77761" nombrePersonalizado="1">
            <descripcion texto="" valor="1" perso="1" />
            <planificacion fcom="" fobj="" />
            <estimacion horas="" />
            <estado estado="" idestado="" estructuramodelo_has_estado_id="" />
            <ejecucion total="134.60" />
            <relaciones>
                    <ok></ok>
                    </relaciones>
        </nivel>
        </root>
*/
async function extraerInformacionNivel(idNivel, arbolProyecto) {
    var url = baseUrl + 'ajax/information.php?identificador=nivel_' + idNivel + '&idproceso=12&accion=getLatestInfo';
	try
	{
        let data = await getXML(url)
//	.then(function (data) {
	    let parser = new DOMParser();
	    let xmlDoc = parser.parseFromString(data, "text/xml");
	    var niveles = xmlDoc.getElementsByTagName("nivel");
	    for (var i = 0; i < niveles.length; i++) {
	        // Planificaciones
	        var planificaciones = niveles[i].getElementsByTagName("planificacion");
	        if ((planificaciones != null) && (planificaciones.length > 0)) {
	            arbolProyecto.fcom = planificaciones[planificaciones.length - 1].getAttribute("fcom");
	            arbolProyecto.fobj = planificaciones[planificaciones.length - 1].getAttribute("fobj");
	        }

	        // Estimaciones
	        var estimaciones = niveles[i].getElementsByTagName("estimacion");
	        if ((estimaciones != null) && (estimaciones.length > 0)) {
	            arbolProyecto.estimacion = estimaciones[0].getAttribute("horas");
	        }

	        // Estado
	        var estados = niveles[i].getElementsByTagName("estado");
	        if ((estados != null) && (estados.length > 0)) {
	            arbolProyecto.estado = estados[estados.length - 1].getAttribute("estado");
	            arbolProyecto.idestado = estados[estados.length - 1].getAttribute("idestado");
	        }

	        // Ejecución
	        var ejecuciones = niveles[i].getElementsByTagName("ejecucion");
	        if ((ejecuciones != null) && (ejecuciones.length > 0)) {
	            arbolProyecto.ejecucion = ejecuciones[0].getAttribute("total");
	        }
	    }
	} catch(err) {
		console.error('Error', err.message);
/*	})
	.catch(function (err) {
	    console.error('Error', err.statusText);*/
	}
}

// Extrae las ejecuciones de un nivel ejecutable
// El XML a analizar tiene el siguiente aspecto:
/*
<root>
	<entry identrada='491930' fecha='2018-10-09' hora='07:00:00' usuario='lresuela' ejecucion='1' comentario='' edit='0'/>
	<entry identrada='491805' fecha='2018-10-08' hora='11:50:00' usuario='lresuela' ejecucion='3.1' comentario='' edit='0'/>
	<entry identrada='491797' fecha='2018-10-08' hora='07:00:00' usuario='lresuela' ejecucion='4.5' comentario='NO HAY COMENTARIO ASOCIADO' edit='0'/>
</root>
*/
async function extraerEjecuciones(idNivel, arbolProyecto) {

    let url = baseUrl + 'ajax/levelx.php?id=nivel_' + idNivel + '&table=ejecucion&offset=0&dataRows=*&accion=getEntries';
	try {
//	.then(function (data) {
		let data = await getXML(url);
	    let parser = new DOMParser();
	    let xmlDoc = parser.parseFromString(data, "text/xml");
	    var ejecuciones = xmlDoc.getElementsByTagName("entry");
	    if ((ejecuciones != null) && (ejecuciones.length > 0)) {
	        arbolProyecto.ejecuciones = [];
	        for (var i = 0; i < ejecuciones.length; i++) {
	            var ejecucion = {};
	            ejecucion.usuario = ejecuciones[i].getAttribute("usuario");
	            ejecucion.tiempo = ejecuciones[i].getAttribute("ejecucion");
	            ejecucion.fecha = ejecuciones[i].getAttribute("fecha");
	            ejecucion.hora = ejecuciones[i].getAttribute("hora");

	            arbolProyecto.ejecuciones.push(ejecucion);
	        }
	    }
/*	})
	.catch(function (err) { */
	} catch(err) {
	    //console.error('Error', err.statusText);
		console.error('Error', err.message);
	}
}

// PUNTO DE ENTRADA AL SCRIPT
$(function () {
    // Añadimos un MutationObserver para que nos avise cuando detecte un cambio en el body
    // En caso de que el body cambie, comprobamos las ediciones visibles para añadir el botón
    // dinámicamente en caso de que sea necesario
    var target = document.querySelector('body')
    idUser = $("#iduser")[0].value;
    var observer = new MutationObserver(function (mutations) {
        comprobarEdicionesVisibles();
    });
    var config = { attributes: true, childList: true, characterData: true };
    observer.observe(target, config);
    generarSeleccionEmpleados();
});