Introducción a los bookmarklets de Asana

Autor de la publicación: @ShunS

Publicación original disponible en inglés: Introduction to Asana bookmarklets

Completa acciones masivas con solo un clic, por ejemplo, marcar todas las tareas como finalizadas o sin finalizar, expandir o contraer todas las secciones de un proyecto y expandir todos los comentarios de una tarea.

Screen Recording 2022-07-25 at 16.21.00 (1)

0. ¿Qué es un bookmarklet?

Un bookmarklet es un fragmento de código JavaScript que se agrega como marcador del navegador para ejecutarse al hacer clic. Podemos marcar fragmentos de JavaScript usando javascript:(function() { y })();.

La front-end de cualquier sitio web, incluido Asana, se compone principalmente de tres lenguajes: HTML (un lenguaje de marcado para crear las partes de una página web), CSS (un lenguaje para especificar el formato y el diseño) y JavaScript (un lenguaje de programación).

Los pares de etiquetas en HTML crean unidades estructuradas (nodos), que JavaScript puede ejecutar. Esta interacción se llama DOM (modelo de objeto de documento).

1. Cómo agregar bookmarklets

Accede a Asana bookmarklets y arrastra los enlaces azules a la barra de marcadores del navegador para agregarlos.

Recomiendo crear una carpeta para que los marcadores no ocupen mucho espacio en la barra de marcadores. O, si los agregas directamente a la barra de marcadores, edita el nombre para conservar solo el emoji.

Para agregar los bookmarklets de forma manual, sigue los pasos a continuación:

  1. Marca una página web cualquiera como favorita.

  2. Luego de agregarla, haz clic en “Más”, selecciona la carpeta y edita el nombre y la URL.

  3. Agrega los títulos del bookmarklet en “Nombre” y el código que comienza con javascript:
    en “URL”.

Si quieres crear una sección en la lista de bookmarklets, usa el separador Chrome Bookmarks Separator.

Puedes hacer clic en los bookmarklets guardados en una pestaña de Asana para ejecutar las acciones automáticas.

2. Ejemplos de bookmarklets

El código fuente y la explicación de los bookmarklets enumerados en Asana bookmarklets.

2-1. Operaciones en una tarea

2-1-1. :speech_balloon: Clean up story (Limpieza del historial)

Realiza las dos opciones a continuación: “Expand comments” (Expandir comentarios) y “Hide connected work links” (Ocultar enlaces de trabajo vinculados).

javascript:(function() {
	const expandLink = document.querySelector('.TaskStoryFeed-expandLink');
	if (expandLink && expandLink.textContent.match(/\d/)) expandLink.click();
	document.querySelectorAll('.TruncatedRichText-expand').forEach(link => link.click());
	document.querySelectorAll('.TaskStoryFeed-expandMiniStoriesLink').forEach(link => link.click());
	document.querySelectorAll('.BacklinkMiniStory').forEach(line => {line.parentNode.style.display = 'none';});
})();

2-1-2. :arrow_up_down: Expand comments (Expandir comentarios)

Hace clic en “X comentarios más“ y en “Ver más” en el historial de la tarea.

javascript:(function() {
	const expandLink = document.querySelector('.TaskStoryFeed-expandLink');
	if (expandLink && expandLink.textContent.match(/\d/)) expandLink.click();
	document.querySelectorAll('.TruncatedRichText-expand').forEach(link => link.click());
	document.querySelectorAll('.TaskStoryFeed-expandMiniStoriesLink').forEach(link => link.click());
})();

2-1-3. ↔ Hide connected work links (Ocultar enlaces de trabajo vinculados)

Oculta todos los “<user_name> mencionó esta tarea en otra tarea: <task_link>” en el historial de la tarea. Esta opción es útil cuando una tarea se menciona en demasiadas tareas, lo que dificulta encontrar otros comentarios y archivos adjuntos.

javascript:(function() {
	document.querySelectorAll('.BacklinkMiniStory').forEach(line => {line.parentNode.style.display = 'none';});
})();

2-1-4. ⫚ Hide completed subtasks (Ocultar subtareas finalizadas)

Oculta todas las subtareas finalizadas en el panel de detalles de la tarea. Para volver a mostrarlas, haz clic en el bookmarklet.

Si tienes muchas subtareas, también te recomiendo usar la extensión de Chrome Asana Load More.

javascript:(function() {
	const completedSubtaskRows = document.querySelectorAll('.SubtaskTaskRow--completed');
	completedSubtaskRows.forEach(row => {
		row.parentNode.parentNode.style.display = row.parentNode.parentNode.style.display? '': 'none';
	});
})();

2-2. Operaciones en una lista de tareas (p. ej., en un proyecto)

:exclamation:Nota: Si tienes muchas tareas y algunas no se muestran en la ventana, simplemente desplázate hacia abajo en la pestaña de Asana para cargar todas las tareas antes de ejecutar
estos bookmarklets.

2-2-1. :arrow_forward:︎ Toggle sections (Activar/desactivar las secciones)

Activa la opción para expandir o contraer las secciones en la vista de Lista.

javascript:(function() {
	const firstButtonIcon = document.querySelector('.TaskGroupHeader-toggleButton .Icon');
	if (!firstButtonIcon) return;
	const firstTriangleClassName = firstButtonIcon.classList.contains('DownTriangleIcon')? 'DownTriangleIcon': 'RightTriangleIcon';
	document.querySelectorAll(`.TaskGroupHeader-toggleButton .${firstTriangleClassName}`).forEach(buttonIcon => buttonIcon.parentNode.click());
})();

2-2-2. ▷ Toggle subtasks (Activar/desactivar subtareas)

Activa la opción para expandir o contraer las subtareas en la vista de Lista, al hacer clic en los pequeños símbolos :arrow_forward: que indican la presencia de subtareas. Si hay demasiadas subtareas, el bookmarklet hará clic en cada enlace “Cargar más subtareas”, lo que lleva un poco más de tiempo.

javascript:(function() {
	const firstSubtaskButton = document.querySelector('.ProjectSpreadsheetGridRow-subtaskToggleButton');
	const firstTriangleClassName = firstSubtaskButton.firstElementChild.classList.contains('DownTriangleIcon')? 'DownTriangleIcon': 'RightTriangleIcon';
 
	const taskPlaceholderHTMLCollection = document.getElementsByClassName('SpreadsheetTaskRowScrollPlaceholder');
	const taskGroup = document.querySelector('.TaskGroup');
	const buttonAtTheBottom = document.querySelector('.SpreadsheetPotGridContents-addSectionButton');
	setTimeout(function () {if (buttonAtTheBottom) buttonAtTheBottom.scrollIntoView();}, 30);
	setTimeout(function () {taskGroup.style.display = 'none';}, 60);
 
	let monitorTaskStructure = setInterval(() => {
		if (taskPlaceholderHTMLCollection.length == 0) {
			document.querySelectorAll('.ProjectSpreadsheetGridRow-subtaskToggleButton').forEach(function (buttonIcon) {
				if (buttonIcon.firstElementChild.classList.contains(firstTriangleClassName)) buttonIcon.click();
			});
			taskGroup.style.display = '';
			clearInterval(monitorTaskStructure);
			const loadMoreLinkHTMLCollection = document.getElementsByClassName('SpreadsheetTaskList-showMoreLink');
			setTimeout(() => {
				let clickingLoadMoreLinks = setInterval(function() {
					if (!loadMoreLinkHTMLCollection.length) {
						clearInterval(clickingLoadMoreLinks);
					} else {
						loadMoreLinkHTMLCollection[0].scrollIntoView();
						loadMoreLinkHTMLCollection[0].click();
					}
				}, 100);
			}, 200);
		}
	}, 100);
})();

2-2-3. :white_check_mark: Complete all tasks (Marcar todas las tareas como finalizadas)

Marca todas las tareas sin finalizar visibles en la vista de Lista como finalizadas. Si hay demasiadas tareas, el bookmarklet ocultará temporalmente todas las tareas y hará clic en 50 tareas a la vez.

javascript:(function() {
	const taskPlaceholderHTMLCollection = document.getElementsByClassName('SpreadsheetTaskRowScrollPlaceholder');
	if (!taskPlaceholderHTMLCollection.length) {
		document.querySelectorAll('.TaskRowCompletionStatus-taskCompletionIcon--incomplete').forEach(incompleteIcon => incompleteIcon.parentNode.click());
	} else {
		const taskGroup = document.querySelector('.TaskGroup');
		const buttonAtTheBottom = document.querySelector('.SpreadsheetPotGridContents-addSectionButton');
		setTimeout(function () {if (buttonAtTheBottom) buttonAtTheBottom.scrollIntoView();}, 30);
		setTimeout(function () {
			taskGroup.style.display = 'none';
			const progressIndicator = document.createElement('span');
			progressIndicator.setAttribute('id', 'progressIndicator');
			progressIndicator.textContent = 'Procesando';
			taskGroup.parentNode.appendChild(progressIndicator);
		}, 60);
 
		let monitorTaskStructure = setInterval(() => {
			if (taskPlaceholderHTMLCollection.length == 0) {
				clearInterval(monitorTaskStructure);
				const progressIndicator = document.querySelector('#progressIndicator');
				const allTasks = Array.from(document.querySelectorAll('.TaskRowCompletionStatus-taskCompletionIcon--incomplete'));
				const numProcesses = Math.floor(allTasks.length / 50) + 1;
				let counter = 0;
				let loopTasks = setInterval(() => {				
					progressIndicator.textContent = `Procesando (${counter}/${numProcesses})`;
 
					for (let i = 50 * counter; i < Math.min(allTasks.length, 50 * (counter + 1)); i++) {
						allTasks[i].parentNode.click();
						if (i == allTasks.length - 1) {
							clearInterval(loopTasks);
							progressIndicator.remove();
							taskGroup.style.display = '';
						}
					}
					counter += 1;
				}, 500);
			}
		}, 100);
	}
})();

2-2-4. :ballot_box_with_check: Mark all tasks incomplete (Marcar todas las tareas como sin finalizar)

Marca todas las tareas finalizadas visibles en la vista de Lista como sin finalizar. Si hay demasiadas tareas, el bookmarklet ocultará temporalmente todas las tareas y hará clic en 50 tareas a la vez.

javascript:(function() {
	const taskPlaceholderHTMLCollection = document.getElementsByClassName('SpreadsheetTaskRowScrollPlaceholder');
	if (!taskPlaceholderHTMLCollection.length) {
		document.querySelectorAll('.TaskRowCompletionStatus-taskCompletionIcon--complete').forEach(incompleteIcon => incompleteIcon.parentNode.click());
	} else {
		const taskGroup = document.querySelector('.TaskGroup');
		const buttonAtTheBottom = document.querySelector('.SpreadsheetPotGridContents-addSectionButton');
		setTimeout(function () {if (buttonAtTheBottom) buttonAtTheBottom.scrollIntoView();}, 30);
		setTimeout(function () {
			taskGroup.style.display = 'none';
			const progressIndicator = document.createElement('span');
			progressIndicator.setAttribute('id', 'progressIndicator');
			progressIndicator.textContent = 'Procesando';
			taskGroup.parentNode.appendChild(progressIndicator);
		}, 60);
 
		let monitorTaskStructure = setInterval(() => {
			if (taskPlaceholderHTMLCollection.length == 0) {
				clearInterval(monitorTaskStructure);
				const progressIndicator = document.querySelector('#progressIndicator');
				const allTasks = Array.from(document.querySelectorAll('.TaskRowCompletionStatus-taskCompletionIcon--complete'));
				const numProcesses = Math.floor(allTasks.length / 50) + 1;
				let counter = 0;
				let loopTasks = setInterval(() => {				
					progressIndicator.textContent = `Procesando (${counter}/${numProcesses})`;
					for (let i = 50 * counter; i < Math.min(allTasks.length, 50 * (counter + 1)); i++) {
						allTasks[i].parentNode.click();
						if (i == allTasks.length - 1) {
							clearInterval(loopTasks);
							progressIndicator.remove();
							taskGroup.style.display = '';
						}
					}
					counter += 1;
				}, 500);
			}
		}, 100);
	}
})();

2-3. Otras operaciones

2-3-1. ☰ Resize sidebar (Cambiar el tamaño del menú lateral)

Duplica el ancho del menú lateral a 480 píxeles para que los nombres extensos de los proyectos sean más legibles. Vuelve a hacer clic en el bookmarklet para volver al ancho predeterminado.

Esta acción se inspiró en el bookmarklet de @Ian_Houser, que compartió en SideBar Adjust Size / Width - #14 by Ian_Houser.

javascript:(function() {
	const widerWidth = '480px';
	const asanaSidebar = document.querySelector('.AsanaMain-sidebar');
	if (!asanaSidebar) return; 
 
	var newStyle = document.querySelector('#asanaSidebarBookmarkletStyle');
	if (!newStyle) {	
		newStyle = document.createElement('style');
		newStyle.id = 'asanaSidebarBookmarkletStyle';
		document.head.appendChild(newStyle);
	}
 
	if (asanaSidebar.style.width == widerWidth) {
		asanaSidebar.style.width = '240px';
		newStyle.innerText = '';
	} else {
		asanaSidebar.style.width = widerWidth;
		newStyle.innerText = `.AsanaMain-sidebar.AsanaMain-sidebar--isCollapsed {margin-left: -${widerWidth} !important}`;
	}
})();
 

3. Desafíos

3-1. Tareas de marcador de posición

Cuando un proyecto tiene muchas tareas, Asana convierte algunas fuera de la vista en marcadores de posición simplificados y muestra solo los nombres de estas tareas para ahorrar recursos (por ejemplo, memoria y energía). Cuando nos desplazamos cerca de estas tareas de marcador de posición, los datos se cargan nuevamente.

Por ejemplo, cuando nos desplazamos desde la parte inferior del proyecto, para las tareas que se encuentran en la parte superior se muestran solo los nombres durante un breve período de tiempo. Menos de un segundo después, las tareas se muestran por completo.

Si queremos incluir todas las tareas como en la sección 2-2, ocultaremos temporalmente todas las tareas para que Asana crea que hay suficiente espacio para mostrarlas todas. Después de que Asana vuelve a mostrar todas las tareas de marcador de posición de forma completa, comienza la automatización y podemos dejar de usar el modo oculto.

3-2. Cómo mejorar los tiempos de carga

Si ejecutamos demasiadas acciones a la vez, sobrecargamos el servidor de Asana. Existe un límite de aproximadamente 50 tareas cuando usamos la API de Asana o cuando realizamos acciones masivas en varias tareas seleccionadas a la vez. Por lo tanto, decidí procesar 50 tareas a la vez en los bookmarklets para marcar todas las tareas como finalizadas o como sin finalizar mencionados anteriormente.

4. Comparación con otros métodos

4-1. Comparación con otros métodos de JavaScript

Comparemos diferentes métodos para automatizar determinadas acciones con Asana. Los métodos de la izquierda son más complejos y potentes, mientras que los de la derecha son más simples. Usar bookmarklets es un método bastante sencillo, ya que solo necesitamos escribir 1/100 o 1/10 líneas de código en comparación con el desarrollo de una extensión de Chrome.
Sin embargo, aún necesitamos escribir algunas líneas de código si consideramos los desafíos descritos en la sección 3.

Métodos OAuth Token de acceso personal (PAT) Extensiones de Chrome Bookmarklets Barra de direcciones del navegador
Ejemplos Iniciar sesión en la Academia Asana con una cuenta de Asana Extensión oficial de Asana En este hilo Obtener la lista de espacios de trabajo
Autenticación Configurar un servidor Codificar un PAT o almacenarlo en variables del entorno Usar cookies del navegador para mostrar el inicio de sesión del usuario Cookie (ídem) Cookie (ídem)
Acceso Otorgar acceso Usar PAT Instalar desde la tienda Chrome Web Store Añadir a favoritos Ingresar la URL en la barra de direcciones
Se puede realizar Operaciones a través de la API Operaciones a través de la API Operaciones a través de API+DOM Operaciones a través de DOM Operaciones GET a través de la API (obtener información)
Ideal para Herramientas a gran escala Escritura de secuencias de comandos para uso personal Distribución Fácil creación y distribución Uso más simple
No se puede realizar Operaciones a través de DOM Operaciones a través de DOM Algunas acciones, por supuesto Ejecutar tareas invisibles Operaciones POST/PUT/DELETE (editar información)
Gestión de grandes volúmenes de datos Paginación Paginación Paginación Tareas de marcador de posición Paginación (no se puede automatizar)

Ejemplo: Finalizar todas las tareas en un proyecto

Usar bookmarklets es un enfoque más simple y rápido.

  • Con la API, primero enviamos una solicitud GET al punto de conexión /projects/<project_gid>/tasks para buscar tareas en el proyecto. Luego, revisamos las tareas y enviamos solicitudes PUT para marcar cada tarea como finalizada. Necesitamos hacer dos rondas de llamadas a la API para obtener y actualizar la información.

  • Con los bookmarklets, recorremos directamente las tareas mostradas y hacemos clic en cada una de ellas.

4-2. Diferencias entre JavaScript y CSS

Podemos usar CSS para personalizar el diseño de Asana. Las principales diferencias de CSS
son estas:

  • No se requiere autenticación porque no interactúa con la API

  • “Siempre” se puede cambiar el diseño de los elementos HTML

  • No puede responder de forma dinámica cuando ocurren ciertos cambios o cuando el usuario realiza una operación específica

Cómo personalizar CSS: instala extensiones de Chrome como Stylish o User CSS. Puedes usar configuraciones de CSS personalizadas creadas por otros usuarios de bibliotecas como userstyles.org o crear tus propias reglas de CSS.

Ejemplo: Cambiar el ancho del menú lateral

  • Los bookmarklets 2-3-1 mencionados anteriormente expanden y contraen el menú lateral cuando se hace clic en el botón

  • Si quieres que el menú lateral tenga siempre el mismo ancho, es mejor personalizar el CSS

5. Consideraciones

5-1. Ingeniería inversa

Los Términos del uso de API de Asana prohíben la ingeniería inversa. Sin embargo, generalmente es posible identificar los elementos DOM al hacer clic derecho y seleccionar “Inspeccionar” y, así, no creo que puedas acceder a los secretos de Asana.

5-2. Posibles cambios en el diseño

Asana puede cambiar los ID y los nombres de las clases que se usan para identificar los elementos HTML en la herramienta de Asana en cualquier momento. Si eso sucede, algunos bookmarklets anteriores dejarán de funcionar. Actualizaré el código si noto los cambios, pero te agradecería que, si encuentras algún cambio, me lo hagas saber en la sección de comentarios de este hilo.

5-3. Exención de responsabilidades

He desarrollado y probado los bookmarklets exhaustivamente, pero no me puedo responsabilizar por los problemas que puedan surgir.

6. Agradecimiento especial

Esta publicación se escribió originalmente en japonés: Asanaブックマークレットのご紹介

1 Like