NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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(); });