Barras de progreso Apps Script usando SPARKLINE()

Hace unos días tuvimos un poco de lío en la Comunidad de GEG Spain. Se nos habían colado más de 11.000 cuentas de spammers, que aunque no verificadas y por tanto sin capacidad de publicar en la comunidad, molestaban bastante.

Evidentemente, borrarlas a manubrio no era una opción, así que nos liamos la manta a la cabeza y le metimos mano a la API de Tribe para montar una estupenda hoja de cálculo de Google desde la que, vía Apps Script, pudiéramos eliminarlas sin dejar ni rastro mientras nos tomábamos unas cervezas. La idea era pues:

  1. Importar todos los usuarios a una tranquilizadora estructura tabulada o tabla, para los amigos.
  2. Utilizar los estupendos controles de filtro para descubrir las cuentas infames y facilitar su selección.
  3. Ejecutar un script GAS para cargárselas sin piedad.
Jugando con la API de Tribe desde hojas de cálculo y Apps Script.

La cosa ha ido bien y, de hecho, resueltos los problemas iniciales y eliminadas las cuentas felonas, nos estamos planteando automatizar una serie de procesos "de fondo" mediante activadores y webhooks controlados por Apps Script para mantener esta vuestra comunidad 😁 limpia de polvo y paja.

Pero de esto seguramente hablaremos otro día.

El caso es que montando este tinglado pensé (otra vez) en el mejor modo de mantener informado al usuario del progreso de un proceso que potencialmente puede llevar varios minutos, como ha sido esto de ventilarse cuentas de usuario por miles a través de una API de terceros. A nadie le gusta estar más de 3 segundos mirando una pantalla en la que no pasa nada. Personalmente en esas situaciones siempre tiendo a pensar que algo se ha roto y estoy contemplando...  la nada. Cosas de ser un agonías, supongo.

Si no nos queremos liar con el servicio HTML de Apps Script para montar un indicador de progreso como dios manda, afortunadamente tenemos alternativas (nota: incierto futuro tiene eso de usar HTML / CSS / JS junto a Apps Script cuando ya existe algo como el es-tu-pen-do Card Service para hacer el apaño (solo para complementos G Suite, nada de editores de documentos, en estos momentos).

Nótese eso de "currently"...

En el caso de las hojas de cálculo tenemos cositas interesantes como por ejemplo el método SpreadsheetApp.toast(mensaje, título, tiempo). Esto hace aparecer, casi a hurtadillas, un pequeño recuadro en la parte inferior derecha de la hoja de cálculo con el texto que queremos en su interior. Inconvenientes tiene, por ejemplo no admite saltos de línea. Y el recuadro aparece de manera animada, lo que puede resultar abrupto cuando se lanzan varios avisos sucesivos.

No obstante, su tamaño y su naturaleza asíncrona hacen que resulte muy cómodo para señalizar cosas como el inicio y fin de un proceso.

SpreadsheetApp.toast() en acción.

Otra estrategia para contarle al espectador qué está pasando es la obvia: actualizar el contenido de alguna celda de la hoja de cálculo con información relevante (numeritos y tal, alguna que otra ☑️, una marca de tiempo, colorines de fondo tal vez...). Todo bien, pero ya puestos, ¿por qué no usar esa navaja suiza que es la función integrada SPARKLINE?

¿Que no te crees que es una navaja tirolesa? Pues contempla con estupor todo lo que se puede hacer con ella en este artículo de Ben Collins, la "biblia" del SPARKLINE, ni más ni menos.

Pero, ¿usar SPARKLINE para qué? Pues para construir una barra de progreso...

🟦🟦🟦⬜️⬜️

...de esas que todos adoramos sin paliativos. Básicamente, podemos construir una de ellas con una fórmula tan concisa como esta...

=SPARKLINE({40\60};{"charttype"\"bar";"color1"\"#46bdc6";"color2"\"#999999"})

...que genera esto tan cuco en la celda en la que se introduzca:

Una barra de progreso generada con SPARKLINE() que representa un avance del 40%.

Los parámetros color1 y color2 pueden omitirse (obtendremos entonces naranja y azul). Pero la idea está clara, ¿a que sí?

He usado funciones SPARKLINE muchísimas veces en mis hojas de cálculo. Pero siempre de la manera habitual. Así que en el contexto de este problema spammico pensé, ¿por qué no combinarlas con Apps Script?

Y es que solo necesitamos unas líneas de código para inyectar en la celda deseada una fórmula con los parámetros adecuados. E ir modificando progresivamente los valores numéricos que controlan el tamaño de cada barra de color a medida que queramos señalizar la evolución del proceso representado.

Tas combinar celdas y ajustar la altura de la fila a discreción, la cosa funciona de un modo así de resultón en nuestro ejemplo inicial.

Ejemplo  de uso de una barra de progreso de  tipo SPARKLINE.

El código Apps Script es ridículamente simple.

Primero se definen unas constantes de partida para la barra:

// Doble barra en la fórmula (\\) para escapar carácter
const BARRA_PROGRESO = {
formula: 'SPARKLINE({0\\100};{"charttype"\\"bar";"color1"\\"#46bdc6";"color2"\\"#999999"})',
celda: 'A1',
pasos: 10
};

A continuación, inyectaremos la fórmula anterior (BARRA_PROGRESO.formula) modificando los valores numéricos  de manera que representen los tramos de cada color de la barra. Para ello se utiliza string.replace(). En este momento se materializará la barra de progreso en la hoja de cálculo en su estado inicial.

// Inicialización de la barra de progreso
const paso = numUsuariosEliminar / BARRA_PROGRESO.pasos;
let valorProgreso = 0;
hoja.getRange(BARRA_PROGRESO.celda)
.setFormula(BARRA_PROGRESO.formula.replace('0\\100', `0\\${BARRA_PROGRESO.pasos}`));

Y aquí se van modificando los dichosos numeritos a medida que deseamos que nuestra barra avance (el flush final para refrescar la hoja de cálculo no parece ser necesario).

procesados++;
if (procesados >= (valorProgreso + 1) * paso) {
if (paso >= 1) valorProgreso++;
else valorProgreso+= (1 / paso);           hoja.getRange(BARRA_PROGRESO.celda).setFormula(BARRA_PROGRESO.formula.replace('0\\100', `${Math.round(valorProgreso)}\\${Math.round(PASOS - valorProgreso)}`));
SpreadsheetApp.flush();

El código se complica un pelín en el ejemplo que nos ocupa dado que se ha fijado de antemano un número total de actualizaciones (BARRA_PROGRESO.pasos) de la barra y debe encajarse en ellas el tamaño del problema (numUsuariosEliminar), que en definitiva viene determinado por el número de iteraciones dentro de un bucle .forEach()

⚠️ Recuerda, escribir en o leer datos de una hoja de cálculo es una operación costosa. Desproporcionadamente costosa con respecto a la ejecución del código GAS general. Si se nos va la mano actualizando el estado de la barra de progreso podría ocurrir que nuestro script se pasase más tiempo esperando a que la hoja refrescara que haciendo lo que tiene que hacer (borrar usuarios).

Como ves, esta barra de progreso tiene una implementación sencilla, pero de resultado vistoso. Por ello me he venido arriba y he publicado un pequeño (¡pequeñísimo!) repositorio con una implementación más compacta y general. El objetivo ha sido triple:

  • Usar una biblioteca (que no librería) Apps Script que encapsule mi barra SPARKLINE para facilitar su uso en otros proyectos. Sí, vergüenza la mía, primera vez que monto una biblioteca en un proyecto GAS.
  • Echarle un ojo a las técnicas de programación orientada a objetos usando JavaScript, a las que no soy nada aficionado (hasta ahora) dado que mis proyectos son personales y de reducido tamaño. Pero una barra de progreso, que no es otra cosa que un elemento típico en una interfaz de usuario, pedía a gritos un enfoque POO.
  • Crear otro repositorio en GitHub. Cuando llegas a 100 me han dicho que hay premio 😂.

El resultado ha sido este repo GitHub (en 🇬🇧):

👉 https://github.com/pfelipm/progress-bar-sparkline 👈

Y esta hoja de cálculo, en plan demo:

👉  Progress bar SPARKLINE # demo 👈

Usando el código del repo para generar una barra de progreso SPARKLINE.

Otro día más y mejor.

 

 

Comentarios