paint-brush
Ahorrando tiempo y nervios con fórmulas y el complemento Structure Jirapor@ipolubentcev
944 lecturas
944 lecturas

Ahorrando tiempo y nervios con fórmulas y el complemento Structure Jira

por Ivan Polubentsev36m2023/10/29
Read on Terminal Reader

Demasiado Largo; Para Leer

Las fórmulas con el complemento Jira Structure pueden ser alucinantes: mejora tu juego mientras creas tablas, simplificas el trabajo con tareas y analizas lanzamientos y proyectos.
featured image - Ahorrando tiempo y nervios con fórmulas y el complemento Structure Jira
Ivan Polubentsev HackerNoon profile picture
0-item
1-item

El complemento Structure para Jira es muy útil para el trabajo diario con tareas y su análisis; lleva la visualización y estructuración de los tickets de Jira a un nuevo nivel y lo hace desde el primer momento.


Y no todo el mundo lo sabe, pero la funcionalidad de las fórmulas de estructura puede dejarte boquiabierto. Usando fórmulas, puedes crear tablas extremadamente útiles que pueden simplificar enormemente el trabajo con tareas y, lo más importante, son útiles para realizar un análisis más profundo de lanzamientos, epopeyas y proyectos.


¿Qué tal mostrar un gráfico de Burndown o mostrar el estado de un ticket en una tabla con tareas?


En este artículo, verá cómo crear sus propias fórmulas, desde los ejemplos más simples hasta casos complejos, pero bastante útiles.


Entonces, ¿para quién es este texto? Uno podría preguntarse por qué escribir un artículo cuando la documentación oficial en el sitio web de ALM Works está ahí esperando a que los lectores profundicen. Eso es cierto. Sin embargo, soy una de esas personas que ni siquiera tenía la más mínima idea de que Structure ocultaba una funcionalidad tan amplia: "Espera, ¡¿esta fue una opción todo el tiempo?!" Esa comprensión me hizo pensar que puede haber otras personas que tampoco sepan todavía el tipo de cosas que pueden hacer con fórmulas y estructura.


Este artículo también será útil para quienes ya estén familiarizados con las fórmulas. Aprenderá algunas opciones prácticas interesantes para usar campos personalizados y, tal vez, tomará prestados algunos de ellos para sus proyectos . Por cierto, si tiene algún ejemplo interesante propio, estaré encantado de que lo comparta en los comentarios. .


Cada ejemplo se analiza en detalle, desde la descripción del problema hasta la explicación del código, lo suficientemente a fondo como para que no queden dudas. Por supuesto, además de las explicaciones, cada ejemplo está ilustrado con un código que puedes probar tú mismo sin tener que profundizar en el análisis.


Si no tiene ganas de leer, pero le interesan las fórmulas, consulte los seminarios web de ALM Works . Estos explican los conceptos básicos en 40 minutos; la información se presenta allí de una manera muy comprimida.


No necesitas ningún conocimiento adicional para entender los ejemplos, por lo que cualquiera que haya trabajado con Jira y Structure podrá repetir los ejemplos en sus tablas sin ningún problema.


Los desarrolladores proporcionaron una sintaxis bastante flexible con su lenguaje Expr. Básicamente, la filosofía aquí es "escribe como quieras y funcionará".


¡Entonces empecemos!


¿Por qué necesitamos fórmulas?

Entonces, ¿por qué querríamos utilizar fórmulas? Bueno, a veces resulta que no tenemos suficientes campos estándar de Jira, como "Asignado", "Puntos de historia", etc. O necesitamos calcular una cantidad para ciertos campos, mostrar la capacidad restante por versión y averiguar cuántas veces la tarea ha cambiado de estado. Tal vez incluso queramos fusionar varios campos en uno para que nuestra Estructura sea más fácil de leer.


Para resolver estos problemas, necesitamos fórmulas y las usaremos para crear campos personalizados.


Lo primero que debemos hacer es entender cómo funciona una fórmula. Nos permite aplicar algún tipo de operación a una cadena. Debido a que cargamos muchas tareas en la estructura, la fórmula se aplica a cada línea de toda la tabla. Por lo general, todas sus operaciones están dirigidas a trabajar con tareas de estas líneas.


Entonces, si le pedimos a la fórmula que muestre algún campo de Jira, por ejemplo, "Asignado", entonces la fórmula se aplicará para cada tarea y tendremos otra columna "Asignado".


Las fórmulas constan de varias entidades básicas:

  • Variables: para acceder a los campos de Jira y guardar resultados intermedios
  • Funciones integradas: realizan una operación predefinida, por ejemplo, contar el número de horas entre fechas o filtrar datos en una matriz
  • Funciones personalizadas: si necesitamos cálculos únicos
  • Diferentes formas de mostrar el resultado, por ejemplo, “Fecha/Hora”, “Duración”, “Número” o “Marcado Wiki” para su opción.


Conociendo fórmulas

Nos familiarizaremos con las fórmulas y su sintaxis a través de algunos ejemplos y veremos seis casos prácticos.


Antes de ver cada ejemplo, indicaremos qué características de Estructura estamos usando; Las nuevas funciones que aún no se han explicado estarán en negrita. Cada uno de los siguientes ejemplos tendrá un nivel creciente de complejidad. Están organizados para presentarle gradualmente las características importantes de la fórmula.


Esta es la estructura básica que verá cada vez:

  • El problema
  • La solución propuesta
  • Las características de la estructura utilizadas.
  • Un ejemplo de código
  • Un análisis de la solución.


Estos ejemplos cubren temas que van desde el mapeo de variables hasta matrices complejas:

  • Dos ejemplos que muestran las fechas de inicio y finalización del trabajo en una tarea (opciones con visualización diferente)
  • Una tarea principal: muestra el tipo y el nombre de la tarea principal
  • La suma de Story Points de las subtareas y el estado de estas evaluaciones.
  • Una indicación de cambios recientes en el estado de la tarea.
  • Un cálculo del tiempo de trabajo, excluyendo los días libres (fines de semana) y estados adicionales.


Creando fórmulas

Primero, descubramos cómo crear campos personalizados con fórmulas. En la parte superior derecha de Estructura, al final de todas las columnas, hay un ícono “+”; haga clic en él. En el campo que aparece, escriba “Fórmula…” y seleccione el elemento apropiado.


Creando fórmulas


Guardar fórmulas

Analicemos cómo guardar una fórmula. Desafortunadamente, todavía no es posible guardar una fórmula específica por separado en algún lugar (sólo en tu cuaderno, como hago yo). En el seminario web de ALM Works, el equipo mencionó que están trabajando en un banco de fórmulas, pero por ahora la única forma de guardarlas es guardar la vista completa junto con la fórmula.


Cuando terminemos de trabajar en una fórmula, debemos hacer clic en la vista de nuestra estructura (lo más probable es que esté marcada con un asterisco azul) y hacer clic en "Guardar" para sobrescribir la vista actual. O puede hacer clic en "Guardar como..." para crear una nueva vista. (No olvides ponerlo a disposición de otros usuarios de Jira, ya que las nuevas vistas son privadas de forma predeterminada).


La fórmula se guardará en el resto de los campos en una vista particular y podrá verla en la pestaña "Avanzado" del menú "Ver detalles".


A partir de la versión 8.2, Structure ahora tiene la capacidad de guardar fórmulas con 3 clics rápidos.

El cuadro de diálogo para guardar está disponible desde la ventana de edición de fórmulas. Si esta ventana no está abierta, simplemente haga clic en el icono del triángulo ▼ en la columna deseada.


Guardar fórmulas


En la ventana de edición vemos el campo “Columna guardada”, a la derecha hay un ícono con una notificación azul, lo que significa que los cambios en la fórmula no se han guardado. Haga clic en este icono y seleccione la opción “Guardar como…”.


Columna guardada


Luego ingresa nombres para nuestra columna (fórmula) y elige en qué espacio guardarla. “Mis Columnas” si queremos guardarla en una lista personal. “Global”, de modo que la fórmula se guardará en la lista general, donde podrá ser editada por todos los usuarios de tu Estructura. Clic en Guardar".


Clic en Guardar


Ahora nuestra fórmula está guardada. Podemos cargarlo en cualquier estructura o volver a guardarlo desde cualquier lugar. Al volver a guardar la fórmula, se actualizará en todas las estructuras en las que se utilice.


El mapeo de variables también se guarda con la fórmula, pero hablaremos sobre el mapeo más adelante.


¡Ahora, pasemos a nuestros ejemplos!


Mostrar las fechas de inicio y finalización del trabajo en una tarea

Fechas personalizadas en las dos últimas columnas

Problema

Necesitamos una tabla con una lista de tareas, así como las fechas de inicio y finalización para trabajar en esas tareas. También necesitamos la tabla para exportarla a un Excel-Gantt separado. Desafortunadamente, Jira y Structure no saben cómo proporcionar dichas fechas de forma inmediata.

Solución propuesta

Las fechas de inicio y finalización son las fechas de transición a estados específicos, en nuestro caso son “En curso” y “Cerrado”. Necesitamos tomar estas fechas y mostrar cada una de ellas en un campo separado (esto es necesario para exportar más a Gantt). Entonces tendremos dos campos (dos fórmulas).


Las características de la estructura utilizadas.

  1. mapeo de variables
  2. La capacidad de ajustar el formato de visualización.


Un ejemplo de código

Campo para la fecha de inicio:

 firstTransitionToStart


Campo para la fecha de finalización:

 latestTransitionToDone


Un análisis de la solución.

En este caso, el código es una única variable, firstTransitionToStart, para el campo de fecha de inicio, y lastTransitionToDone para el segundo campo.


Centrémonos en el campo de fecha de inicio por ahora. Nuestro objetivo es obtener la fecha en que la tarea pasó al estado "En progreso" (esto corresponde al inicio lógico de la tarea), por lo que la variable se nombra, de manera bastante explícita para evitar la necesidad de adivinar más adelante, como "primera transición a comenzar".


Para convertir una fecha en una variable, recurrimos al mapeo de variables. Guardemos nuestra fórmula haciendo clic en el botón "Guardar".


Haga clic para guardar la fórmula.


Nuestra variable apareció en la sección "Variables", con un signo de exclamación al lado. La estructura indica que no puede vincular una variable a un campo en Jira y tendremos que hacerlo nosotros mismos (es decir, mapearlo).


Haga clic en la variable y vaya a la interfaz de mapeo. Seleccione el campo o la operación necesaria; busque la operación “Fecha de transición…”. Para ello, escriba “transición” en el campo de selección. Se le ofrecerán varias opciones a la vez y una de ellas nos conviene: "Primera transición a En curso". Pero para demostrar cómo funciona el mapeo, elijamos la opción "Fecha de transición ...".


Configuración de mapeo


Después de eso, debe elegir el estado en el que se produjo la transición y el orden de esta transición: la primera o la última.


Seleccione o ingrese en "Estado" - "Estado: En progreso" (o el estado correspondiente en su flujo de trabajo), y en "Transición" - "Primera transición al estado", ya que el comienzo del trabajo en una tarea es la primera transición al estatus correspondiente.


Seleccione la categoría deseada



Si en lugar de “Fecha de transición…” elegimos la opción inicialmente propuesta “Primera transición a En curso”, entonces el resultado sería casi el mismo: la Estructura elegiría los parámetros necesarios por nosotros. Lo único es que, en lugar de "Estado: En curso", tendríamos "Categoría: En curso".


Diferencia entre categoría de estado y estado


Permítanme señalar una característica importante: un estado y una categoría son dos cosas diferentes. Un estado es un estado específico, no es ambiguo, pero una categoría puede incluir varios estados. Sólo hay tres categorías: "Por hacer", "En curso" y "Listo". En Jira, suelen estar marcados con colores gris, azul y verde respectivamente. El estado debe pertenecer a una de estas categorías.

Recomiendo indicar un estado específico en casos como este para evitar confusiones con estados de la misma categoría. Por ejemplo, tenemos dos estados de la categoría "Por hacer" en el proyecto, "Abierto" y "Cola de control de calidad".


Volvamos a nuestro ejemplo.


Una vez que hayamos seleccionado las opciones necesarias, podemos hacer clic en “<Volver a la lista de variables” para completar las opciones de mapeo para la variable firstTransitionToStart. Si hacemos todo bien, veremos una marca de verificación verde.


General muestra el valor predeterminado (en milisegundos)


Al mismo tiempo, en nuestro campo personalizado, vemos algunos números extraños que no parecen una fecha en absoluto. En nuestro caso, el resultado de la fórmula será el valor de la variable firstTransitionToStart, y su valor es milisegundos desde enero de 1970. Para obtener la fecha correcta, debemos elegir un formato de visualización de fórmula específico.


La selección de formato se encuentra en la parte inferior de la ventana de edición. Allí está seleccionado "General" de forma predeterminada. Necesitamos “Fecha/Hora” para mostrar la fecha correctamente.


Seleccione Fecha/Hora, en lugar de General


Para el segundo campo, LatestTransitionToDone, haremos lo mismo. La única diferencia es que al mapear ya podemos seleccionar la categoría "Listo" y no el estado (ya que generalmente solo hay un estado de finalización de tarea inequívoco). Seleccionamos “Última transición” como parámetro de transición, ya que estamos interesados en la transición más reciente a la categoría “Listo”.


El resultado final para los dos campos se verá así.


Vista final con fechas.


Ahora veamos cómo lograr el mismo resultado, pero con nuestro propio formato de visualización.


Visualización de fecha con nuestro propio formato.

Ejemplo de formato personalizado


Problema

No estamos satisfechos con el formato de visualización de la fecha del ejemplo anterior, ya que necesitamos uno especial para la tabla de Gantt: “01.01.2022”.


Solución propuesta

Visualicemos las fechas usando las funciones integradas en Estructura, especificando el formato que más nos convenga.


Características estructurales utilizadas

  1. mapeo de variables
  2. funciones expr


Un ejemplo de código

 FORMAT_DATETIME(firstTransitionToStart;"dd.MM.yyyy")


Un análisis de la solución.

Los desarrolladores han proporcionado muchas funciones diferentes, incluida una separada para mostrar la fecha en nuestro propio formato: FORMAT_DATETIME; eso es lo que vamos a usar. La función utiliza dos argumentos: una fecha y una cadena del formato deseado.


Configuramos la variable firstTransitionToStart (primer argumento) usando las mismas reglas de mapeo que en el ejemplo anterior. El segundo argumento es una cadena que especifica el formato y lo definimos así: “dd.MM.aaaa”. Esto corresponde al formulario que queremos, “01.01.2022”.


Por lo tanto, nuestra fórmula dará inmediatamente un resultado en la forma deseada. Entonces, podemos mantener la opción “General” en la configuración del campo.


El segundo campo con la fecha de finalización del trabajo se realiza de la misma forma. Como resultado, la estructura debería verse como en la imagen de abajo.


Alimentación final después de la transformación.


En principio, no existen dificultades importantes al trabajar con la sintaxis de fórmulas. Si necesitas una variable, escribe su nombre; Si necesita una función, nuevamente, simplemente escriba su nombre y pase los argumentos (si son necesarios).


Cuando Structure encuentra un nombre desconocido, asume que es una variable e intenta mapearlo por sí mismo o nos pide ayuda.


Por cierto, una nota importante: la estructura no distingue entre mayúsculas y minúsculas, por lo que firstTransitionToStart, firsttransitiontostart y firSttrAnsItiontOStarT son la misma variable. La misma regla se aplica a las funciones. Para lograr un estilo de código inequívoco, en los ejemplos intentaremos cumplir con las reglas de las Convenciones de capitalización de MSDN.


Ahora profundicemos en la sintaxis y veamos un formato especial para mostrar el resultado.


Mostrar el nombre de la tarea principal

El nombre del padre se muestra antes del Resumen.


Problema

Trabajamos con tareas regulares (Tarea, Bug, etc.) y con tareas tipo Historia que tienen subtareas. En algún momento, necesitamos saber en qué tareas y subtareas trabajó el empleado durante un período determinado.


El problema es que muchas subtareas no proporcionan información sobre la historia en sí, como se les llama "trabajar en la historia", "preparar" o, por ejemplo, "activar el efecto". Y si solicitamos una lista de tareas para un período determinado, obtendremos una docena de tareas con el nombre "trabajar en la historia" sin ninguna otra información útil.


Nos gustaría tener una vista con una lista dividida en dos columnas: una tarea y una tarea principal, para que en el futuro sea posible agrupar dicha lista por empleados.


Solución propuesta

En nuestro proyecto, tenemos dos opciones cuando una tarea puede tener un padre:

  1. Una tarea es una subtarea y su padre es solo Historia
  2. Una tarea es una tarea normal (tarea, error, etc.) y puede tener o no Epic, en cuyo caso la tarea no tiene ningún padre.


Entonces, debemos:

  1. Averiguar si una tarea tiene un padre
  2. Descubra el tipo de este padre
  3. Calcule el tipo y nombre de esta tarea de acuerdo con el siguiente esquema: “[Tipo de padre] Nombre de padre”.


Para simplificar la percepción de la información, colorearemos el texto del tipo de tarea: es decir, “[Historia]” o “[Épica]”.


Qué usaremos:

  1. mapeo de variables
  2. Condición
  3. Acceso a los campos de tareas
  4. Formato de visualización: marcado wiki


Un ejemplo de código

 if( Parent.Issuetype = "Story"; """{color:green}[${Parent.Issuetype}]{color} ${Parent.Summary}"""; EpicLink; """{color:#713A82}[${EpicLink.Issuetype}]{color} ${EpicLink.EpicName}""" )


Un análisis de la solución.

¿Por qué la fórmula comienza con una condición if, si solo necesitamos generar una cadena e insertar allí el tipo y el nombre de la tarea? ¿No existe alguna forma universal de acceder a los campos de tareas? Sí, pero para tareas y epopeyas, estos campos tienen nombres diferentes y también debes acceder a ellos de manera diferente; esta es una característica de Jira.


Las diferencias comienzan en el nivel de la búsqueda de los padres. Para una subtarea, el padre vive en el campo Jira "Problema principal", y para una tarea normal, el epic será el padre, ubicado en el campo "Enlace épico". En consecuencia, tendremos que escribir dos opciones diferentes para acceder a estos campos.


Aquí es donde necesitamos una condición if. El lenguaje Expr tiene diferentes formas de lidiar con las condiciones. La elección entre ellos es cuestión de gustos.


Existe un método "similar a Excel":

 if (condition1; result1; condition2; result2 … )


O un método más "similar a un código":

 if condition1 : result1 else if condition2 : result2 else result3


En el ejemplo, utilicé la primera opción; Ahora veamos nuestro código de forma simplificada:

 if( Parent.Issuetype = "Story"; Some kind of result 1; EpicLink; Some kind of result 2 )


Vemos dos condiciones obvias:

  • Parent.Issuetype = "Historia"
  • Enlace épico


Averigüemos qué hacen y comencemos con el primero, Parent.Issuetype=”Story”.


En este caso, Parent es una variable que se asigna automáticamente al campo "Problema principal". Aquí es donde, como comentamos anteriormente, debe vivir el padre de la subtarea. Usando la notación de puntos (.), accedemos a la propiedad de este padre, en particular, a la propiedad Issuetype, que corresponde al campo "Tipo de problema" de Jira. Resulta que toda la línea Parent.Issuetype nos devuelve el tipo de tarea principal, si dicha tarea existe.


Además, no tuvimos que definir ni mapear nada, ya que los desarrolladores ya hicieron todo lo posible por nosotros. Aquí, por ejemplo, hay un enlace a todas las propiedades (incluidos los campos de Jira) que están predefinidas en el idioma, y aquí puede ver una lista de todas las variables estándar, a las que también se puede acceder de forma segura sin configuraciones adicionales.


Por lo tanto, la primera condición es ver si el tipo de tarea principal es Historia. Si no se cumple la primera condición, entonces el tipo de tarea principal no es Historia o no existe en absoluto. Y esto nos lleva a la segunda condición: EpicLink.


De hecho, es aquí cuando comprobamos si el campo “Epic Link” de Jira está rellenado (es decir, comprobamos su existencia). La variable EpicLink también es estándar y no es necesario asignarla. Resulta que nuestra condición se cumple si la tarea tiene Epic Link.


Y la tercera opción es cuando no se cumple ninguna de las condiciones, es decir, la tarea no tiene padre ni Epic Link. En este caso, no mostramos nada y dejamos el campo vacío. Esto se hace automáticamente ya que no obtendremos ninguno de los resultados.


Descubrimos las condiciones, ahora pasemos a los resultados. En ambos casos, es una cadena con texto y formato especial.


Resultado 1 (si el padre es Story):

 """{color:green}[${Parent.Issuetype}]{color} ${Parent.Summary}"""


Resultado 2 (si hay Epic Link):

 """{color:#713A82}[${EpicLink.Issuetype}]{color} ${EpicLink.EpicName}"""


Ambos resultados son similares en estructura: ambos constan de comillas triples “”” al principio y al final de la cadena de salida, especificación de color en los bloques de apertura {color: COLOR} y de cierre {color}, así como operaciones realizadas a través del Símbolo $. Las comillas triples le dicen a la estructura que dentro habrá variables, operaciones o bloques de formato (como colores).


Para el resultado de la primera condición, hacemos:

  1. Transferir el tipo de tarea principal ${Parent.Issuetype}
  2. Enciérrelo entre corchetes “[…]”
  3. Resalte todo en verde, envolviendo esta expresión [${Parent.Issuetype}] en el bloque de selección de color {color:green}…{color}, donde escribimos “verde”
  4. Y una última cosa, agrega el nombre de la tarea principal separada por un espacio ${Parent.Summary}.


Por lo tanto, obtenemos la cadena "[Historia] Nombre de alguna tarea". Como habrás adivinado, Resumen también es una variable estándar. Para aclarar el esquema para construir dichas cadenas, permítanme compartir una imagen de la documentación oficial.


Esquema de filas personalizado de la documentación oficial.


De manera similar, recopilamos la cadena para el segundo resultado, pero configuramos el color mediante el código hexadecimal. Descubrí que el color de Epic era “#713A82” (en los comentarios, por cierto, puedes sugerir un color más preciso para Epic). No te olvides de los campos (propiedades) que cambian para Epic. En lugar de "Resumen", utilice "EpicName", en lugar de "Parent", utilice "EpicLink".


Como resultado, el esquema de nuestra fórmula se puede representar como una tabla de condiciones.


Condición: la tarea principal existe y su tipo es Historia.

Resultado: Línea con el tipo verde de tarea principal y su nombre.

Condición: El campo Enlace épico está completo.

Resultado: Línea con el color épico del tipo y su nombre.


De forma predeterminada, la opción de visualización "General" está seleccionada en el campo y, si no la cambia, el resultado se verá como texto sin formato sin cambiar el color ni identificar los bloques. Si cambia el formato de visualización a "Marcado Wiki", el texto se transformará.


1) Mostrar General: de forma predeterminada, muestra texto sin formato tal como está. 2) Reemplazar General con Marcado Wiki.



Ahora, familiaricémonos con las variables que no están relacionadas con los campos de Jira: las variables locales.


Calcular la cantidad de puntos de historia con indicación de color

Las sumas de puntos de la historia están resaltadas con diferentes colores.


Problema

En el ejemplo anterior, aprendiste que estamos trabajando con tareas del tipo Historia, que tienen subtareas. Esto da lugar a un caso especial con las estimaciones. Para obtener una puntuación de Historia, resumimos las puntuaciones de sus subtareas, que se estiman en puntos abstractos de Historia.


El enfoque es inusual, pero funciona para nosotros. Entonces, cuando la Historia no tiene una estimación, pero las subtareas sí, no hay problema, pero cuando tanto la Historia como las subtareas tienen una estimación, la opción estándar de Estructura, “Σ Puntos de la Historia”, funciona incorrectamente.


Esto se debe a que la estimación de Story se suma a la suma de las subtareas. Como resultado, se muestra una cantidad incorrecta en Story. Nos gustaría evitar esto y agregar una indicación de inconsistencia con la estimación establecida en Story y la suma de subtareas.


Solución propuesta

Necesitamos varias condiciones, ya que todo depende de si la estimación está configurada en Story.


Entonces las condiciones son:


Cuando Story no tiene estimación , mostramos la suma de la estimación de subtareas en naranja para indicar que este valor aún no se ha establecido en Story


Si Story tiene una estimación , verifique si corresponde a la suma de la estimación de las subtareas:

  • Si no coincide, coloree una estimación en rojo y escriba la cantidad correcta al lado entre paréntesis.
  • Si una estimación y la suma coinciden, simplemente escriba una estimación en verde


La redacción de estas condiciones puede resultar confusa, así que expresémoslas en un esquema.


Algoritmo para elegir la opción de visualización de texto.


Características estructurales utilizadas

  1. mapeo de variables
  2. variables locales
  3. Métodos de agregación
  4. Condiciones
  5. Texto con formato


Un ejemplo de código

 with isEstimated = storypoints != undefined: with childrenSum = sum#children{storypoints}: with isStory = issueType = "Story": with isErr = isStory AND childrenSum != storypoints: with color = if isStory : if isEstimated : if isErr : "red" else "green" else "orange": if isEstimated : """{color:$color}$storypoints{color} ${if isErr :""" ($childrenSum)"""}""" else """{color:$color}$childrenSum{color}"""


Un análisis de la solución.

Antes de sumergirnos en el código, transformemos nuestro esquema en una forma más "similar a un código" para comprender qué variables necesitamos.


El mismo algoritmo reescrito con variables.


De este esquema vemos que necesitaremos:


Variables de condición:

  • isEstimated (disponibilidad de la estimación)
  • isError (correspondencia de la estimación de la historia y la suma)


Una variable del color del texto : color


Dos variables de estimación:

  • suma (la suma de la estimación de subtareas)
  • sp (puntos de la historia)


Además, la variable de color también depende de una serie de condiciones, por ejemplo, de la disponibilidad de una estimación y del tipo de tarea en la línea (consulte el esquema a continuación).


Algoritmo para elegir colores.


Entonces, para determinar el color, necesitaremos otra variable de condición, isStory, que indica si el tipo de tarea es Story.


La variable sp (storypoints) será estándar, lo que significa que se asignará automáticamente al campo Jira apropiado. El resto de variables las debemos definir nosotros mismos y serán locales para nosotros.


Ahora intentemos implementar los esquemas en código. Primero, definamos todas las variables.


 with isEstimated = storypoints != undefined: with childrenSum = sum#children{storypoints}: with isStory = issueType = "Story": with isErr = isStory AND childrenSum != storypoints:


Las líneas están unidas por el mismo esquema de sintaxis: la palabra clave with, el nombre de la variable y el símbolo de dos puntos “:” al final de la línea.


Sintaxis para la declaración de variables locales.


La palabra clave with se usa para indicar variables locales (y funciones personalizadas, pero hablaremos más sobre eso en un ejemplo separado). Le dice a la fórmula que a continuación va una variable que no necesita ser asignada. Los dos puntos “:” marcan el final de la definición de variable.


Así, creamos la variable isEstimated (recordatorio, ese caso no es importante). Almacenaremos 1 o 0 en él, dependiendo de si el campo de puntos de la historia está lleno. La variable storypoints se asigna automáticamente porque no hemos creado antes una variable local con el mismo nombre (por ejemplo, con storypoints =... :).


La variable indefinida denota la inexistencia de algo (como nulo, NaN y similares en otros idiomas). Por lo tanto, la expresión storypoints != indefinido puede leerse como una pregunta: “¿Está completo el campo storypoints?”.


A continuación, debemos determinar la suma de los puntos de la historia de todas las tareas infantiles. Para ello, creamos una variable local: ChildrenSum.


 with childrenSum = sum#children{storypoints}:


Esta suma se calcula mediante la función de agregación. (Puede leer sobre funciones como esta en la documentación oficial ). En pocas palabras, Structure puede realizar varias operaciones con tareas, teniendo en cuenta la jerarquía de la vista actual.


Usamos la función de suma y, además, usando el símbolo "#", pasamos los hijos de aclaración, lo que limita el cálculo de la suma solo a cualquier tarea secundaria de la línea actual. Entre llaves, indicamos qué campo queremos resumir; necesitamos una estimación en puntos de historia.


La siguiente variable local, isStory, almacena una condición: si el tipo de tarea en la línea actual es una Historia.


 with isStory = issueType = "Story":


Pasamos a la variable issuesType, familiar del ejemplo anterior, es decir, el tipo de tarea que se asigna por sí sola al campo deseado. Estamos haciendo esto porque es una variable estándar y no la hemos definido previamente.


Ahora definamos la variable isErr: señala una discrepancia entre la suma de la subtarea y la estimación de la Historia.


 with isErr = isStory AND childrenSum != storypoints:


Aquí estamos usando las variables locales isStory y ChildrenSum que creamos anteriormente. Para señalar un error, necesitamos que se cumplan dos condiciones simultáneamente: el tipo de problema es Historia (isStory) y (Y) la suma de puntos de los niños (childrenSum) no es igual (!=) a la estimación establecida en la tarea (storypoints ). Al igual que en JQL, podemos usar palabras de enlace al crear condiciones, como AND u OR.


Tenga en cuenta que para cada una de las variables locales hay un símbolo ":" al final de la línea. Debe estar al final, después de todas las operaciones que definen la variable. Por ejemplo, si necesitamos dividir la definición de una variable en varias líneas, entonces los dos puntos ":" se colocan solo después de la última operación. Como en el ejemplo con la variable color: el color del texto.


 with color = if isStory : if isEstimated : if isErr : "red" else "green" else "orange":


Aquí vemos muchos “:”, pero desempeñan funciones diferentes. Los dos puntos después de if isStory son el resultado de la condición isStory. Recordemos la construcción: si condición: resultado. Presentemos esta construcción en una forma más compleja, que define una variable.


 with variable = (if condition: (if condition2 : result2 else result3) ):


Resulta que si condición2: resultado2, de lo contrario resultado3 es, por así decirlo, el resultado de la primera condición, y al final hay dos puntos ":", que completan la definición de la variable.


A primera vista, la definición de color puede parecer complicada, aunque, de hecho, hemos descrito aquí el esquema de definición de color presentado al principio del ejemplo. Simplemente, como resultado de la primera condición, comienza otra condición: una condición anidada y otra en ella.


Pero el resultado final es ligeramente diferente al esquema presentado anteriormente.


 if isEstimated : """{color:$color}$storypoints{color} ${if isErr :""" ($childrenSum)"""}""" else """{color:$color}$childrenSum{color}"""


No tenemos que escribir “{color}$sp'' dos veces en el código, como estaba en el esquema; Seremos más inteligentes con las cosas. En la rama, si la tarea tiene una estimación, siempre mostraremos {color: $color}$storypoints{color} (es decir, solo una estimación en puntos de historia en el color necesario), y si hay un error, entonces después de un espacio, complementaremos la línea con la suma de la estimación de las subtareas: ($childrenSum).


Si no hay ningún error, no se agregará. También llamo su atención sobre el hecho de que no existe el símbolo “:”, ya que no definimos una variable, sino que mostramos el resultado final a través de una condición.


Podemos evaluar nuestro trabajo en la imagen de abajo en el campo “∑SP (mod)”. La captura de pantalla muestra específicamente dos campos adicionales:


  • “Puntos de historia”: una estimación en puntos de historia (campo estándar de Jira).
  • “∑ Story Points”: un campo personalizado estándar de estructura que calcula la cantidad incorrectamente.


Vista final del campo y comparación con los campos estándar Story Points y ∑ Story Points


Con la ayuda de estos ejemplos, hemos analizado las características principales del lenguaje estructural que le ayudarán a resolver la mayoría de los problemas. Veamos ahora dos características más útiles, nuestras funciones y matrices. Veremos cómo crear nuestra propia función personalizada.


Últimos cambios

Presta atención al emoji de la izquierda: representa un campo personalizado


Problema

A veces hay muchas tareas en un sprint y es posible que nos perdamos pequeños cambios en ellas. Por ejemplo, podemos perdernos una nueva subtarea o el hecho de que una de las historias haya pasado a la siguiente etapa. Sería bueno tener una herramienta que nos notifique sobre los últimos cambios importantes en las tareas.


Solución propuesta

Nos interesan tres tipos de cambios de estado de tareas que han ocurrido desde ayer: comenzamos a trabajar en la tarea, apareció una nueva tarea y la tarea se cerró. Además, será útil ver que la tarea se cierra con la resolución "No funciona".


Para ello crearemos un campo con una cadena de emojis que son responsables de los últimos cambios. Por ejemplo, si ayer se creó una tarea y comenzamos a trabajar en ella, entonces se marcará con dos emojis: “En progreso” y “Nueva tarea”.


¿Por qué necesitamos un campo personalizado de este tipo si se pueden mostrar varios campos adicionales, por ejemplo, la fecha de transición al estado "En curso" o un campo de "Resolución" separado? La respuesta es simple: las personas perciben los emojis más fácil y rápidamente que el texto, que se encuentra en diferentes campos y necesita ser analizado. La fórmula recopilará todo en un solo lugar y lo analizará por nosotros, lo que nos ahorrará esfuerzo y tiempo para cosas más útiles.


Determinemos de qué se encargarán los diferentes emoji:

  • *️⃣ es la forma más común de marcar una nueva tarea
  • ✅ marca una tarea completada
  • ❌ indica una tarea que decidiste cancelar (“No funcionará”)
  • 🚀 significa que decidimos empezar a trabajar en la tarea (este emoji es adecuado para nuestro equipo, puede ser diferente para ti)


Características estructurales utilizadas

  1. mapeo de variables
  2. métodos de lenguaje expr
  3. variables locales
  4. Condiciones
  5. Nuestra propia función


Un ejemplo de código

 if defined(issueType): with now = now(): with daysScope = 1.3: with workDaysBetween(today, from)= ( with weekends = (Weeknum(today) - Weeknum(from)) * 2: HOURS_BETWEEN(from;today)/24 - weekends ): with daysAfterCreated = workDaysBetween(now,created): with daysAfterStart = workDaysBetween(now,latestTransitionToProgress): with daysAfterDone = workDaysBetween(now, resolutionDate): with isWontDo = resolution = "Won't Do": with isRecentCreated = daysAfterCreated >= 0 and daysAfterCreated <= daysScope and not(resolution): with isRecentWork = daysAfterStart >= 0 and daysAfterStart <= daysScope : with isRecentDone = daysAfterDone >= 0 and daysAfterDone <= daysScope : concat( if isRecentCreated : "*️⃣", if isRecentWork : "🚀", if isRecentDone : "✅", if isWontDo : "❌")

Un análisis de la solución.


Para empezar, pensemos en las variables globales que necesitamos para determinar los eventos que nos interesan. Necesitamos saber si desde ayer:

  • La tarea ha sido creada.
  • El estado ha cambiado a "En curso"
  • Se ha encontrado una solución (y cuál)


El uso de variables ya existentes junto con nuevas variables de mapeo nos ayudará a verificar todas estas condiciones.

  • creado: la fecha de creación de la tarea
  • lastTransitionToProgress: la última fecha de transición al estado "En progreso" (la asignamos como en el ejemplo anterior)
  • resoluciónDate: la fecha de finalización de la tarea
  • resolución — texto de resolución


Pasemos al código. La primera línea comienza con una condición que verifica si el tipo de tarea existe.


 if defined(issueType):


Esto se hace a través de la función definida incorporada, que verifica la existencia del campo especificado. La verificación se realiza para optimizar el cálculo de la fórmula.


No cargaremos Estructura con cálculos inútiles, si la línea no es una tarea. Resulta que todo el código después de if es el resultado, es decir, la segunda parte de la construcción if (condición: resultado). Y si no se cumple la condición, el código tampoco funcionará.


La siguiente línea con now = now(): también es necesaria para optimizar los cálculos. Más adelante en el código, tendremos que comparar diferentes fechas con la fecha actual varias veces. Para no hacer el mismo cálculo varias veces, calcularemos esta fecha una vez y ahora la convertiremos en una variable local.


También sería bueno mantener nuestro “ayer” por separado. El conveniente "ayer" empíricamente se convirtió en 1,3 días. Convirtamos esto en una variable: con daysScope = 1.3:.


Ahora necesitamos calcular varias veces el número de días entre dos fechas. Por ejemplo, entre la fecha actual y la fecha de inicio del trabajo. Por supuesto, hay una función DAYS_BETWEEN incorporada, que parece adecuada para nosotros. Pero si la tarea, por ejemplo, se creó el viernes, el lunes no veremos ningún aviso de nueva tarea, ya que en realidad han pasado más de 1,3 días. Además, la función DAYS_BETWEEN solo cuenta el número total de días (es decir, 0,5 días se convertirán en 0 días), lo que tampoco nos conviene.


Hemos formulado un requisito: debemos calcular el número exacto de días hábiles entre estas fechas; y una función personalizada nos ayudará con esto.


Sintaxis para declaración de funciones locales.


Su sintaxis de definición es muy similar a la sintaxis para definir una variable local. La única diferencia y la única adición es la enumeración opcional de argumentos entre los primeros corchetes. Los segundos corchetes contienen las operaciones que se realizarán cuando se llame a nuestra función. Esta definición de la función no es la única posible, pero usaremos ésta (se pueden encontrar otras en la documentación oficial ).


 with workDaysBetween(today, from)= ( with weekends = (Weeknum(today) - Weeknum(from)) * 2: HOURS_BETWEEN(from;today)/24 - weekends ):


Nuestra función personalizada workDaysBetween calculará los días laborables entre hoy y desde las fechas, que se pasan como argumentos. La lógica de la función es muy sencilla: contamos el número de días libres y los restamos del número total de días entre las fechas.


Para calcular el número de días libres, necesitamos saber cuántas semanas han pasado entre hoy y desde. Para ello, calculamos la diferencia entre los números de cada una de las semanas. Este número lo obtendremos de la función Weeknum, que nos proporciona el número de semana del inicio del año. Multiplicando esta diferencia por dos obtenemos el número de días libres transcurridos.


A continuación, la función HOURS_BETWEEN cuenta el número de horas entre nuestras fechas. Dividimos el resultado por 24 para obtener el número de días y restamos los días libres de este número, para obtener los días laborables entre las fechas.


Usando nuestra nueva función, definamos un montón de variables auxiliares. Tenga en cuenta que algunas de las fechas en las definiciones son variables globales, de las que hablamos al principio del ejemplo.


 with daysAfterCreated = workDaysBetween(now,created): with daysAfterStart = workDaysBetween(now,latestTransitionToProgress): with daysAfterDone = workDaysBetween(now, resolutionDate):


Para que el código sea fácil de leer, definamos variables que almacenen los resultados de las condiciones.


 with isWontDo = resolution = "Won't Do": with isRecentCreated = daysAfterCreated >= 0 and daysAfterCreated <= daysScope and not(resolution): with isRecentWork = daysAfterStart >= 0 and daysAfterStart <= daysScope : with isRecentDone = daysAfterDone >= 0 and daysAfterDone <= daysScope :


Para la variable isRecentCreated, agregué una condición opcional y not(resolución), lo que me ayuda a simplificar la línea futura, porque si la tarea ya está cerrada, entonces no me interesa información sobre su creación reciente.


El resultado final se construye mediante la función concat, concatenando las líneas.


 concat( if isRecentCreated : "*️⃣", if isRecentWork : "🚀", if isRecentDone : "✅", if isWontDo : "❌")


Resulta que el emoji estará en la línea solo cuando la variable en la condición sea igual a 1. Por lo tanto, nuestra línea puede mostrar simultáneamente cambios independientes en la tarea.


Vista final de la columna con cambios (lado izquierdo)


Hemos tocado el tema de contar los días laborables sin días libres. Hay otro problema relacionado con esto, que analizaremos en nuestro último ejemplo y al mismo tiempo nos familiarizaremos con las matrices.


Cálculo de la jornada laboral, excluidos los días libres.

Ejemplo de la exhibición final.


Problema

A veces queremos saber cuánto tiempo lleva ejecutándose una tarea, excluyendo los días libres. Esto es necesario, por ejemplo, para analizar la versión publicada. Para entender por qué necesitamos días libres. Excepto que uno funcionaba de lunes a jueves y el otro, de viernes a lunes. En tal situación, no podemos afirmar que las tareas sean equivalentes, aunque la diferencia de días naturales nos dice lo contrario.


Desafortunadamente, la estructura "lista para usar" no sabe cómo ignorar los días libres, y el campo con la opción "Tiempo en estado..." produce un resultado independientemente de la configuración de Jira, incluso si el sábado y el domingo se especifican como días libres.


Como resultado, nuestro objetivo es calcular el número exacto de días laborables, ignorando los días libres y tener en cuenta el impacto de las transiciones de estado en este tiempo.


¿Y qué tienen que ver los estatus con esto? Déjame responder. Supongamos que calculamos que entre el 10 y el 20 de marzo, la tarea estuvo funcionando durante tres días. Pero de estos 3 días estuvo en pausa un día y en revisión día y medio. Resulta que la tarea estuvo en el trabajo solo medio día.


La solución del ejemplo anterior no nos conviene debido al problema de cambiar entre estados, porque la función personalizada workDaysBetween solo tiene en cuenta el tiempo entre dos fechas seleccionadas.


Solución propuesta

Este problema se puede solucionar de diferentes formas. El método del ejemplo es el más caro en términos de rendimiento, pero el más preciso en cuanto a contar los días libres y los estados. Tenga en cuenta que su implementación solo funciona en la versión de Structure anterior a 7.4 (diciembre de 2021).


Entonces, la idea detrás de la fórmula es la siguiente:


  1. Necesitamos saber cuántos días han pasado desde el inicio hasta la finalización de la tarea.
  2. A partir de esto hacemos una matriz, es decir, una lista de días entre el inicio y el final de nuestro trabajo en la tarea.
  3. Mantenga solo los días libres en la lista


Filtrar solo los fines de semana de todas las fechas (pueden tener estados diferentes)


  1. Fuera de estos días libres, mantenemos solo aquellos en los que la tarea estaba en estado "En progreso" (la característica de la versión 7.4 "Valor histórico" nos ayudará aquí)


Eliminar estados innecesarios con el estado "en progreso" restante


  1. Ahora en la lista solo tenemos aquellos días libres que coincidieron con el período "En curso".
  2. Por separado, averiguamos la duración total del estado "En progreso" (a través de la opción integrada de Estructura "Tiempo en estado...");
  3. Resta de este tiempo el número de días libres obtenidos previamente.


Así, obtendremos el tiempo exacto de trabajo en la tarea, ignorando los días libres y las transiciones entre estados adicionales.


Características estructurales utilizadas

  1. mapeo de variables
  2. métodos de lenguaje expr
  3. variables locales
  4. Condiciones
  5. Un método interno (nuestra propia función)
  6. matrices
  7. Acceso al historial de la tarea.
  8. Texto con formato


Un ejemplo de código

 if defined(issueType) : if status != "Open" : with finishDate = if toQA != Undefined : toQA else if toDone != Undefined : toDone else now(): with startDate = DEFAULT(toProgress, toDone): with statusWeekendsCount(dates, status) = ( dates.filter(x -> weekday(x) > 5 and historical_value(this,"status",x)=status).size() ): with overallDays = round(hours_between(startDate,finishDate)/24): with sequenceArray = SEQUENCE(0,overallDays): with datesArray = sequenceArray.map(DATE_ADD(startDate,$,"day")): with progressWeekends = statusWeekendsCount(datesArray, "in Progress"): with progressDays = (timeInProgress/86400000 - progressWeekends).round(1): with color = if( progressDays = 0 ; "gray" ; progressDays > 0 and progressDays <= 2.5; "green" ; progressDays > 2.5 and progressDays <= 4; "orange" ; progressDays > 4; "red" ): """{color:$color}$progressDays d{color}"""


Un análisis de la solución.


Antes de transferir nuestro algoritmo al código, facilitemos los cálculos de Estructura.


 if defined(issueType) : if status != "Open" :


Si la línea no es una tarea o su estado es "Abierto", omitiremos esas líneas. Sólo nos interesan las tareas que se han lanzado a funcionar.


Para calcular el número de días entre fechas, primero debemos determinar estas fechas: FinishDate y StartDate.


 with finishDate = if toQA != Undefined : toQA else if toDone != Undefined : toDone else now(): with startDate = DEFAULT(toProgress, toDone): 


Identificar los estados que significan el final lógico del trabajo.


Supondremos que la fecha de finalización de la tarea (finishDate) es:

  • O la fecha en la que la tarea se transfirió al estado "QA"
  • O la fecha de transición a “Cerrado”
  • O si la tarea todavía está en "En progreso", entonces la fecha de hoy (para saber cuánto tiempo ha pasado)


Fecha de inicio del trabajo startDate está determinada por la fecha de transición al estado "En curso". Hay casos en los que la tarea se cierra sin pasar a la etapa de trabajo. En tales casos, consideramos la fecha de cierre como fecha de inicio, por lo que el resultado es 0 días.


Como habrás adivinado, toQA, toDone y toProgress son variables que deben asignarse a los estados apropiados como en el primer ejemplo y en el anterior.


También vemos la nueva función DEFAULT(toProgress, toDone). Comprueba si toProgress tiene un valor y, si no, utiliza el valor de la variable toDone.


Luego viene la definición de la función personalizada statusWeekendsCount, pero volveremos a ella más adelante, ya que está estrechamente relacionada con las listas de fechas. Es mejor ir directamente a la definición de esta lista, para que luego podamos entender cómo aplicarle nuestra función.


Queremos obtener una lista de fechas de la siguiente forma: [fecha de inicio (digamos 11.03), 12.03, 13.03, 14.03… fecha de finalización]. No existe una función simple que haga todo el trabajo por nosotros en Structure. Entonces recurramos a un truco:


  1. Crearemos una lista simple a partir de una secuencia de números desde 0 hasta el número de días de trabajo, es decir, [0, 1, 2, 3… n días de trabajo]
  2. Agregue la fecha de inicio de la tarea a cada número (es decir, día). Como resultado, obtenemos una lista (matriz) del tipo requerido: [inicio + 0 días, inicio + 1 día, inicio + 2 días… inicio + n días de trabajo].


Crear una matriz inicial de fechas desde la fecha de inicio hasta el final lógico


Ahora, veamos cómo podemos implementarlo en el código. Trabajaremos con matrices.


 with overallDays = round(hours_between(startDate,finishDate)/24): with sequenceArray = SEQUENCE(0,overallDays): with datesArray = sequenceArray.map(DATE_ADD(startDate,$,"day")):


Contamos cuántos días llevará trabajar en una tarea. Como en el ejemplo anterior, mediante la división por 24 y la función horas_entre(fechainicial,fechafinalizada). El resultado se escribe en la variable globalDays.


Creamos una matriz de la secuencia de números en forma de variable secuenciaArray. Esta matriz se construye mediante la función SEQUENCE(0,overallDays), que simplemente crea una matriz del tamaño deseado con una secuencia de 0 a globalDays.


Luego viene la magia. Una de las funciones de la matriz es map. Aplica la operación especificada a cada elemento de la matriz.


Nuestra tarea es sumar la fecha de inicio a cada número (es decir, el número del día). La función DATE_ADD puede hacer esto, agrega una cierta cantidad de días, meses o años a la fecha especificada.


Sabiendo esto, desciframos la cadena:


 with datesArray = sequenceArray.map(DATE_ADD(startDate, $,"day"))


A cada elemento de la secuenciaArray, se aplica la función .map() DATE_ADD(startDate, $, “día”).


Veamos qué se pasa en los argumentos de DATE_ADD. Lo primero es startDate, la fecha a la que se sumará el número deseado. Este número está especificado por el segundo argumento, pero vemos $.


El símbolo $ indica un elemento de matriz. La estructura entiende que la función DATE_ADD se aplica a un array, y por tanto en lugar de $ estará el elemento del array deseado (es decir, 0, 1, 2…).


El último argumento “día” es una indicación de que agregamos un día, ya que la función puede agregar un día, mes y año, dependiendo de lo que especifiquemos.


Por lo tanto, la variable dateArray almacenará una serie de fechas desde el inicio del trabajo hasta su finalización.


Volvamos a la función personalizada que nos perdimos. Filtrará los días adicionales y calculará el resto. Describimos este algoritmo al principio del ejemplo, antes de analizar el código, concretamente en los párrafos 3 y 4 sobre el filtrado de días libres y estados.


 with statusWeekendsCount(dates, status) = ( dates.filter(x -> weekday(x) > 5 and historical_value(this,"status",x)=status).size() ):


Pasaremos dos argumentos a la función personalizada: una matriz de fechas, llamémosla fechas, y el estado requerido: estado. Aplicamos la función .filter() a la matriz de fechas transferidas, que mantiene solo aquellos registros en la matriz que han pasado por la condición de filtro. En nuestro caso, son dos y se combinan mediante y. Después del filtro, vemos .size(), devuelve el tamaño de la matriz después de realizar todas las operaciones en ella.


Si simplificamos la expresión, obtenemos algo como esto: array.filter(condición1 y condición2).size(). Entonces, como resultado, obtuvimos el número de días libres que nos convenían, es decir, aquellos días libres que cumplían las condiciones.


Echemos un vistazo más de cerca a ambas condiciones:


 x -> weekday(x) > 5 and historical_value(this,"status",x)=status


La expresión x -> es solo parte de la sintaxis del filtro, lo que indica que llamaremos al elemento de la matriz x. Por lo tanto, x aparece en cada condición (similar a como ocurría con $). Resulta que x es cada fecha de la matriz de fechas transferidas.


La primera condición, día de la semana(x) > 5, requiere que el día de la semana de la fecha x (es decir, cada elemento) sea mayor que 5: es sábado (6) o domingo (7).


La segunda condición usa valor_histórico.


 historical_value(this,"status",x) = status


Esa es una característica de Structure de la versión 7.4.


La función accede al historial de la tarea y busca una fecha específica en el campo especificado. En este caso, buscamos la fecha x en el campo "estado". Esta variable es solo parte de la sintaxis de la función, se asigna automáticamente y representa la tarea actual en la línea.


Por lo tanto, en la condición, comparamos el argumento de estado transferido y el campo "estado", que devuelve la función valor_histórico para cada fecha x en la matriz. Si coinciden, la entrada permanece en la lista.


El toque final es el uso de nuestra función para contar el número de días en el estado deseado:


 with progressWeekends = statusWeekendsCount(datesArray, "in Progress"): with progressDays = (timeInProgress/86400000 - progressWeekends).round(1):


Primero, averigüemos cuántos días libres con el estado "en progreso" hay en nuestro conjunto de fechas. Es decir, pasamos nuestra lista de fechas y el estado deseado a la función personalizada statusWeekendsCount. La función elimina todos los días de la semana y todos los días libres en los que el estado de la tarea difiere del estado "en progreso" y devuelve el número de días restantes en la lista.


Luego restamos esta cantidad a la variable timeInProgress, que mapeamos mediante la opción “Tiempo en estado…”.


El número 86400000 es el divisor que convertirá milisegundos en días. La función .round(1) es necesaria para redondear el resultado a décimas, por ejemplo a “4.1”, de lo contrario puede obtener este tipo de entrada: “4.0999999…”.


Para indicar la duración de la tarea, introducimos la variable color. Lo cambiaremos dependiendo de la cantidad de días dedicados a la tarea.


  • Gris — 0 días
  • Verde: más de 0 pero menos de 2,5 días
  • Rojo: de 2,5 a 4 días
  • Rojo: más de 4 días


 with color = if( progressDays = 0 ; "gray" ; progressDays > 0 and progressDays <= 2.5; "green" ; progressDays > 2.5 and progressDays <= 4; "orange" ; progressDays > 4; "red" ):


Y la línea final con el resultado de los días calculados:


 """{color:$color}$progressDays d{color}"""


Nuestro resultado se verá como en la imagen de abajo.


Vista final del campo Tiempo en el trabajo


Por cierto, en la misma fórmula, puede mostrar la hora de cualquier estado. Si, por ejemplo, pasamos el estado "Pausa" a nuestra función personalizada y asignamos la variable timeInProgress a través de "Tiempo en... - Pausa", entonces calcularemos el tiempo exacto en la pausa.


Puede combinar estados y realizar una entrada como “wip: 3.2d | rev: 12d”, es decir calcular el tiempo en trabajo y el tiempo en revisión. Sólo estás limitado por tu imaginación y tu flujo de trabajo.


Conclusión

Presentamos una serie exhaustiva de características de este lenguaje de fórmulas que lo ayudarán a hacer algo similar o escribir algo completamente nuevo e interesante para analizar las tareas de Jira.


Espero que el artículo te haya ayudado a descubrir las fórmulas, o al menos te haya interesado en este tema. No pretendo tener “el mejor código y algoritmo”, así que si tienes ideas sobre cómo mejorar los ejemplos, ¡me encantaría que las compartas!


Por supuesto, debe comprender que nadie le informará mejor sobre las fórmulas que los desarrolladores de ALM Works. Por lo tanto, adjunto enlaces a su documentación y seminarios web. Y si comienza a trabajar con campos personalizados, revíselos con frecuencia para ver qué otras funciones puede utilizar.