Introdução a bookmarklets na Asana

Autoria do texto: @ShunS

Texto original disponível em inglês: Introduction to Asana bookmarklets

Marque como concluídas/por concluir todas as tarefas de um projeto, expanda e recolha todas as seções de um projeto, e faça todas essas ações em massa com apenas um clique.

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

0. O que são bookmarklets?

São trechos de código JavaScript que podemos salvar nos favoritos do navegador para executarmos com um clique. Para adicionar como favorito um trecho JavaScript, é preciso cercá-lo com javascript:(function() { e })();.

O frontend de qualquer site, inclusive da Asana, é composto principalmente por três linguagens: HTML (linguagem de marcação para montar as partes), CSS (linguagem para especificar o layout e design) e JavaScript (linguagem de programação).

Pares de tags em HTML montam unidades estruturais (nós), que podem ser operadas por JavaScript. Esta interação se chama DOM (modelo de documento por objetos, na sigla inglesa).

1. Como adicionar bookmarklets

Acesse Asana bookmarklets e arraste os links azuis até os favoritos do navegador para salvá-los.

Recomendo criar uma pasta para que os bookmarklets não ocupem muito espaço na barra de favoritos; ou, então, se você for adicioná-los diretamente à barra, recomendo que edite o nome do bookmarklet para manter apenas o emoji.

Para adicionar manualmente os bookmarklets, siga estas etapas:

  1. Guarde uma página Web qualquer como favorita no seu navegador.

  2. Ao salvá-la, clique em “Mais…”, selecione a pasta e edite o nome e o URL.

  3. Adicione o título do bookmarklet a “Nome” e insira em “URL” o código que começa com javascript:.

Se quiser criar uma seção na lista de bookmarklets, recomendo usar o Chrome Bookmarks Separator.

Para executar as ações automáticas, clique nos bookmarklets salvos numa aba aberta na Asana.

2. Exemplos de bookmarklets

Código-fonte e explicação sobre os bookmarklets listados em Asana bookmarklets.

2-1. Operações em uma tarefa

2-1-1. :speech_balloon: Clean up story (Limpar história)

Executa tanto “Expand comments” (Expandir comentários) como “Hide connected work links” (Ocultar links de trabalhos vinculados), abaixo.

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 comentários)

Clica em todos os “Mais X comentários” e “Ver mais” nas histórias da tarefa.

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 links de trabalhos vinculados)

Oculta todos os “<nome_usuário> mencionou esta tarefa em outra tarefa: <link_tarefa>” nas histórias da tarefa. Isso é útil quando uma tarefa é citada por muitas outras, o que dificulta a localização de outros comentários e anexos.

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

2-1-4. ⫚ Hide completed subtasks (Ocultar subtarefas concluídas)

Oculta todas as subtarefas concluídas no painel de detalhes da tarefa. Clique novamente no bookmarklet para voltar a exibi-las.

Se tiver muitas subtarefas, também recomendo usar a extensão para 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. Operações em uma lista de tarefas (p. ex., num projeto)

:exclamation:Observação: se houver muitas tarefas e algumas não forem exibidas na janela, role a aba da Asana até o fim para carregar todas as tarefas antes de executar estes bookmarklets.

2-2-1. :arrow_forward:︎ Toggle sections (Comutar seções)

Alterna entre expandir e recolher seções na visualização 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 (Comutar subtarefas)

Alterna entre expandir e recolher subtarefas na visualização de lista clicando no pequeno símbolo :arrow_forward:, que indica a existência de subtarefas. Se houver um grande número de subtarefas, o bookmarklet clicará em cada link “Carregar mais subtarefas”, o que demora algum tempo.

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 (Concluir todas as tarefas)

Marca todas as tarefas por fazer que estejam visíveis na visualização de lista como tarefas concluídas. Se houver tarefas de mais, o bookmarklet temporariamente ocultará todas elas e clicará em 50 tarefas de cada 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 = 'Processando';
			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 = `Processando (${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 as tarefas como não concluídas)

Marca todas as tarefas concluídas visíveis na visualização de lista como tarefas por concluir. Se houver tarefas de mais, o bookmarklet temporariamente ocultará todas elas e clicará em 50 tarefas de cada 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 = 'Processando';
			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 = `Processando (${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. Outras operações

2-3-1. ☰ Redimensionar a barra lateral

Dobra a largura da barra lateral para 480 px, deixando que os nomes longos dos projetos fiquem mais legíveis. Clique novamente no bookmarklet para voltar a largura ao tamanho padrão.

Esta ação foi inspirada no bookmarklet de @anon91858240’s compartilhado em https://forum.asana.com/t/sidebar-adjust-size-width/27297/14!

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. Desafios encontrados

3-1. Carregamento incompleto de tarefas

Quando um projeto tem muitas tarefas, a Asana faz um carregamento parcial de algumas das tarefas menos visíveis, mostrando apenas o nome para poupar recursos (por exemplo, memória e consumo de energia). Quando rolamos a barra perto das tarefas minimizadas, os dados são carregados novamente.

Exemplo: quando rolamos a tela a partir do canto inferior do projeto, as tarefas próximas ao topo são exibidas apenas com nomes por menos de um segundo e, em seguida, as demais informações são então completamente carregadas.

Ao atuar sobre todas as tarefas como na seção 2-2, os bookmarklets ocultam temporariamente todas elas para fazer a Asana pensar que há espaço suficiente para exibi-las por completo. Depois que a Asana carregar por completo todas as tarefas parciais, a automatização se inicia, e já se pode então desfazer a ocultação.

3-2. Como atenuar o uso de recursos

Se fizermos muita coisa de uma vez, sobrecarregamos o servidor da Asana. Quando usamos a API da Asana ou quando realizamos ações em massa em múltiplas tarefas selecionadas, há limites de cerca de 50 tarefas. Portanto, decidi processar 50 tarefas de cada vez nos bookmarklets para as ações de concluir e desfazer a conclusão de todas as tarefas, que apresentei mais acima.

4. Comparação com outros métodos

4-1. Comparação com outros métodos JavaScript

Vamos comparar diferentes métodos de automatização na Asana. Os métodos à esquerda são mais complicados e potentes, enquanto aqueles à direita são mais simples. Os bookmarklets são bem fáceis porque precisamos escrever apenas uma fração das linhas de código necessárias a uma extensão do Chrome. No entanto, ainda precisamos de certa quantidade de código se considerarmos os desafios discutidos na seção 3.

Métodos: OAuth Token de acesso pessoal (PAT) Extensões do Chrome Bookmarklets Barra de endereço do navegador
Exemplos: Iniciar sessão na Asana Academy com uma conta Asana Extensão oficial da Asana Listados neste tópico Obter a lista de espaços de trabalho
Autenticação: Configurar um servidor Programar um PAT de forma rígida ou salvá-lo em variáveis do ambiente Usar um cookie de navegador lembrando os dados de acesso do usuário Cookie (ditto) Cookie (ditto)
Acesso: Conceder acesso Usar PAT Instalar a partir da Chrome Web Store Adicionar aos favoritos Inserir URL na barra de endereço
Pode fazer: Operações via API Operações via API Operações via API + DOM Operações via DOM Operações GET via API (obtenção de informação)
Útil para: Ferramentas de larga escala Elaborar scripts de uso pessoal Distribuição Criação e distribuição fáceis Uso mais fácil
Não pode fazer: Operações via DOM Operações via DOM (Algumas coisas, claro) Operar em tarefas invisíveis Operações POST/PUT/DELETE (edição de informação)
Como processa grande volume de dados: Paginação Paginação Paginação Tarefas não carregada por completo Paginação (não é possível automatizar)

Exemplo: concluir todas as tarefas em um projeto

O bookmarklet é uma estratégia mais simples e mais rápida.

  • Com a API, primeiro enviamos uma solicitação GET ao endpoint /projects/<project_gid>/tasks para coletar as tarefas no projeto. Em seguida, passamos pelas tarefas e enviamos solicitações PUT para concluir cada tarefa. Precisamos fazer duas rodadas de chamadas de API para obter e atualizar as informações.

  • Com os bookmarklets, passamos diretamente pelas tarefas exibidas e clicamos nelas.

4-2. Comparando JavaScript e CSS

Podemos usar o CSS para personalizar o design da Asana. As principais diferenças do CSS são:

  • Não requer autenticação porque não interage com APIs.

  • É “sempre” possível alterar o design dos elementos HTML.

  • Não reage de forma dinâmica quando certas alterações acontecem ou um usuário faz uma operação específica.

Como usar a personalização do CSS: instale extensões do Chrome como o Stylish ou User CSS. Você pode usar as configurações do CSS criadas por outros usuários a partir de bibliotecas como userstyles.org ou, então, pode criar as suas próprias regras de CSS.

Exemplo: mudar a largura da barra lateral

  • O bookmarklet 2-3-1 mencionado acima expande ou contrai a barra lateral ao clicar no botão.

  • Se quiser que a largura da barra lateral permaneça alterada de forma permanente, então é melhor personalizar o CSS.

5. Considerações

5-1. Engenharia reversa

Os Termos de API da Asana proíbem a engenharia reversa. No entanto, o que se pode fazer é identificar os elementos DOM com o clique do botão direito do mouse e selecionar “Inspecionar”, e não acho que isso exponha os dados restritos da Asana de forma alguma.

5-2. Possíveis alterações de design

A Asana pode a qualquer momento alterar os IDs e nomes de classe que utilizamos para identificar elementos HTML na ferramenta da Asana. Alguns bookmarklets acima deixam de funcionar em tais situações. Atualizarei o código se notar alterações, e agradeço que me avisem respondendo a este tópico.

5-3. Exoneração de responsabilidade

Eu desenvolvi e testei os bookmarklets com cuidado, mas não me responsabilizo por problemas resultantes deles.

6. Agradecimento especial

O tópico original foi escrito em japonês: Asanaブックマークレットのご紹介