Crear y enviar una tarjeta (Adaptive Card) a un grupo de Teams a partir de una acción en D365 CE

El día de hoy quiero enfocarme en una funcionalidad bastante sencilla que une Teams, MS Flow y D365 CE. La idea es aprovechar los chat que tenemos en Teams como canal para recibir notificaciones.

El flujo que deseo explicar, consiste en enviar una tarjeta de notificación a un grupo de Teams a partir de la creación de un caso, aunque lo mismo podría aplicar si que queramos notificar el escalamiento del caso a una cola, la creación de una oportunidad, avisar de una oportunidad ganada o perdida, entre otros.

1. Creación de tarjeta (Adaptive Card)

Este primer paso consiste en crear la tarjeta, para ello nos apoyaremos en el diseñador que nos dispone microsoft:https://acdesignerbeta.azurewebsites.net/

En este punto, no hay mucho que explicar, realmente el diseñador es medianamente sencillo de utilizar (aun esta en beta y tiene algunos pequeños bug, que esperemos próximamente se corrijan), ademas nos muestra como se verían dicha tarjeta en distintas aplicaciones. Allí arrastramos componente y vamos mirando en vivo como va quedando, mientras que en la parte inferior nos muestra una especie de consola donde se va generando el JSON que corresponde al esquema de dicha tarjeta. Este JSON es el que utilizaremos en MS FLOW para poder enviarla como notificación.

En el ejemplo particular, la tarjeta que diseñe es la siguiente (si lo se, nada del otro mundo)

Si quieres usar el esquema que cree de ejemplo puedes copiarlo del siguiente LINK

2. Creando el Flujo en MS FLOW

Si ya tenemos el diseño de nuestra tarjeta, el paso siguiente es crear un flujo a partir de una acción, este ejemplo como lo indique al inicio, consiste en notificar cuando un caso es creado, pero esto tiene muchas aristas y va a depender netamente de nuestro requerimiento.

El flujo inicialmente es muy sencillo, parte con la creación de un registro en la entidad Caso y la declaración de dos variables, una para guardar y formar la URL del registro en D365 CE, y otra para guardar el nombre del cliente

Para lo que están familiarizados con D365 CE, sabrán que el cliente puede ser una Cuenta o un Contacto, y el flujo inicialmente solo nos da el id del cliente, por lo tanto, aquí aplico un condicional basado en el Customer Type, comparo si es una cuenta, y en caso positivo obtengo la información de la cuenta, de lo contrario, obtengo la información del contacto, en ambos caso guardo el valor que corresponda de la cuenta/contacto en la variable cliente.

El siguiente paso es buscar el conector de Teams, y seleccionamos la acción que se muestra a continuación

Aquí básicamente seleccionamos el equipo y luego el canal. En el mensaje pegamos el JSON de la tarjeta que generamos, y sustituimos los valores de prueba con los datos obtenidos del disparador del flujo.

Una vez que guardemos el flujo, el ultimo paso es crear un caso de D365 CE y esperar que en nuestro canal de Teams se reciba la tarjeta.

Como pudimos observar el ejemplo es muy sencillo, pero la idea es mostrar un poco las opciones que tenemos con Teams y como podemos seguir potenciándola como herramienta colaborativa.

Usar y crear campos nuevos sobre el perfil de usuario de Office 365 para hacer búsquedas en SharePoint. Parte 2/2

En la primera parte, escribí sobre como trabajar sobre una propiedad personalizada del perfil de usuario Office 365 para que fuera “rastreable“, en esta segunda parte, pasaré a una parte mas técnica, ya que estaré mostrando como usar la API de SharePoint para obtener la lista de usuarios y hacer filtro sobre campos personalizados. Para ello, mostrare ejemplos en JavaScript , trabajando en la consola del navegador (es importante que sobre la pagina que se quiera emular el ejemplo se tenga la referencia a la librería de jquery, ya que la estaré usando para realizar las consultas ajax, por lo que recomiendo usar una pagina clásica e inyectar el script con el Content Editor).

Los ejemplos estarán enfocado en el uso de la API “/_api/search/” y el uso de “/_api/Web/SiteUserInfoList/items“, ademas de explicar cual es la diferencia entre usar una y otra.

Obteniendo la lista de usuarios a través de
SiteUserInfoList

Para construir la URL de búsqueda usaré el objeto _spPageContextInfo (que trae una serie de parámetros del contexto de la pagina de SharePoint). Adicionalmente, a la consulta agregué un filtro donde el EMail de usuario no fuera null, esto permitirá filtrar la respuesta con resultados validos (de lo contrario el resultado podría traer información de grupos de SharePoint o usuarios de sistemas que para este caso en particular no queremos mostrar…)

$.ajax({ 
    url: _spPageContextInfo.webAbsoluteUrl +  "/_api/Web/SiteUserInfoList/items?$filter=EMail+ne+null",
    headers:{
        "accept": "application/json;odata=verbose",
        "content-type":"application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val()
    }
}).then((response)=>{ console.log(response)})
  .catch((response)=>{console.log(response)})

El resultado seria el siguiente (se muestra uno de los resultado del array ampliado)


Nota: Cuando el campo personalizado esta recién creado, no sera obtenible en los resultados de manera inmediata, por lo que es necesario esperar aproximadamente entre 20 minutos y 1 hora para que SharePoint lo indexe

Lo interesante (y poco agradable) de este método, es que el resultado obtenido esta limitado a la actividad de los usuarios dentro del sitio donde estamos haciendo la consulta, para explicarme mejor, el resultado obtenido solamente mostrara a los usuarios que pertenecen a los grupos de permisos del sitio, o de aquellos usuarios que han ingresado a dicho sitio. Es decir, si acabamos de crear un sitio llamado /GestorDocumental donde solamente han ingresado dos usuarios, cuando hagamos la consulta a la API, el resultado mostrara solamente la información de esos 2 usuarios, indiferentemente si en nuestro TENAN tenemos un total de 500 usuarios.

Finalmente, y para continuar, si queremos hacer una consulta filtrada basada en el campo personalizado, simplemente agregamos la condición en el $filter como se muestra a continuación

$.ajax({ 
     url: _spPageContextInfo.webAbsoluteUrl +  "/_api/Web/SiteUserInfoList/items?$filter=EMail+ne+null+and+NumeroIdentificacion+eq+'221321'",
     headers:{
         "accept": "application/json;odata=verbose",
         "content-type":"application/json;odata=verbose",
         "X-RequestDigest": $("#__REQUESTDIGEST").val()
     }
 }).then( response =>{ console.log(response)})
   .catch( response =>{console.log(response)})

Obteniendo la lista de usuarios a través de la API de búsqueda con /_api/search

La API /_api/search tiene muchas ventajas frente al anterior método, ya que lo podemos usar incluso para hacer búsqueda de documentos basado en el contenido de los archivos, pero esto sera un punto del cual escribiré en otra entrada… La forma de obtener el resultado basado exclusivamente en usuarios es adicionar a la consulta <sourceid=’B09A7990-05EA-4AF9-81EF-EDFAB16C4E31′>.

$$.ajax({ 
    url: _spPageContextInfo.webAbsoluteUrl +  "/_api/search/query?querytext='*'&sourceid='B09A7990-05EA-4AF9-81EF-EDFAB16C4E31'&rowlimit=5&startrow=0",
    headers:{
        "accept": "application/json",
    }
}).then(response=>{ 
	var totalRow = response.PrimaryQueryResult.RelevantResults.TotalRows;
	var items = response.PrimaryQueryResult.RelevantResults.Table.Rows;
	var elementsResult = [];
	items.forEach( element =>{
		var temp = {};
			element.Cells.forEach((act, i)=>{
			temp[act.Key] = act.Value
		})
		elementsResult.push(temp)
 	})
	console.log("Filas totales: "+totalRow);
	console.log(elementsResult);
}).catch(response=>{console.log(response)})

El resultado obtenido es un poco difícil de leer, por lo que en el código anterior simplemente procesé el resultado para que quede más legible y es lo que imprimo en la consola.

Resultado ampliado de uno de los registro

La ventaja de consultar de esta manera es que no tenemos la limitante que explique sobre el SiteUserInfoList, sin embargo, esta trae otras limitantes, que explicare a continuación:

  • 500 resultados por consulta: si revisan el código anterior, notaran que coloque en el query “rowlimit=5“, esto es la cantidad de filas que me devolverá, siendo el máximo posible 500 (colocar una cantidad mayor no hará que devuelva mas registros), y también coloque startrow=0, con lo cual estoy diciendo que los 5 registros deben partir desde la fila 0, por lo que si quiero obtener los 5 registros siguientes, debo hacer otra consulta pero colocando “startrow=5″. Adicionalmente la consulta nos devuelve una propiedad donde nos indica la cantidad total de registros que cumplen con el criterio de búsqueda, por lo que con este valor podemos determinar la cantidad de veces que podemos paginar el resultado. En el ejemplo ese valor total lo almacene en la variable totalRow y lo imprimí en la consola como se muestra al inicio de la foto anterior.
  • Propiedades personalizadas de usuario no son visible de manera predeterminada: otra de las cosas que se nota a simple vista es que la propiedad personalizada no se muestra, por lo que la consulta debe ser modificada para agregar una condición que permita indicar que propiedades de usuarios queremos mostrar, como por ejemplo el numero de identificación. En este punto vale la pena acotar, que las propiedades personalizadas no serán mostradas si estas no fueron asignadas a un “Refinable” como explique en el post anterior, y para obtenerla debemos “pedirla” a través del alias que le fue definido, que en nuestro caso era NumeroIdentificacion01

Entonces para que hagamos una consulta obteniendo las propiedades personalizadas y un conjunto de propiedades nativas podemos hacerlo adicionando al final de la consulta algo como: &selectproperties=’Title,NumeroIdentificacion01,JobTitle,WorkEmail’

$.ajax({ 
    url: _spPageContextInfo.webAbsoluteUrl +  "/_api/search/query?querytext='*'&sourceid='B09A7990-05EA-4AF9-81EF-EDFAB16C4E31'&rowlimit=5&startrow=0&selectproperties='Title,NumeroIdentificacion01,JobTitle,WorkEmail'",
    headers:{
        "accept": "application/json",
    }
}).then(response=>{ 
	var totalRow = response.PrimaryQueryResult.RelevantResults.TotalRows;
	var items = response.PrimaryQueryResult.RelevantResults.Table.Rows;
	var elementsResult = [];
	items.forEach( element =>{
		var temp = {};
			element.Cells.forEach((act, i)=>{
			temp[act.Key] = act.Value
		})
		elementsResult.push(temp)
 	})
	console.log("Filas totales:" +totalRow);
	console.log(elementsResult);
}).catch(response=>{console.log(response)})

El ultimo paso seria hacer consultas filtradas en base al parámetro personalizado, para ello simplemente modificamos el .<querytext=”*”> por <querytext=’NumeroIdentificacion01={valor}’>

$.ajax({ 
    url: _spPageContextInfo.webAbsoluteUrl +  "/_api/search/query?querytext='NumeroIdentificacion01=324234234'&sourceid='B09A7990-05EA-4AF9-81EF-EDFAB16C4E31'&rowlimit=5&startrow=0&selectproperties='Title,NumeroIdentificacion01,JobTitle,WorkEmail'",
    headers:{
        "accept": "application/json",
    }
}).then(response=>{ 
	var totalRow = response.PrimaryQueryResult.RelevantResults.TotalRows;
	var items = response.PrimaryQueryResult.RelevantResults.Table.Rows;
	var elementsResult = [];
	items.forEach( element =>{
		var temp = {};
			element.Cells.forEach((act, i)=>{
			temp[act.Key] = act.Value
		})
		elementsResult.push(temp)
 	})
	console.log("Filas totales:" +totalRow);
	console.log(elementsResult);
}).catch(response=>{console.log(response)})

Espero que esta serie de post les sirva de ayuda a todos aquellos colegas que realizan soluciones hospedas (Add-Ins) y desarrollos en SPFx. Próximamente estaré liberando pequeñas soluciones básicas en SPFx que utilizan estas mismas consultas.

Usar y crear campos nuevos sobre el perfil de usuario de Office 365 para hacer búsquedas en SharePoint. Parte 1/2

Un pedida muy común dentro de los desarrollos que abarcan el mundo de Office 365 (sobre todo en SharePoint), es la necesidad de que los perfiles de usuarios cuenten con información (propiedades) adicional a la que ya nos trae tipicamente, esto para poder cumplir con soluciones y desarrollados que se alimentan de dicha información. Una opción bastante útil es usar las propiedades de perfil de usuario en SharePoint, el cual trae un conjunto relativamente amplio que puede ayudar para este propósito, los mas destacados son: Cargo, departamento, fecha de cumpleaños, fecha de contratación, teléfono, entre otros (para ver la lista completa si eres administrador en SharePoint: https://{NombreDelTenan}-admin.sharepoint.com/_layouts/15/tenantprofileadmin/MgrProperty.aspx?ProfileType=User&ApplicationID=00000000-0000-0000-0000-000000000000 )

Adicional a los campos anteriores, la configuración de usuario de SharePoint permite agregar propiedades adicionales, como por ejemplo, una propiedad que tenga como propósito guardar el numero de identidad o numero de seguro social del usuario. Estas propiedades permiten distintos tipos de datos, y pueden ser configurados para que sean públicos (incluso para ser vistos en el perfil de Delve) o privados, ademas de que pueden ser o no sobrescritos por los propios usuarios.

Para poder aprovechar estas propiedades, es necesario usar la api de búsqueda https://{nombreTenan}.sharepoint/_api/search/” o https://{nombreTenan}.sharepoint/_api/Web/SiteUserInfoList/items”

1. Crear una propiedad personalizado

En este caso particular, el ejemplo consiste en crear una propiedad sobre el perfil de usuario llamado “Numero de Identificación“, para ello debemos poseer los privilegios de administrador en SharePoint.

1.1 El primer paso es ingresar al portal de Office 365 y dirigirnos a la administración clásica de SharePoint, luego clickeamos: Perfiles de usuarios -> administrar propiedad de usuarios

1.2 Seguidamente, en las opciones, damos en “Nueva propiedad”, y allí se nos desplegara un formulario con campos bastantes explicativos… lo que si me gustaría hacer énfasis en 3 cosas particular: el 1ro es que escojamos el tipo de dato apropiado de acuerdo a la información que va almacenar (si es de tipo string, datetime, integer, float, etc); el 2do es que dejemos marcado el campo como indexable (para que podamos filtrar y consultar a través de el) , y el 3ro se trata sobre si queremos que dicha propiedad se visualice como información adicional sobre el perfil de usuario en Delve, si es así, entonces marcar la opción “Mostrar en la sección Propiedades del perfil de la página de perfil del usuario“. Para este ejemplo escogeremos el tipo de dato string (single value)

img 1.2.1
img 1.2.2

1.3. Luego de crear la propiedad, el siguiente paso, es asignar el valor sobre el perfil de usuario. La ruta es administración clásica de SharePoint -> Perfiles de Usuarios -> administrar perfiles de usuarios.

1.4. A continuación buscamos al usuario por el nombre o el correo, seleccionamos el que corresponda y le damos “Editar mi perfil“.

1.5 Al darle “Editar mi perfil” se mostraran todas las propiedades de usuario, y es aquí donde buscaremos la propiedad a la que le queremos asignar un valor, en este caso la propiedad personalizada Numero de Identificación

1.6 Si la propiedad fue marcada “Mostrar en la sección Propiedades de la pagina de perfil del usuario” como se indico en el paso 1.2, entonces podemos ir al perfil del usuario en Delve y ya podemos ver visible la información


Con los pasos anteriores, ya hemos configurado una propiedad personalizada sobre el perfil de usuario, el próximo paso sera hacer esta propiedad rastreable para poder hacer consultas filtradas

2. Hacer que una propiedad sea rastreable

No todas las propiedades nativas del perfil de usuario son rastreables (es decir, que si la pedimos como campo a través de la api de búsqueda de SharePoint estas llegaran con valor null), por lo que las propiedades personalizadas tampoco lo son. Para hacer la propiedad rastreable los paso son:

2.1 Ir a administración clásica de SharePoint -> Búsqueda -> Administrar esquema de búsqueda

2.2 Luego del paso anterior, vamos a escribir en el buscador alguno de las siguientes palabras que explicare a continuación

  • RefinableString: esto es para obtener los campos que permiten envolver los valores de propiedades de tipo string
  • RefinableInt: para obtener los campos que envuelven resultados de tipo entero
  • RefinableDateTime: para obtener los campos que envuelven los valores de tipo fecha
  • RefinableDecimal: para obtener resultados de campos que envuelven valores de tipo decimal

En nuestro caso, y siguiente el ejemplo, vamos a buscar “RefinableString”, ya que la propiedad Numero de Identificación que creamos, fue del tipo de dato string. En la imagen siguiente, se puede notar que el RefinableString00 ya esta en uso, por lo que tomaremos el RefinableString01

2.3 Cuando estamos editando la propiedad RefinableString01, lo primero que debemos hacer es colocar un alias, en mi caso, acostumbro a colocar de alias el nombre interno de la propiedad que queremos, adicionando el sufijo de “01”, en este caso NumeroIdentificacion01.

2.4 Después de definir el alias buscamos un poco mas abajo en el formulario, y presionamos el botón de “Agregar una asignación“, esto desplegara una ventana modal, y allí en el campo “Filtrar una categoría” seleccionamos “People“, esperamos que recargue los resultados, y luego ubicamos la propiedad que anteriormente creamos (en esta ventana las propiedades tienen de prefijo la categoría): “People:NumeroIdentificacion

2.5 Luego de aceptar y guardar los campos, podemos buscar de nuevo los RefinableString y validar que se muestra con la propiedad y alias correspondiente

Finalizado todos los pasos anteriores, ya estamos listos para poder hacer consultas a los perfiles de usuarios con dichas propiedad a través de la API de SharePoint, la cual explicare en una segunda parte.

Propósito de este blog

He creado este blog por la necesidad que he tenido (desde ya hace tiempo) de aportar ideas y soluciones a las distintas comunidades de SharePoint y Dynamics CRM,  y romper esa barrera donde me quedo solo como lector. Tengo la convicción de que aveces no es suficiente con aprender de los demás, si no que para seguir avanzando, es necesario aportar tus propias ideas, experiencias y formas de trabajo para permitir que otros puedan debatir, corregir y sugerir sobre tu trabajo, obteniendo así una retroalimentación que permita a uno como individuo y como profesional seguir creciendo.

Los temas y puntos donde enfocare este blog estará relacionado a las tecnologías Microsoft, específicamente Dynamics 365 CE, todo lo que conforma la Power Platform (PowerApps, MS FLOW, PowerBI) , SharePoint online, e inclusive MS Teams. Aunque dichas tecnologías, algunas tienen finalidades y objetivos distintos, y otras se complementan, es importante resaltar que todas pueden convivir como un gran ecosistema que de manera conjunta pueden satisfacer necesidades de negocios. Lo interesante de todo esto, es que Microsoft nos provee un marco donde podemos modelar totalmente sobre soluciones nativas o soluciones personalizadas y complejas según se requiera  (para modelos de datos y aplicación empresarial tenemos PowerApps , para flujos de trabajo, automatización de procesos, e integración de servicios a MS FLOW,  para el análisis de datos a PowerBI, para gestión documental y colaboración SharePoint y Teams). Obviamente existe mucho mas allá de esto, como la integración con bots, el uso de inteligencia artificial, el internet de las cosas (IoT), entre otros, y espero en algún momento poder abarcarlo (cuando tenga el conocimiento y la madures de hacerlo llegar a los demás)

La intención es invitar a todas las personas, independiente del rol que ocupen en sus organizaciones, que den sus opiniones, dudas y sugerencias  sobre las distintas publicaciones que vaya realizando.

Sin mas que decir, espero que los distintos tópicos que encuentren en este sitio les sea de ayude para ustedes y sus organizaciones.