(Y una breve reflexión acerca de las “malas” prácticas)
El caso de trabajo: Un Backlog de DevOps (o una red comercial)
Supongamos una empresa que trabaja con metodología Agile en la que existen Epics (o proyectos), de las que dependen Features, User Stories y Task (existe una jerarquía Feature<Epic<US<Task, a los que llamaremos “Items”).
Los jefes de proyecto podrán ver a nivel global los Epics de los que sean responsables, asignarán las User Stories, Tasks a otros usuarios, que a su vez pueden tener un responsable técnico diferente, etc… Puede haber una asignación de permisos bastante ramificada y que opera a diferentes niveles, e incluso al mismo nivel.
Un caso de uso similar podría ser el de un organigrama o en una red comercial, en la que, por ejemplo, exista una Dirección comercial, Delegados de Zona, Comerciales y Clientes asignados a cada uno, o incluso a varios de ellos.
Con este planteamiento se trata de que un usuario pueda ver sus Items, sean del nivel que sean, organizados de forma jerárquica para facilitar su seguimiento y contextualizar y entender mejor las “historias”.
Los roles de seguridad en Power BI se configuran con fórmulas DAX y, en teoría, podríamos configurar un sistema de permisos por esta vía de un modo operativo pero algo complejo, usando tablas desconectadas. Pero tampoco es aconsejable crear reglas complicadas pues pueden tener un impacto negativo en la ejecución de las consultas. En este post propongo otra alternativa.
Los datos de origen
Partiremos de un excel con la siguiente información: Usuarios y Tasks (tareas).
Como se puede ver en la tabla “Tasks” los items conforman una jerarquía: Una task depende de una US, una US de una Feature y una Feature de una Epic. El campo “parenttask” indica el item padre, que es una de los “taskid”.
En las dos últimas columnas tenemos los usuarios asignados a cada item.
Por otro lado disponemos de una lista de usuarios. Podemos ver que el usuario 3, Ingrid, tiene asignadas US y Features, algunas de las mismas tiene como supervisor otros usuarios.
Modelo y relaciones
Si tratamos de consumir directamente estas tablas podremos relacionar la dimensión “usuario” con una de las columnas “user_*”, pero no con ambas al mismo tiempo.
Va a ser un objetivo complicado de desarrollar con Power BI de forma básica. Puede ser tentador tratar de resolverlo con medidas DAX haciendo uso de USERELATIONSHIP, CROSSJOIN…, pero hay que tener en cuenta que alguna de estas funciones cuando existen roles de seguridad quedan invalidadas y no funcionarán al publicar en el servicio de Power BI. Otros workaround creo que complican el modelo de forma exagerada y eso es una línea roja para mi, más que otros aspectos.
Por ello vamos a darle una vuelta y replantear el modelo para eliminar limitaciones, expandiendo sus posibilidades. Y de un modo muy sencillo.
Sigamos el caso del usuario con ID=3 (Ingrid).
Primer paso: Sacar en una columna todos los items y los usuarios asignados a cada uno
Cargamos las tablas en el modelo.
“Duplicamos” la tabla “Tasks” haciendo una referencia a la tabla “Tasks” original. La llamaremos “Tasks_Users”
Nos quedaremos con solo con las columnas indicadas y para convertir a filas los usuarios aplicaremos el paso “Unpivot other columns”:
Y este es el resultado:
Si quitamos las columnas innecesarias observamos que los items tienen los usuarios asignados, uno o varios, en una estructura mucho más manejable.
Esta tabla es la que nos va a servir de puente entre tareas y permisos y, tras aplicar cambios, vamos a relacionarla del siguiente modo:
De este modo podremos ver los Items a los que tiene permiso, sea como responsable o técnico:
NOTA: Destacar que la relación entre tasks y task_user es bidireccional (algo que se considera una “mala práctica”…)
Si queremos ver los items como una jerarquía que permita ver la traza completa desde Epic a Task habrá que crear una jerarquía.
Para ello añadiremos columnas calculadas (algo que se considera también una “mala práctica”) que proporcionen los niveles. Usaremos las funciones PATH y PATHITEM.
PATH da la ruta completa de items, usando las columnas taskid y taskparentid:
path = PATH(TASKS[taskid], TASKS[parenttask])
Mientras que para cada nivel usaremos PATHITEM para identificar el elemento que corresponde a cada nivel,apoyándose en la anterior columna “path”:
1.Epic =
var idd=PATHITEM(TASKS[path], 1, INTEGER)
var lk=LOOKUPVALUE(TASKS[description],TASKS[taskid], idd)
return lk
2.Feature =
var idd=PATHITEM(TASKS[path], 2, INTEGER)
var lk=LOOKUPVALUE(TASKS[description],TASKS[taskid], idd)
return lk
3.US =
var idd=PATHITEM(TASKS[path], 3, INTEGER)
var lk=LOOKUPVALUE(TASKS[description],TASKS[taskid], idd)
return lk
4.Task =
var idd=PATHITEM(TASKS[path], 4, INTEGER)
var lk=LOOKUPVALUE(TASKS[description],TASKS[taskid], idd)
return lk
El LOOKUPVALUE es para sacar el literal del Item,pues de otro modo mostrará su ID.
Así es como quedaría la tabla Tasks:
Con las columna que conforman la jerarquía, la anterior tabla plana podremos verla por niveles y entender mejor las asignaciones existentes:
Y es funcional con cualquier usuario que se elija, por ejemplo “Sigrid”:
Creación de Roles de usuario
Si lo que se pretende es publicar el informe para que un usuario vea solo sus items, sin necesidad de que tenga que “seleccionarse” como usuario en el filtro, crearemos los roles de usuario.
Como ejemplo vamos a crear uno específico para “Sigrid”, pero si disponemos de emails de usuarios y estos tienen cuenta de Power BI Pro podríamos usar la función USERNAME para que el RLS se aplique a partir de un rol genérico, sin necesidad de crear un rol concreto para cada usuario. Esto facilitaría mucho las gestión de la seguridad en el servicio de Power BI, sin necesidad de modificar-crear-borrar los roles existentes.
Y si vemos como “Sigrid” observamos que del filtro de usuarios quedan excluídos todos menos ella:
¿Malas prácticas?
Para resolver este proyecto se ha recurrido a los que se consideran 2 “malas prácticas”: El uso de relaciones bidireccionales y creación de columnas calculadas. Alguno te garantizará un puesto en el infierno si recurres a ellas…
Pero ¿son realmente malas? Pues depende. Si sabes lo que haces y sus consecuencias, pero proporcionan el resultado buscado sin comprometer el modelo ni su rendimiento, ni a corto ni largo plazo, facilitan el desarrollo y son escalables, fáciles de auditar-mantener, y tú y el cliente estáis satisfechos, son más que lícitas y con argumentos más que defendibles.
Si te pones una “soga en el cuello” por evitar una mala práctica, es muy probable que el desarrollo se vuelva en tu contra.
Eso sí, igual de malo es recurrir a una mala práctica incondicionalmente, sin haber evaluado previamente otras posibilidades: Si hay soluciones alternativas que te permiten evitarlas dentro de unos tiempos razonables de desarrollo, escalables y sencillas de mantener, pues investiga y sopesa la conveniencia de usarlas, pero son consejos, no máximas.
Espero que te sea de ayuda.
Puedes descargar los archivos del proyecto de aquí:
Permisos multiples camposApasionado de las tecnologías de análisis de datos y Business Intelligence, y con larga experiencia en el uso de las mismas en empresas de todos los sectores, tamaños, y proyectos de diversa complejidad, en los cuales disfruto creando, planteando y desarrollando soluciones.
Tengo una curiosidad insaciable y demasiadas aficiones, entre ellas tocar el bajo con mi grupo.