miércoles, 26 de marzo de 2014

Designer 2013: Cómo modificar los permisos de un ítem en un workflow.

Puede que, como yo, hayáis llegado a pasar un buen rato intentando encontrar las acciones en el diseñador de flujos de SharePoint Designer 2013 para modificar la seguridad de un ítem de una lista o de un documento en una biblioteca.

Para los que no todavía no hayáis caído en la trampa, os ahorraré tiempo: NO EXISTEN.

Sorprendente pero cierto. Y diréis: ¿Pero esto no existía en 2010? ¡¡Y tendréis razón!! En 2010 se podía, en un “Impersonation Step” incluir las acciones necesarias para modificar los permisos de un ítem, pero en 2013, el nuevo motor de flujos en Workflow Foundation 4.0, no lo contempla. Entonces, ¿Cómo podemos establecer la seguridad de un ítem en un flujo de SharePoint 2013 realizado con SharePoint Designer 2013? La respuesta es tan simple como “curiosa”: Invocando desde el flujo de SharePoint 2013 un flujo de SharePoint 2010 que se encargue de realizar dichas tareas.

Sin palabras, ¿Eh?

Es cierto que con 2013 podemos crear indistintamente flujos en 2013 (si tenemos instalado correctamente Workfow Manager) como en 2010, pero uno se pregunta qué pasará cuando llegue SharePoint 2015 o versiones posteriores. ¿Seguirá siendo posible invocar flujos en formato 2010? Y en caso negativo, ¿Qué ocurrirá con este tipo de soluciones “forzadas” para gestionar los permisos de un ítem? Sólo puedo pedirles fehacientemente a los chicos de Microsoft que en 2015 el motor de flujo sí incluya este tipo de acciones, a fin de poder facilitarnos posibles migraciones.

Tapándonos los ojos y centrándonos en el presente, para quien no supiera cómo configurar la seguridad de un ítem en flujos de 2010, ahí va un “step by step”:

1.   Creamos el flujo de 2010 para modificar la seguridad de un ítem en SharePoint Designer 2013

1.1 - Insertamos un “Impersonation Step” (hay que situar el cursor antes o después de un step normal, si no está situado fuera del “Step”, no se iluminará la opción para crear el Impersonation)

image

1.2 – Nos situamos dentro del nuevo “Impersonation Step”. Ahora veremos que en el menú de “Action” aparecen las opciones deseadas para la gestión de la seguridad del ítem.

image[39]

1.3 – Configuramos la opción de seguridad escogida en función de nuestros requerimientos concretosimage

1.4 – El flujo nos quedará algo similar a la siguiente imagen:

image

2.   Salimos del flujo anterior y creamos ahora nuestro flujo en 2013 con SharePoint Designer 2013.

3.   En el punto donde sea necesario modificar la seguridad del ítem/documento, utilizar la acción “Start a List Workflow”, indicando el flujo de 2010 que hemos diseñado previamente. Es posible que el flujo requiera algún parámetro adicional, como el ID del ítem o la indicación de quien será el usuario que tenga un determinado permiso.

image

4.   Nuestro flujo será tan dispar como nuestros requerimientos lo requieran, pero deberá quedar con una acción de invocación al flujo en 2010 tal y como muestra la siguiente imagen:

image

¡¡Listo!! Como veis, poder se puede hacer… Otra cosa son las inquietudes que os puedan quedar al invocar flujos en 2010 desde flujos 2013. De nuevo, SharePoint te da lo justo para que un tercero pueda darte mucho más (Custom Workflow Actions, Nintex, K2…) y así sigue manteniéndose todo el ecosistema del mundo de SharePoint!

¡Hasta otra!

lunes, 24 de marzo de 2014

SP2013: Cómo simular permisos a nivel de campo (columna) de lista mediante 2 listas relacionadas

Que quede claro desde el principio: En SharePoint (2013 y anteriores) no se puede hacer una configuración de permisos a nivel de campo (columna/metadato) en una lista o biblioteca de forma out of the box. El más bajo nivel de granularidad de permisos está a nivel de ítem o documento. Hasta donde humildemente sé, Microsoft no ofrece este nivel de permisos para evitar que el rendimiento de la plataforma se vea afectado (no comments...)

Para simular este comportamiento, se puede recurrir a diversas soluciones adicionales, tales como:
  • Formularios personalizados: InfoPath, custom .aspx, Nintex Forms...
  • Fabricarse con Visual Studio y código .NET un tipo de control en listas que permita especificar permisos.

Personalmente no soy amigo de ninguna de ellas, debido a los problemas que el custom code puede suponer en futuras migraciones. En relación a InfoPath, ya sabemos todos que le han hecho un funeral propio en la SharePoint Conference 2014, y que en cuestión de un año aproximadamente se empezará a recibir una tecnología sustitutiva (recemos para que incorpore lógica de campos en sus primeras versiones).

Así que hoy me voy a centrar en otro tipo de solución para "simular" (no es realmente así) el comportamiento de "permisos a nivel de campo de lista". Es cierto que no os servirá para todos los casos, ni para las casuísticas más complejas que requieran de múltiples estados, pasos o campos, pero estoy seguro de que, como a mí, os podrá alegrar el día en más de una ocasión.

Imaginemos el siguiente requerimiento de cliente: Quiero tener un listado de tareas (tipo Project professional) con múltiples campos, pero el usuario solo ha de poder modificar uno de ellos.

Lo primero que de forma natural nos vendrá a la cabeza es que SharePoint no permite "que el usuario solo pueda modificar uno de ellos", la configuración de seguridad se deja hacer a nivel de lista o de ítem, no de metadato. Es cierto que podríamos jugar con aspectos como la configuración de las vistas, o limitar la edición de algunos campos para que en los formularios no fueran visibles, pero seguramente al cliente no le satisfacería ninguna de ellas, por la carencia de una seguridad real (las vistas capadas se podrían saltar si el usuario conoce la URL de la vista "completa") o por los posibles efectos secundarios de las mismas (si configuras que un campo se oculte en los formularios, tampoco se mostrará en el momento que lo necesites informar para su alta).

Así pues, la solución propuesta es la de disponer de 2 listas distintas: Una para el usuario que crea la información, otra para el usuario que la modifica, y vincularlas mediante campos de tipo "lookup" y un sencillo flujo de trabajo. El esquema de funcionamiento sería el siguiente:

 
Vamos por pasos:

1.- Crear la lista original (que he llamado "Project Tasks"), con todos los campos de información que el usuario creador de contenidos ha de poder informar. Esta "custom list" la crearemos de forma totalmente normal, con sus campos de tipo "single line of text", "Number", "Date and Time"... En mi caso tiene un aspecto como el siguiente:



2.-Crear la lista destino (que he llamado "User Tasks"). En este caso también será una "Custom list", pero sólo crearemos de forma "normal" los campos que el usuario deba poder modificar. En mi caso era un único campo, "New Remaining Duration". El resto de campos los importaremos de la lista anterior mediante un campo de tipo lookup vinculado a la primera lista. El campo principal será el nombre de la tarea, pero luego añadiremos todos los campos que queremos que el usuario pueda ver (pero no modificar).

 
3.- A continuación, en esta segunda lista, iremos a su configuración avanzada (list settings --> Advanced Settings) y marcaremos que permita la gestión de los tipos de contenido en la lista

 
 
4.- A continuación, iremos a la pantalla de edición del content type de la lista, desde la pantalla de "list settings" (sección "Content Types").


5.- Ocultaremos todos los campos que no queramos que aparezcan en los formularios de edición del ítem, como la propia columna que vincula con la primera lista (tasks), debido a que ya crearemos un workflow que se encargue de generar automáticamente los ítems en la segunda lista. Para ello, pulsaremos en los nombres de las columnas que queramos ocultar, y en la pantalla de detalles del campo, pulsaremos en la opción "This column is: Hidden (Will not appear in forms)".


En mi caso oculté las columnas "Title", "Task" y alguna otra que creé específicamente para la gestión de permisos de los ítems. Dejé únicamente como "Optional" la columna que sí quería que el usuario pudiera modificar.


Llegados a este punto, si intentamos crear un nuevo ítem en la segunda lista, veremos que, efectivamente, SharePoint solo nos pregunta por el campo que hemos dejado visible en el content type.

6.- Ahora ya solo nos falta crear, en la lista original, un sencillo workflow de SharePoint 2013 que, cada vez que se cree un nuevo elemento en la lista principal, copie el ítem en la segunda lista. El workflow, en mi caso, consta de los siguientes pasos:
  1. Acción para fijar el estado del flujo inicialmente ("Set Workflow Status" en "Core Actions")
  2. Acción para notificar en el log del flujo que el flujo ha comenzado correctamente ("Log to History List" en "Core Actions")
  3. Acción que para crear el ítem en la segunda lista, basándonos en todos los valores que contiene la primera ("Create List Ítem" en "List Actions")
  4. Acción para notificar en el log del flujo que el flujo ha finalizado ("Log to History List" en "Core Actions")
  5. Acción para fijar el estado del flujo inicialmente ("Set Workflow Status" en "Core Actions")
Destacar que la acción número 3, la de copiar el ítem de la 2a a la primera lista, el campo principal a informar es el que creamos como base del lookup ("task" en mi ejemplo) y que aunque al final veamos un valor de tipo string escrito en él, en realidad, le tenemos que pasar el ID del elemento actual que está ejecutando el flujo. Es importante el detalle de escribir el ID en lugar del campo "task", porque el lookup espera un campo de tipo integer (ID del elemento origen) y no un string, y hacerlo de otra forma provocaría error en el flujo.


Así pues, el flujo debería tener un aspecto similar al de la siguiente imagen.

 
 
Ya está todo hecho. Veremos que si el usuario "creador" introduce en ítems en la primera lista, estos se copian a la segunda, donde el usuario "informador", o "editor" podrá ver todos los datos, pero editar únicamente los que creamos explícitamente a tal efecto y dejamos visibles en la configuración del content type de la lista.

Se podría complicar la casuística, añadiendo más fases de edición (más listas) y reflujos de retorno (que al modificar algo en la segunda lista, copiara los cambios en la 1a, por ejemplo), pero esto ya dependerá de los requerimientos de cada cliente y aquí cada uno evolucionará el modelo según la necesidad de cumplir dichos requerimientos.

Realmente no es una forma sencilla ni "bonita" de trabajar, pero podemos cumplir con lo de "que unos usuarios puedan editar unos campos y otros usuarios editen otros campos" sin programar una sola línea de código.

Personalmente rezo para que lleguen pronto los prometidos FoSL (Evolución natural de los List Forms), y que permitan realizar, con una interface intuitiva integrada en la gestión de las listas, lo que ahora teníamos que configurar en InfoPath, Visual Studio .NET u otros programas de formularios SharePoint de terceros.

Hasta entonces...¡¡Ahí queda eso!!

lunes, 10 de marzo de 2014

SP Designer 2013: Bucle para recorrer todos los ítems de una lista.

Este post, es la continuación de un post anterior:
SP Designer 2013: Cómo usar la acción de "Call HTTP web service" para obtener los ítems de una lista de SharePoint, donde vimos cómo se podía utilizar el Call HTTP Web Service Action para obtener todos los ítems de una lista en una variable de tipo Dictionary, y cómo obtener la información de cada elemento de la lista. Hoy vamos a evolucionar el mismo ejemplo, partiendo de la misma base creada, para crear un bucle que vaya recorriendo todos los elementos de la lista y vaya realizando cambios en cada uno de ellos.

Para ello, partimos de la base del flujo que se creó en el post anterior, pues hay que tener en cuenta que la manera más sencilla y eficaz de recorrer todos los ítems de una lista va a ser a partir de un Web Service que nos retorne en una variable de tipo array (Dictionary) todos los ítems de dicha lista.



La idea es trabajar el siguiente ejemplo: A partir de un listado que contenga información de cada empleado, incluyendo sus días de vacaciones, hacer un flujo que al final del año permita:
  • Reiniciar los "Dias de vacaciones aprovados" a 0
  • Ver si quedan días pendientes, y notificarlos como días Extras para el nuevo año (si me han sobrado 3 días en 2013, voy a tener 3 días adicionales en 2014).
Puesto que en los flujos de SharePoint 2013, el valor que asume por defecto el "Workflow Status" es el del nombre de la etapa (Stage) donde se encuentra, no está de más que la primera acción del flujo sea un "Set Workflow Status" (en Core Actions), para poder mostrar un estado que al usuario no le parezca extraño (El nombre de la Stage no suele ser muy concreto).

También hay que tener en cuenta que vamos a necesitar una variable índice para recorrer el bucle, así que la creamos, de tipo Integer


Y luego añadimos una acción al principio del flujo (tras la modificación del Status), con la acción "Set Workflow Variable" (En Core Actions), que inicialice el índice a 0.

El siguiente paso es saber cuantas iteraciones vamos a tener que realizar en el bucle. Puesto que ya guardamos en una variable de tipo Dictionary todos los ítems devueltos por el WS ("Get d/results"), haremos uso de la acción "Count Items in a Dictionary" (en Core Actions) para contar de forma específica cuantos elementos contiene esa variable (que es lo mismo que saber cuantos ítems contiene esa lista). El Output de esta acción se guardará automáticamente en otra variable de tipo integer.

Ahora ya estamos listos para iniciar el bucle. En este punto, si se desea, se puede hacer un cambio de Workflow Status, o incluso de Step o Stage del flujo.

Para crear el bucle, iremos a la Ribbon y seleccionaremos "Loop" y en concreto "Loop n Times"


Y modificamos la variable para que coincida con la que nos devolvió el número de elementos del listado en la acción "Count Ítems in a Dictionary".


Este paso es opcional, pero importante si se quiere hacer que el flujo deje algún mensaje en el log que vaya informando de lo que va ocurriendo. Se trata de guardar información del ítem que actualmente está procesando el bucle, para irla mostrando en el log del Workflow. Para ello, volveremos a utilizar la Acción "Get an Item from a Dictionary" (Core Actions), pero esta vez especificando el ítem y el campo a retornar. En este caso la fórmula a utilizar sería la siguiente:
d/results([%Variable: Index%])/Title.

Como hemos inicializado Index a 0, la primera vez ejecutará: "d/results(0)/Title", de forma que retornará el Título del primer ítem del Array.


Podríamos repetir esta acción tantas veces como queramos para obtener diversos campos de cada elemento y luego componer una cadena con ellos en una acción "Log To History List".

Hay un campo que es necesario retornar, para luego poder modificar el ítem correctamente, y es el ID del ítem, que almacenaremos en una nueva variable de tipo Integer, ItemID :


Ahora, utilizando el ID del ítem retornado, podremos obtener, por ejemplo, cuantos días pendientes de vacaciones tiene ese empleado en concreto. Para ello usaremos una Acción del tipo "Set Workflow Variable" (Core Actions). En el campo "Data Source" haremos referencia al listado donde se contiene la información de los usuarios (el que hemos atacado con el WS), en "Field from source" especificaremos el nombre de la columna que almacena el dato que queremos recuperar (días pendientes de vacaciones), y en la segunda parte del formulario, especificaremos que se base en el ítem que contenga el Field ID, con el valor de la variable ÍtemID que inicializamos en el paso anterior. Al usar el campo ID, que es único para cada ítem, nos aseguramos de estar accediendo a un ítem concreto sin riesgo a duplicidad en la consulta. El resultado lo almacenaremos en una nueva variable de tipo Integer, NewExtraLeave.


 En este punto, y en mi caso, consideré oportuno usar una acción del tipo "Log to History List" (Core Actions) del siguiente tipo:


Una vez realizado esto, ya podemos actualizar el ítem en cuestión, modificando los campos de días aprobados (en mi caso "Approved Leave") a 0 y los de días extra (en mi caso "Extra Leave") al número de días que teníamos pendientes a final de año (recopilados en la variable "NewExtraLeave"). En mi caso, la lista también  contiene un campo donde se van justificando el por qué se añaden días extras (en mi caso "Extra Leave Reasons"), que también actualizo en esta acción mediante un string que explica el número de días sobrantes a final del año.

En la última parte del formulario de esta acción hay que especificar que el ítem de la lista que queremos cambiar es el que tiene el campo ID con el valor de la variable del flujo "ItemID" (proveniente del "Get d/results([%Variable:Index%])/ID).


Ahora ya solo nos queda incrementar el valor del índice del bucle. Para ello usaremos una acción del tipo "Do Calculation" (Core Actions), donde especificaremos un cálculo del tipo "Index plus 1". Esto se guardará en una nueva variable de tipo "Number".

Notad aquí una cosa "rara": Nuestra variable Index es de tipo "Integer", pero al realizar el cálculo, nos fuerza a guardarlo en un "Number". ¿Cómo lo hacemos compatible? Pues nos fuerza añadir una nueva acción del tipo "Set Workflow Variable", donde especificaremos que "Index" asume el valor del resultado del cálculo (simpatías de SharePoint Designer :-p)


Ya tenemos el flujo completo! Ahora ya estamos obteniendo los valores de una lista, recorriéndolos uno a uno y modificándolos al paso iterativo del bucle.

Podemos añadir algún paso adicional de cambio de estado al final del flujo, y por último marcar el "Transition to stage" final a "End of Workflow".

Así pues, nuestro flujo resultante ha de tener un aspecto similar al de la siguiente imagen:


¡Listo! Aquí un ejemplo de las nuevas capacidades de los flujos en SharePoint 2013, con llamadas a Web Services, bucles y uso de variables de tipo Dictionary.

Observad que todo el rato hemos utilizado acciones de tipo "Core Actions", por lo que podríamos haber definido este flujo como un "Site Workflow", ya que no hace uso de ninguna acción específica de lista. Con ello se consigue que un flujo delicado como este (añade días de vacaciones por nuevo año a todos los empleados) sólo se pudiera activar desde la Site Administration (que suele estar más restringida a administradores), y no desde una lista en concreto.

Eso es todo por hoy. ¡¡Un saludo!!

jueves, 6 de marzo de 2014

SP Designer 2013: Cómo usar la acción de "Call HTTP web service" para obtener los ítems de una lista de SharePoint

Sin duda ya casi todos sabréis que una de las novedades de SharePoint Designer 2013 en cuanto a la generación de workflows es que ahora incluye por defecto la acción "Call HTTP web service".
Lo que quizás algunos no sabréis es cómo utilizar correctamente esta acción, ya que no es tan trivial como pudiera parecer.

Voy a centrarme en un ejemplo de cómo atacar a las propias listas de SharePoint, para obtener un listado completo de ítems de una lista. Recordad que otras de las novedades de los flujos en SharePoint 2013 es que podemos crear variables de tipo array ("Dictionary") de datos, y usar bucles (loops). Todo junto, nos ofrece una serie de posibilidades que para emularlas en 2010 hubiéramos tenido que tirar de desarrollos a medida, soluciones de terceros o apaños rocambolescos.

Para obtener de forma estándar (sin desarrollos a medida) el listado de ítems de cualquier lista o biblioteca que queramos de nuestro SharePoint en SharePoint Designer, tendremos que hacerlo haciendo uso de los Web Services GET que SharePoint pone a servicio para obtener datos de listas/bibliotecas.

 Tal y como podemos ver en las páginas de MSDN respecto al REST service, las llamadas a los Web Services para obtener datos de listas/bibliotecas son las siguientes:
 
Description
URL endpoint
HTTP method
Retrieves the title of a list
web/title
GET
Retrieves all lists on a site
lists
GET
Retrieves a single 'list's metadata
lists/getbytitle('listname')
GET
Retrieves items within a list
lists/getbytitle('listname')/items
GET
Retrieves a specific property of a document. (In this case, the document title.)
lists/getbytitle('listname')?select=Title
GET

Así pues, nuestro Web Service debe tener una estructura como la siguiente (recomiendo probarlo previamente en el navegador, para comprobar sus resultados antes de trabajar con esta cadena de conexión en SharePoint Designer):

https://sitecollection/site/_api/web/lists/GetByTitle('listname')/items

Otro punto importante a tener en cuenta es que en la api REST de SharePoint 2013, las llamadas han de tener formato JSON, y para ello, tendremos que añadir información en las cabeceras http (http headers) de la llamada REST, concretamente tendremos que construir la siguiente cabecera:

Accept : application/json;odata=verbose
Content-Type : application/json;odata=verbose

Para hacerlo, primero tendremos que crear una variable en SharePoint Designer de tipo Dictionary (Local Variables - Add).


El siguiente paso es añadir a nuestro flujo una acción del tipo "Build Dictionary" (en "Core Actions"). En ella añadiremos los siguientes valores:

  • Name
    • Content-Type
  • Type
    • String
  • Value
    • application/json;odata=verbose
y también:
  • Name
    • Accept
  • Type
    • String
  • Value
    • application/json;odata=verbose
Como output, indicaremos la variable de tipo Dictionary que acabamos de crear.


Con la cabecera http construida, ya podremos invocar el Web Service de forma exitosa. Para ello añadiremos la acción Call HTTP Web Service (en "Core Actions") a nuestro flujo, con los siguientes parámetros:
  • this: Pulsar y añadir la cadena de conexión al WS, seleccionando el método HTTP GET
  • request: Dejar en blanco
  • response: Añadir aquí una nueva variable de tipo Dictionary, donde se almacenará el XML retornado en formato JSON.
  • responseHeaders: Dejar en blanco
  • variable: Nueva variable de tipo String que informará de si la operación se ha ejecutado correctamente o no (se aconseja justo después de la acción Call HTT Web Service, usar la "Log to History List" y mostrar esta variable, para comprobar si la llamada se ha realizado correctamente o no)

Hay que tener en cuenta para qué sirve cada método y por qué nosotros, en este caso, especificamos el HTTP GET.
  • HTTP GET - Para obtener información.
  • HTTP PUT - Para crear nueva información.
  • HTTP POST - Para editar información ya existente.
  • HTTP DELETE - Para borrar información ya existente.
Ahora tendremos que introducir en nuestra llamada, la cabecera HTTP que preparamos previamente. Para ello, desplegad el menú de opciones de la acción Call HTTP Web Service, y seleccionad "Properties..."
 
En el campo "RequestHeaders" es donde debemos indicar el nombre de la variable de tipo "Dictionary" que preparamos previamente (pulsad OK para guardar los cambios).


 Pues ahora sí, nuestra acción de llamada a servicio web HTTP debería funcionar correctamente.

¿Cómo podemos explotar la información retornada en la variable del campo "response"? Pues mediante la acción "Get an Item from a Dictionary" (Core Actions)

Hay que tener en cuenta que el formato JSON de los REST Services que retornan elementos de listas de SharePoint, tienen un formato del tipo:


Y por tanto, para referirnos a ellos desde una variable de tipo Dictionary deberemos hacerlo mediante "d/results".

En la acción de "Get an Item from a Dictionary", si queremos obtener todo el conjunto de resultados (todos los ítems), tendremos que especificar los siguientes valores:
  • ítem by name or path: "d/results". Que, como hemos visto es el formato de esquema XML que retorna el JSON de los REST Services de SharePoint.
  • dictionary: Variable de tipo Dictionary que pusimos en el parámetro "response" de la acción "Call HTTP Web Service"
  • ítem: Una nueva variable de tipo Dictionary, donde almacenaremos el conjunto de valores retornado.

Aquí comentar que también podríamos acceder a un ítem concreto (con todos sus metadatos) si en lugar de "d/results" especificamos "d/results(0)", donde (0) es el índice del elemento al que queremos acceder, siendo (0) el primer elemento retornado, (1) el segundo, etc. Puesto que seguiría retornando un conjunto de elementos, el Output debería ser una variable de tipo Dictionary.

Por último, también podríamos acceder a un metadato/propiedad específica de un elemento concreto, utilizando en este caso "d/results(0)/Title", donde "Title" es el nombre del metadato/propiedad que queremos obtener. En este caso, el output será una variable de tipo string, number, boolean, Date/Time... en función del metadato en cuestión.

Hasta este punto, nuestro flujo en SharePoint Designer, debería tener un formato similar al de la siguiente imagen:

 
Así pues, ya hemos visto cómo atacar a una lista de SharePoint mediante una Call HTTP Web Service action de SharePoint Designer 2013 y explotar sus resultados.

De momento lo dejamos aquí, pero en un próximo post (SP Designer 2013: Bucle para recorrer todos los ítems de una lista), veremos cómo podemos aprovechar el trabajo realizado hasta ahora, para, por ejemplo, recorrer ítem por ítem una lista de SharePoint y efectuar cambios en todos sus elementos (loop para modificar todos los ítems de una lista).

Hasta entonces, ¡Un saludo!