Anish Reghu
Kilo Sage
Kilo Sage

The code tracks lack of progress on stories, scrum tasks and helps a scrum master by listing them. It uses the Levenshtein approach to calculate the similarity between two recent work notes and judge if they are duplicates, if yes validate it as equivalent to No recent updates for that story or scrum task. This can be further presented on a dashboard or as a scheduled job as per one's requirement.

 

 

// --- Configuration ---
// IMPORTANT: Replace 'SYS_ID_OF_YOUR_ASSIGNMENT_GROUP' with your actual Assignment Group Sys ID
var assignmentGroupSysId = 'SYS_ID_OF_YOUR_ASSIGNMENT_GROUP';

var results = {};

// --- Process Stories ---
var storyGR = new GlideRecord('rm_story');
storyGR.addQuery('assignment_group', assignmentGroupSysId);
storyGR.addQuery('active', true);
storyGR.addQuery('state', '2');
storyGR.query();

var storiesToProcessScrumTasksFor = [];

while (storyGR.next()) {
    var assignee = storyGR.assigned_to.getDisplayValue();
    if (!results[assignee]) {
        results[assignee] = { stories: [], scrum_tasks: [] };
    }

    if (lacksMeaningfulUpdates(storyGR.sys_id.toString())) {
        results[assignee].stories.push(storyGR.number.toString());
    } else {
        storiesToProcessScrumTasksFor.push(storyGR.sys_id.toString());
    }
}

// --- Process Scrum Tasks associated with stories that had updates ---
if (storiesToProcessScrumTasksFor.length > 0) {
    var scrumGR = new GlideRecord('rm_scrum_task');
    scrumGR.addQuery('story', 'IN', storiesToProcessScrumTasksFor.join(','));
    scrumGR.addQuery('active', true);
    scrumGR.addQuery('state', 'IN', '1,2');
    scrumGR.query();

    while (scrumGR.next()) {
        var assignee = scrumGR.assigned_to.getDisplayValue();
        if (!results[assignee]) {
            results[assignee] = { stories: [], scrum_tasks: [] };
        }
        if (lacksMeaningfulUpdates(scrumGR.sys_id.toString())) {
            results[assignee].scrum_tasks.push(scrumGR.number.toString());
        }
    }
}

// --- Process Independent Scrum Tasks ---
var independentScrumTaskGR = new GlideRecord('rm_scrum_task');
independentScrumTaskGR.addQuery('assignment_group', assignmentGroupSysId);
independentScrumTaskGR.addQuery('active', true);
independentScrumTaskGR.addQuery('state', 'IN', '1,2');
independentScrumTaskGR.query();

while (independentScrumTaskGR.next()) {
    var assignee = independentScrumTaskGR.assigned_to.getDisplayValue();
    if (!results[assignee]) {
        results[assignee] = { stories: [], scrum_tasks: [] };
    }
    if (lacksMeaningfulUpdates(independentScrumTaskGR.sys_id.toString())) {
        results[assignee].scrum_tasks.push(independentScrumTaskGR.number.toString());
    }
}

// --- OUTPUT ---
for (var user in results) {
    if (results.hasOwnProperty(user)) {
        var data = results[user];
        if (data.stories.length > 0 || data.scrum_tasks.length > 0) {
            gs.info("--- " + user + " ---");
            if (data.stories.length > 0) {
                gs.info("Stories lacking updates: " + data.stories.join(", "));
            }
            if (data.scrum_tasks.length > 0) {
                gs.info("Scrum tasks lacking updates: " + data.scrum_tasks.join(", "));
            }
        }
    }
}

// =====================
// HELPER FUNCTIONS
// =====================

function lacksMeaningfulUpdates(recordSysId) {
    var notesGR = new GlideRecord('sys_journal_field');
    notesGR.addQuery('element_id', recordSysId);
    notesGR.addQuery('element', 'work_notes');
    notesGR.orderByDesc('sys_created_on');
    notesGR.setLimit(2);
    notesGR.query();

    var notes = [];
    while (notesGR.next()) {
        notes.push(notesGR.value.toString().toLowerCase().trim());
    }

    if (notes.length === 0 || notes.length === 1) {
        return true;
    }

    var note1 = notes[0];
    var note2 = notes[1];

    if (note1 === note2) {
        return true;
    }

    return calculateSimilarity(note1, note2) >= 0.8;
}

function calculateSimilarity(str1, str2) {
    function levenshtein(a, b) {
        if (a.length === 0) return b.length;
        if (b.length === 0) return a.length;

        var matrix = [];
        for (var i = 0; i <= b.length; i++) {
            matrix[i] = [i];
        }
        for (var j = 0; j <= a.length; j++) {
            matrix[0][j] = j;
        }

        for (var i = 1; i <= b.length; i++) {
            for (var j = 1; j <= a.length; j++) {
                if (b.charAt(i - 1) === a.charAt(j - 1)) {
                    matrix[i][j] = matrix[i - 1][j - 1];
                } else {
                    matrix[i][j] = Math.min(
                        matrix[i - 1][j - 1] + 1,
                        Math.min(
                            matrix[i][j - 1] + 1,
                            matrix[i - 1][j] + 1
                        )
                    );
                }
            }
        }
        return matrix[b.length][a.length];
    }

    var distance = levenshtein(str1, str2);
    var maxLen = Math.max(str1.length, str2.length);
    return maxLen === 0 ? 1.0 : (maxLen - distance) / maxLen;
}

 

 

 

SAMPLE OUTPUT:

== Mary Sam ==
Stories lacking updates: STRY0012345, STRY0012378
Scrum tasks lacking updates: STSK0015678

==  Rahul Jain ==
Scrum tasks lacking updates: STSK0018901, STSK0018910

 

Good luck!

 

Regards,

Anish