Jira issue page updated notification

Give Jira issue page an update button, in the top navigation bar, when the issue is updated while the page is shown. Also update the title.

Versión del día 24/03/2023. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name        Jira issue page updated notification
// @description Give Jira issue page an update button, in the top navigation bar, when the issue is updated while the page is shown.  Also update the title.
// @author      Marnix Klooster <[email protected]>
// @copyright   public domain
// @version     0.4
// @include     /^https?://(jira\.[^/]*|[^/]*\.atlassian\.net)/browse//
// @require     https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js
// @grant       none
// @namespace https://greasyfork.org/users/1047370
// ==/UserScript==

/// TODO list:
///  * Also handle pages like https://jira.example.org/projects/PROJ/issues/PROJ-64201
///  * Better error handling around unexpected URLs; stop retrying if no issue name can be found?
///  * Robustness in case `aui-nav` element does not exist.
///  * Perhaps: Wait longer after an error response?
///  * Perhaps: If another update is done, update the time on the button.

"use strict";

/// configuration settings
/// (also look at @include and @match in the manifest above,
/// which you can usually override in your userscript browser extension configuration)
var timeBetweenChecksInSeconds = 10;
var issueUpdateDelayInSeconds = 2; // to prevent the update button after you edit the issue page yourself

/// From https://stackoverflow.com/a/35385518/223837
function htmlToElement(html) {
    var template = document.createElement('template');
    html = html.trim(); // Never return a text node of whitespace as the result
    template.innerHTML = html;
    return template.content.firstChild;
}

(function () {

    var pageLastUpdatedInMillis = Date.now(); // `undefined` means that we are not checking for issue updates

    function regularlyCheckForUpdates() {
        if (!pageLastUpdatedInMillis) {
            console.log(`ERROR: internal inconsistency, we don't know when this page was last updated...`);
            return;
        }
        var issueNumber = new URL(window.location.href).pathname.split('/')[2];
        console.log(`Checking whether issue ${issueNumber} has been recently updated`);
        $.ajax({url:`/rest/api/latest/issue/${issueNumber}?fields=updated`, type:"GET", dataType:"json", contenType: "application/json",
                success: function(response) {
                    var issueLastUpdated = luxon.DateTime.fromISO(response.fields.updated);
                    console.log(`Issue ${issueNumber} was updated ${(Date.now() - issueLastUpdated.toMillis())/1000} seconds ago`);
                    console.log(`This page was updated ${(Date.now() - pageLastUpdatedInMillis)/1000} seconds ago`);
                    if (pageLastUpdatedInMillis + issueUpdateDelayInSeconds*1000 < issueLastUpdated.toMillis()) {
                        // issue was updated after the page was loaded
                        var issueLastUpdatedText = issueLastUpdated.toLocaleString(luxon.DateTime.TIME_WITH_SHORT_OFFSET, {});
                        console.log(`Issue updated after last page update, just now at ${issueLastUpdatedText}: showing update button and updating title`);

                        document.title = `\u21BB ${document.title}`;

                        // We put the button as the last in the <ul class="aui-nav"> top navigation bar.
                        // (The button is the same as the 'Create' button; `href="#"` is needed for the correct hover color.)
                        var updateButtonElement = htmlToElement(`
                            <li id="marnix_update_page_button">
                                <a
                                    href="#"
                                    class="aui-button aui-button-primary aui-style"
                                    title="Update page"
                                   >Update page (issue changed ${issueLastUpdatedText})</a>
                            </li>
                        `);
                        updateButtonElement.addEventListener("click", updateThisPage);
                        document.getElementsByClassName("aui-nav")[0].appendChild(updateButtonElement);

                        // From now on leave the page alone, the `REFRESH_ISSUE_PAGE` handler (below) will re-enable the regular check
                        pageLastUpdatedInMillis = undefined;
                        return;
                    }

                    console.log(`No recent issue ${issueNumber} update, will check again in a little while.`);
                    setTimeout(regularlyCheckForUpdates, timeBetweenChecksInSeconds*1000);
                },
                error: function(xhr, textStatus, errorThrown) {
                    console.log(`Something went wrong checking for updates of issue ${issueNumber}, will retry in a little while: ${textStatus} : ${errorThrown}`);
                    setTimeout(regularlyCheckForUpdates, timeBetweenChecksInSeconds*1000);
                }
               });
    }

    function updateThisPage() {
        console.log(`Let this page update its information (which also updates the title)`);
        JIRA.trigger(JIRA.Events.REFRESH_ISSUE_PAGE, [JIRA.Issue.getIssueId()]);
        // this event is caught by the event handler below, which will re-enable the regular check for issue updates
        // (or it triggers a full page reload, sometimes)
    }

    JIRA.bind(JIRA.Events.ISSUE_REFRESHED, function (e, context) {
        console.log(`Something triggered a refresh of this page`);

        if (!pageLastUpdatedInMillis) {
            console.log(`We will start to look for issue updates again in a little while`);
            setTimeout(regularlyCheckForUpdates, timeBetweenChecksInSeconds*1000);
        }
        pageLastUpdatedInMillis = Date.now();

        var updateButtonElement = document.getElementById('marnix_update_page_button')
        if (updateButtonElement) {
            console.log(`We can remove the update button again`);
            updateButtonElement.remove();
        }
    });

    regularlyCheckForUpdates();
})();