Created an internal plugin using tampermonkey to see the latest comment in a jira ticket within a list view.

// ==UserScript==
// @name         Jira Latest Comment Hover
// @namespace    [jiraurl]
// @version      1.0
// @description  Show latest comment when hovering over a Jira issue in a queue
// @author       You
// @match        [jiraurl]/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      [jiraurl]
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';

    // Fetch the stored email and API key
    const EMAIL = "[email]";
    const API_TOKEN = "[token]";
    const JIRA_BASE_URL = "[jiraurl]";

    // Add tooltip styles
    GM_addStyle(`
      .jira-tooltip {
          position: absolute;
          background: #2E7D32; /* Green color */
          color: #ffffff; /* White text */
          padding: 5px 10px;
          border-radius: 4px;
          font-size: 12px;
          max-width: 300px;
          word-wrap: break-word;
          box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
          z-index: 9999;
          display: none;
      }
    `);

    let tooltip = document.createElement("div");
    tooltip.className = "jira-tooltip";
    document.body.appendChild(tooltip);

    // Simple cache to store comments
    const commentCache = new Map();

    function fetchLatestComment(issueKey, callback) {
        // Check cache first
        if (commentCache.has(issueKey)) {
            callback(commentCache.get(issueKey));
            return;
        }

        const authHeader = "Basic " + btoa(`${EMAIL}:${API_TOKEN}`);

        GM_xmlhttpRequest({
            method: "GET",
            url: `${JIRA_BASE_URL}/rest/api/3/issue/${issueKey}?fields=comment`,
            headers: {
                "Accept": "application/json",
                "Authorization": authHeader
            },
            onload: function(response) {
                try {
                    if (response.status === 200) {
                        const issueData = JSON.parse(response.responseText);
                        const comments = issueData.fields.comment?.comments || [];
                        let latestComment = "No comments available";

                        if (comments.length > 0) {
                            const lastComment = comments[comments.length - 1];
                            if (typeof lastComment.body === 'string') {
                                latestComment = lastComment.body;
                            } else if (lastComment.body.content) {
                                latestComment = lastComment.body.content
                                    .map(block => {
                                        if (block.type === 'paragraph') {
                                            return block.content
                                                .map(text => text.text || '')
                                                .join('');
                                        }
                                        return '';
                                    })
                                    .join('\n')
                                    .trim();
                            }
                        }

                        // Cache the result
                        commentCache.set(issueKey, latestComment);
                        callback(latestComment);
                    } else {
                        const errorMsg = `Error ${response.status}: Unable to load comment`;
                        commentCache.set(issueKey, errorMsg);
                        callback(errorMsg);
                    }
                } catch (error) {
                    console.error("Error processing response:", error);
                    callback("Error loading comment");
                }
            },
            onerror: function(error) {
                console.error("Request failed:", error);
                callback("Failed to fetch comment");
            }
        });
    }

    function showTooltip(event, text) {
        const maxWidth = 300;
        const padding = 10;

        tooltip.textContent = text;
        tooltip.style.display = "block";

        let left = event.pageX + padding;
        let top = event.pageY + padding;

        // Adjust position if tooltip would go off screen
        if (left + maxWidth > window.innerWidth) {
            left = window.innerWidth - maxWidth - padding;
        }

        if (top + tooltip.offsetHeight > window.innerHeight) {
            top = event.pageY - tooltip.offsetHeight - padding;
        }

        tooltip.style.left = `${left}px`;
        tooltip.style.top = `${top}px`;
    }

    function hideTooltip() {
        tooltip.style.display = "none";
    }

    // Initialize the hover functionality
    function init() {
        // Add hover listeners
        document.addEventListener("mouseover", (event) => {
            const issueElement = event.target.closest('[data-issue-key]');
            if (issueElement) {
                const issueKey = issueElement.getAttribute("data-issue-key");
                fetchLatestComment(issueKey, (comment) => {
                    showTooltip(event, comment);
                });
            }
        });

        document.addEventListener("mouseout", (event) => {
            if (event.target.closest('[data-issue-key]')) {
                hideTooltip();
            }
        });
    }

    // Wait for page to be ready
    if (document.readyState === "complete") {
        init();
    } else {
        window.addEventListener("load", init);
    }
})();

Leave a Reply