Potenciando la personalización y la experiencia del usuario
Para desarrollar una extensión web en Azure Devops, Microsoft deja a disposición una gran cantidad de documentación usando HTML, JavaScript y CSS e incluso un listado de repositorios de ejemplo donde se puede encontrar el azure-devops-extension-sample.
React es el framework que se usa en general para desarrollar las extensiones y una gran ventaja que se obtiene es que se puede usar la dependencia azure-devops-ui que permite obtener el mismo aspecto (familia de fuentes, tamaños de fuente, tratamiento de hipervínculos, etc.) de las páginas de Azure DevOps lo que la haría más cercana al estándar. Ahora bien, si tu fuerte no es React y te sientes más cómodo usando otros frameworks como Angular, este post es para ti.
Angular es conocido por su enfoque en la creación de aplicaciones web de una sola página (Single Page Applications - SPAs), donde la interacción del usuario es fluida y rápida. Además, proporciona un conjunto de herramientas y funcionalidades que facilitan el desarrollo de interfaces de usuario complejas y dinámicas. Al utilizar Angular para desarrollar una extensión web de Azure DevOps, se puede obtener una serie de beneficios significativos:
En primer lugar, Angular proporciona una estructura modular que permite una mejor organización del código y una mayor reutilización de componentes. Esto conduce a un desarrollo más rápido y mantenible.
Además, Angular ofrece un enlace de datos bidireccional (two-way data binding) que facilita la sincronización automática de datos entre el modelo y la vista. Esto agiliza el desarrollo de interacciones interactivas y en tiempo real.
Otro beneficio importante de Angular es su capacidad de optimización de rendimiento. Gracias a su enfoque en la detección de cambios y la actualización selectiva de la interfaz de usuario, Angular asegura una experiencia fluida para el usuario, incluso en aplicaciones con conjuntos de datos masivos.
Los pasos para desarrollar una extensión web en Azure DevOps con Angular serán similar a los pasos que existen en la documentación, pero con algunas variaciones. El ejemplo a seguir es análogo al de la documentación oficial para demostrar la conexión entre la página host y el iframe de la extensión.
Paso 1: Configuración del entorno de desarrollo
Asegúrate de tener instalado Node.js en tu máquina.
Instala la herramienta de empaquetado de extensiones (TFX).
Ejecuta npm install -g tfx-cli desde un símbolo del sistema, este se usará para empaquetar la extensión más adelante.
Debes ser propietario de la organización.
Paso 2: Creación del proyecto de la extensión usando Angular
Abre el terminal y navega hasta la ubicación de tu carpeta.
Ejecuta el siguiente comando para generar la estructura inicial del proyecto:
Sigue las instrucciones en la terminal para proporcionar los detalles.
Paso 3: Instalar el SDK de extensión web de Azure DevOps
El cliente SDK permite a las extensiones web comunicarse con el marco del host. Este se puede utilizar para:
Notificar al host que la extensión está cargada o tiene errores.
Obtener información contextual básica sobre la página actual (usuario actual, host e información de la extensión)
Obtener información sobre el tema
Obtener un token de autorización para utilizarlo en llamadas REST de vuelta a Azure DevOps
Obtener servicios remotos ofrecidos por el marco del host
Para instalarlo ejecuta la siguiente instrucción en el directorio del proyecto.
Paso 4: Creación del archivo manifest
Deberás crear un archivo de manifiesto con el nombre vss-extension.json en la raíz del directorio de la extensión, este va a tener el siguiente contenido:
A continuación explico el significado de los diferentes elementos del archivo de manifiesto para la extensión:
ManifestVersion: Indica la versión del manifiesto de la extensión. En este caso, se usa la versión 1.
id: Es un identificador único para la extensión. Debe empezar por un carácter alfabético o numérico y contener de la "A" a la "Z", de la "a" a la "z", de "0" a "9" y "-" (guión). En este ejemplo, se utiliza "my-first-extension-angular" como identificador.
Publisher: Es el nombre del editor o autor de la extensión. En este caso, "Antony-Triana" es el nombre del editor.
Version: Indica la versión de la extensión. En este caso, se usa "1.0.0". Debe tener el formato major.minor.patch
Name: Es el nombre de la extensión. Aquí se usa "Angular My First Extension". Limitado a 200 caracteres.
Description: Proporciona una breve descripción de la extensión. En este ejemplo, se usa "A sample Visual Studio Services extension" (Una extensión de ejemplo de Visual Studio Services).
Public: Es un valor booleano que indica si la extensión es pública o privada. En este caso, se establece en "false", lo que significa que es una extensión privada.
Categories: Es una lista de categorías (Array de strings) a las que pertenece la extensión. Aquí se usa "Azure Repos" como categoría. Se debe proporcionar al menos una categoría y no hay límite en el número de categorías que se pueden incluir.
Targets: Indica a qué servicios o componentes de Azure DevOps se dirige la extensión. En este caso, se dirige a "Microsoft.VisualStudio.Services". Este es un array de objetos, donde cada objeto tiene un campo id que indica uno de los siguientes:
Microsoft.VisualStudio.Services (extensión que funciona con Azure DevOps o TFS)
Microsoft.TeamFoundation.Server (extensión que funciona con TFS)
Microsoft.VisualStudio.Services.Integration (integraciones que funcionan con Azure DevOps o TFS)
Microsoft.TeamFoundation.Server.Integration (integraciones que funcionan con TFS)
Contributions: Define las contribuciones de la extensión, como paneles, menús, etc. En este ejemplo, se define una contribución de tipo "ms.vss-web.hub" con un ID de "my-hub", que se dirige al grupo "ms.vss-code-web.code-hub-group". También se proporcionan propiedades como el nombre, la URI, el icono y el soporte móvil de la contribución. Para más información consulta acá
A continuación, explicaré el significado de cada propiedad en el ejemplo que proporcioné:
Name: Es el nombre de la contribución. En este caso, se establece como "Angular Sample Hub", lo que define el nombre del hub de la extensión.
uri: Es la ruta relativa a la ubicación del archivo HTML principal de la contribución. En este ejemplo, se establece como "dist/index.html". Indica que el archivo principal del hub se encuentra en la carpeta "dist" y se llama "index.html". Esta ruta representa la ubicación del contenido que se mostrará cuando se seleccione el hub dentro de Azure DevOps.
icon: Es la ruta del archivo de icono que se utilizará para representar la contribución. En el ejemplo, se utiliza "asset://static/sample-icon.png" como la ubicación del archivo de icono. Esto implica que el archivo de icono se encuentra en la carpeta "static" de la extensión y se llama "sample-icon.png".
SupportsMobile: Es un valor booleano que indica si la contribución es compatible con dispositivos móviles. En este caso, se establece como true, lo que indica que el hub de la extensión es compatible con dispositivos móviles.
icons: Se especifica la ruta del archivo de icono de la extensión. En este caso, se usa "logo.png" como el icono por defecto.
content: Proporciona información adicional sobre el contenido de la extensión. En este ejemplo, se usa "overview.md" como el archivo que contiene los detalles.
files: Define una lista de archivos y carpetas que forman parte de la extensión y son accesibles desde la misma. En este caso, se incluyen las carpetas "static" y "dist". Estas carpetas se agregan para que los archivos y recursos contenidos en ellas sean accesibles desde la extensión web en Azure DevOps. A continuación, explico con más detalle:
"static": La carpeta "static" generalmente se utiliza para almacenar archivos estáticos que son necesarios para la funcionalidad de la extensión, como imágenes, hojas de estilo CSS, scripts JavaScript y otros recursos similares. Estos archivos estáticos pueden ser referenciados y utilizados por la extensión desde su código.
"dist": La carpeta "dist" se utiliza comúnmente para almacenar los archivos generados después de compilar el código fuente de la extensión. Aquí es donde se colocan los archivos de distribución o "build" que contienen el código compilado, optimizado y listo para su ejecución en un entorno de producción. Esto puede incluir archivos JavaScript, HTML, CSS y otros recursos necesarios para la extensión.
El manifiesto define los detalles esenciales de la extensión, como su identificador, versión, contribuciones y otros elementos necesarios para su funcionamiento dentro de Azure DevOps.
Paso 5: Importar el SDK de Azure DevOps en el código TypeScript
Añade la importación en el componente raíz del proyecto Angular en src/app/ app.component.ts de la siguiente manera:
Paso 6: Inicializar el SDK
Una vez se haya renderizado el contenido de la extensión se debe llamar a SDK.init(). El contenido de la extensión no se mostrará hasta que haya notificado al marco host que está listo. Para esto hay dos opciones:
Llamar a SDK.init() sin la opción loaded que es lo que haremos en este artículo debido a que no será mucho lo que tenga que cargar.
Llamar a SDK.init({ loaded: false }) para comenzar a inicializar el SDK. Luego llamar a SDK.notifyLoadSucceeded() una vez que haya terminado el renderizado inicial. Esto permite realizar otras llamadas al SDK mientras el contenido aún se está cargando (y oculto tras un spinner).
En este caso se implementará la interface OnInit para usar el método ngOnInit y cargar el SDK en el componente raíz, es decir src/app/app.component.ts. También se creará un método para obtener el usuario en Azure DevOps, pero antes deberás llamar el método SDK.ready() para esperar a que la plataforma esté completamente cargada antes de ejecutar cualquier código adicional de la extensión. Y finalmente se obtiene información del usuario actual mediante la función SDK.getUser() y con la propiedad displayName se obtienen los nombres y apellidos y se le transfiere a una variable previamente definida llamada userName.
El código sería el siguiente:
Ahora a modificar el componente “app.component.html”. Se puede eliminar todo el código HTML o bien pasar por interpolación la variable userName en el HTML, en este caso dejaré el código actual para tener la plantilla original de Angular cuando se crea el proyecto, solo agregaré la variable de userName como en la imagen
Paso 7: Modificar el outputPath
Como anteriormente había definido en las propiedades de las contribuciones (uri específicamente) que la ruta relativa del HTML de la contribución principal sería "dist/index.html", entonces en el archivo de configuración angular.json se debe cambiar el outputPath
Paso 8: Cambiar la URL base relativa en el index.html
Para esto solo se requiere cambiar la URL base relativa en el index.html para que la extensión pueda cargar correctamente todos los recursos, se necesita cambiar la etiqueta <base href="/"> por <base href="./">
Al cambiar la etiqueta, se está especificando que las rutas y enlaces de la aplicación Angular se resuelvan desde la ubicación actual de la aplicación, lo que puede ser útil cuando la aplicación se ejecuta en un subdirectorio o contexto específico como es este caso.
Paso 9: Empaquetar y publicar la extensión
Finalmente solo resta empaquetar el proyecto Angular
Primero ejecuta npm run build
Posteriormente realiza un empaquetado de la extensión ejecutando tfx extension create
Ya solo queda publicar la extensión web e instalarla en la organización de Azure DevOps, si requieres más información consulta aquí.
Una vez publicada la extensión e instalada este es el resultado:
Una vez cargada la extensión se visualiza de la siguiente manera:
Y así es posible construir una extensión usando Angular. Lo que para mí comenzó como mera curiosidad, se volvió un desafío y el resultado ha sido escribir este artículo que espero haya sido de ayuda, pienso que en cada desafío hay siempre una oportunidad de crecimiento y aprendizaje. Te invito a ver el código de ejemplo en el siguiente repositorio y no dudes en escribir un comentario si tienes alguna duda.
¡Gracias por leer!
Exelente que cambios se de ne hacer para hacer un widget para dashboard.
Buen articulo!