Sign-Off Trello

Create a Sign-Off file from a selected list

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name           Sign-Off Trello
// @namespace      https://openuserjs.org/users/clemente
// @match          https://trello.com/*
// @version        1.0
// @grant          GM_xmlhttpRequest
// @grant          GM_download
// @connect        trello.com
// @author         clemente
// @license        MIT
// @description    Create a Sign-Off file from a selected list
// @icon           https://images.emojiterra.com/mozilla/128px/1f4c4.png
// @inject-into    content
// @run-at         document-idle
// @homepageURL    https://openuserjs.org/scripts/clemente/Sign-Off_Trello
// @supportURL     https://openuserjs.org/scripts/clemente/Sign-Off_Trello/issues
// @noframes
// ==/UserScript==

/* Logic to get the validations from a list */

function gm_fetch(url) {
  return new Promise((resolve, reject) => {
    GM_xmlhttpRequest({
      method: "GET",
      url: url,
      onload: function({ status, responseText }) {
        if (status < 200 && status >= 300) return reject();
        resolve(JSON.parse(responseText));
      },
      onerror: function() { reject(); },
    });
  });
}

async function getBoardId() {
  const url = `${document.URL}.json?fields=id`;
  const boardInformation = await gm_fetch(url);
  return boardInformation.id;
}

async function getBoardsLists(boardId) {
  const url = `https://trello.com/1/boards/${boardId}/lists`;
  const lists = await gm_fetch(url);
  return lists;
}

async function getListCards(listId) {
  const url = `https://trello.com/1/lists/${listId}/cards?fields=id`;
  const cards = await gm_fetch(url);
  return cards.map(card => card.id);
}

async function getCardLastAction(cardId) {
  const url = `https://trello.com/1/cards/${cardId}?actions=updateCard%3AidList&actions_display=true&action_memberCreator_fields=fullNam%2Cusername&actions_limit=1&fields=email`;
  const card = await gm_fetch(url);
  return card.actions[0];
}

async function getInformation(cardId) {
  const action = await getCardLastAction(cardId);
  const date = action.date;
  const validator = action.display.entities.memberCreator.text;
  const shortLink = action.display.entities.card.shortLink;
  const name = action.display.entities.card.text;
  const id = action.data.card.idShort;
  return { date, validator, shortLink, name, id };
}

async function getListValidations(listId) {
  const cards = await getListCards(listId);
  const validations = await Promise.all(cards.map(getInformation));
  return validations;
}

function formatValidations(validations) {
  const headers = "id,nom du ticket,lien,date de validation,validateur\n";
  const content = validations
    .map(({ date, validator, shortLink, name, id }) => `${id},"${name}",https://trello.com/c/${shortLink},${date},"${validator}"`)
    .join('\n');
  return headers + content;
}

async function downloadSignOff(formattedValidations) {
  const content = 'data:application/csv;charset=utf-8,' + encodeURIComponent(formattedValidations);
  GM_download({ url: content, name: 'sign-off.csv' });
}

async function setValidationsInClipboard(listName) {
  try { 
    const boardId = await getBoardId();
    const lists = await getBoardsLists(boardId);
    const listId = lists.find(list => list.name === listName).id;
    const validations = await getListValidations(listId);
    const formattedValidations = formatValidations(validations);
    downloadSignOff(formattedValidations);
  } catch (e) {
    console.log(e);
    alert('Erreur lors de la création du rapport. Veuillez regarder les logs.');
  }
}

/* Logic to add a button to the list options */


function addReportValidationButton(popoverNode, listName) {
  const reportButton = document.createElement('li');
  const reportButtonLink = document.createElement('a');
  reportButtonLink.textContent = "Créer le rapport de Sign-Off";
  reportButtonLink.href = '#';
  reportButton.append(reportButtonLink);
  reportButton.onclick = () => {
    setValidationsInClipboard(listName);
    popoverNode.querySelector('.icon-close').click();
  };
  popoverNode.querySelector('.pop-over-list').append(reportButton);
}

function onPopoverShown(mutations, observer, listName) {
  mutations.forEach(mutation => {
    if (mutation.addedNodes.length > 0) {
      mutation.addedNodes.forEach(node => {
        addReportValidationButton(node, listName);
        observer.disconnect();
      });
    }
  });
}

function onListOptionsClick(event) {
  const listName = event.target.parentNode.parentNode.querySelector('.js-list-name-input').textContent;
  // Create a mutation observer instead of adding directly the button because the popover is mounted a bit after the click
  const popoverObserver = new MutationObserver((mutations, observer) => onPopoverShown(mutations, observer, listName));
  popoverObserver.observe(document.querySelector('.pop-over'), { childList: true });
}

function initListOptionsWatch() {
  document.querySelectorAll('.js-open-list-menu').forEach(node => {
    node.removeEventListener('click', onListOptionsClick); // Remove previous event listener if already set
    node.addEventListener('click', onListOptionsClick);
  });
}

// Wait 30 secondes for the board to load
setTimeout(initListOptionsWatch, 30000);