您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Change card colors based on Jira task priority and add an emoji if a task has been in a status for more than 3 working days, excluding specific columns
- // ==UserScript==
- // @name Jira Task Priority Colorizer
- // @namespace http://tampermonkey.net/
- // @version 0.2.3
- // @description Change card colors based on Jira task priority and add an emoji if a task has been in a status for more than 3 working days, excluding specific columns
- // @author erolatex
- // @include https://*/secure/RapidBoard.jspa*
- // @grant none
- // @license MIT
- // ==/UserScript==
- (function() {
- 'use strict';
- // Columns to exclude from emoji update
- const excludedColumns = ['ready for development', 'deployed'];
- // CSS styles to change card colors and position the emoji
- const styleContent = `
- .ghx-issue[data-priority*="P0"] {
- background-color: #FFADB0 !important;
- }
- .ghx-issue[data-priority*="P1"] {
- background-color: #FF8488 !important;
- }
- .ghx-issue[data-priority*="P2"] {
- background-color: #FFD3C6 !important;
- }
- .ghx-issue[data-priority*="P3"],
- .ghx-issue[data-priority*="P4"] {
- background-color: #FFF !important;
- }
- .stale-emoji {
- position: absolute;
- bottom: 5px;
- right: 5px;
- font-size: 14px;
- display: flex;
- align-items: center;
- background-color: #d2b48c;
- border-radius: 3px;
- padding: 2px 4px;
- font-weight: bold;
- }
- .stale-emoji span {
- margin-left: 5px;
- font-size: 12px;
- color: #000;
- }
- .ghx-issue {
- position: relative;
- }
- .column-badge.bad-count {
- margin-left: 5px;
- background-color: #d2b48c;
- border-radius: 3px;
- padding: 0 4px;
- font-size: 11px;
- color: #333;
- font-weight: normal;
- text-align: center;
- display: inline-block;
- vertical-align: middle;
- line-height: 20px;
- }
- .ghx-limits {
- display: flex;
- align-items: center;
- gap: 5px;
- }
- `;
- // Inject CSS styles into the page
- const styleElement = document.createElement('style');
- styleElement.type = 'text/css';
- styleElement.appendChild(document.createTextNode(styleContent));
- document.head.appendChild(styleElement);
- /**
- * Calculates the number of working days between two dates.
- * Excludes Saturdays and Sundays.
- * @param {Date} startDate - The start date.
- * @param {Date} endDate - The end date.
- * @returns {number} - The number of working days.
- */
- function calculateWorkingDays(startDate, endDate) {
- let count = 0;
- let currentDate = new Date(startDate);
- // Set time to midnight to avoid timezone issues
- currentDate.setHours(0, 0, 0, 0);
- endDate = new Date(endDate);
- endDate.setHours(0, 0, 0, 0);
- while (currentDate <= endDate) {
- const dayOfWeek = currentDate.getDay();
- if (dayOfWeek !== 0 && dayOfWeek !== 6) { // Exclude Sunday (0) and Saturday (6)
- count++;
- }
- currentDate.setDate(currentDate.getDate() + 1);
- }
- return count;
- }
- /**
- * Calculates the number of working days based on the total days in the column.
- * Assumes days are counted backward from the current date.
- * @param {number} daysInColumn - Total number of days in the column.
- * @returns {number} - The number of working days.
- */
- function calculateWorkingDaysFromDaysInColumn(daysInColumn) {
- let workingDays = 0;
- let currentDate = new Date();
- // Set time to midnight to avoid timezone issues
- currentDate.setHours(0, 0, 0, 0);
- for (let i = 0; i < daysInColumn; i++) {
- const dayOfWeek = currentDate.getDay();
- if (dayOfWeek !== 0 && dayOfWeek !== 6) { // Exclude Sunday (0) and Saturday (6)
- workingDays++;
- }
- // Move to the previous day
- currentDate.setDate(currentDate.getDate() - 1);
- }
- return workingDays;
- }
- /**
- * Updates the priorities of the cards and adds/removes the emoji based on working days.
- */
- function updateCardPriorities() {
- // Disconnect the observer to prevent it from reacting to our changes
- observer.disconnect();
- let cards = document.querySelectorAll('.ghx-issue');
- let poopCountPerColumn = {};
- cards.forEach(card => {
- // Update priority attribute
- let priorityElement = card.querySelector('.ghx-priority');
- if (priorityElement) {
- let priority = priorityElement.getAttribute('title') || priorityElement.getAttribute('aria-label') || priorityElement.innerText || priorityElement.textContent;
- if (priority) {
- card.setAttribute('data-priority', priority);
- }
- }
- // Initialize working days count
- let workingDays = 0;
- // Attempt to get the start date from a data attribute (e.g., data-start-date)
- let startDateAttr = card.getAttribute('data-start-date'); // Example: '2024-04-25'
- if (startDateAttr) {
- let startDate = new Date(startDateAttr);
- let today = new Date();
- workingDays = calculateWorkingDays(startDate, today);
- } else {
- // If start date is not available, use daysInColumn
- let daysElement = card.querySelector('.ghx-days');
- if (daysElement) {
- let title = daysElement.getAttribute('title');
- if (title) {
- let daysMatch = title.match(/(\d+)\s+days?/);
- if (daysMatch && daysMatch[1]) {
- let daysInColumn = parseInt(daysMatch[1], 10);
- workingDays = calculateWorkingDaysFromDaysInColumn(daysInColumn);
- }
- }
- }
- }
- // Check and update the emoji 💩
- let columnElement = card.closest('.ghx-column');
- if (workingDays > 3 && columnElement) {
- let columnTitle = columnElement.textContent.trim().toLowerCase();
- if (!excludedColumns.some(col => columnTitle.includes(col))) {
- let existingEmoji = card.querySelector('.stale-emoji');
- if (!existingEmoji) {
- let emojiContainer = document.createElement('div');
- emojiContainer.className = 'stale-emoji';
- let emojiElement = document.createElement('span');
- emojiElement.textContent = '💩';
- let daysText = document.createElement('span');
- daysText.textContent = `${workingDays} d`;
- emojiContainer.appendChild(emojiElement);
- emojiContainer.appendChild(daysText);
- card.appendChild(emojiContainer);
- } else {
- let daysText = existingEmoji.querySelector('span:last-child');
- daysText.textContent = `${workingDays} d`;
- }
- // Count poop emoji per column
- let columnId = columnElement.getAttribute('data-column-id') || columnElement.getAttribute('data-id');
- if (columnId) {
- if (!poopCountPerColumn[columnId]) {
- poopCountPerColumn[columnId] = 0;
- }
- poopCountPerColumn[columnId]++;
- }
- }
- } else {
- let existingEmoji = card.querySelector('.stale-emoji');
- if (existingEmoji) {
- existingEmoji.remove();
- }
- }
- });
- // Update poop count badges for each column
- Object.keys(poopCountPerColumn).forEach(columnId => {
- let columnHeader = document.querySelector(`.ghx-column[data-id="${columnId}"]`);
- if (columnHeader) {
- let limitsContainer = columnHeader.querySelector('.ghx-column-header .ghx-limits');
- let existingBadge = columnHeader.querySelector('.column-badge.bad-count');
- if (!existingBadge) {
- // Change from 'div' to 'span' and adjust classes
- existingBadge = document.createElement('span');
- existingBadge.className = 'ghx-constraint aui-lozenge aui-lozenge-subtle column-badge bad-count';
- }
- existingBadge.textContent = `💩 ${poopCountPerColumn[columnId]}`;
- existingBadge.style.display = 'inline-block';
- if (limitsContainer) {
- let maxBadge = limitsContainer.querySelector('.ghx-constraint.ghx-busted-max');
- if (maxBadge) {
- limitsContainer.insertBefore(existingBadge, maxBadge);
- } else {
- limitsContainer.appendChild(existingBadge);
- }
- } else {
- // If limitsContainer doesn't exist, create it
- limitsContainer = document.createElement('div');
- limitsContainer.className = 'ghx-limits';
- limitsContainer.appendChild(existingBadge);
- let headerContent = columnHeader.querySelector('.ghx-column-header-content');
- if (headerContent) {
- headerContent.appendChild(limitsContainer);
- }
- }
- }
- });
- // Reconnect the observer after making changes
- observer.observe(document.body, { childList: true, subtree: true });
- }
- // MutationObserver to watch for changes in the DOM and update priorities accordingly
- const observer = new MutationObserver(() => {
- updateCardPriorities();
- });
- // Start observing the body
- observer.observe(document.body, { childList: true, subtree: true });
- // Update priorities when the page loads
- window.addEventListener('load', function() {
- updateCardPriorities();
- });
- // Periodically update priorities every 5 seconds
- setInterval(updateCardPriorities, 5000);
- })();