|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | +// GitHub link: https://github.com/Sedeniono/ADO-History-Diff |
| 3 | + |
| 4 | +// @ts-check |
| 5 | + |
| 6 | + |
| 7 | +import { gWorkItemRESTClient } from "./Globals"; |
| 8 | +import { DiffHtmlText } from "./Utils"; |
| 9 | + |
| 10 | + |
| 11 | +// Artificial id used for comment updates. |
| 12 | +export const COMMENT_UPDATE_ID = 'COMMENT'; |
| 13 | + |
| 14 | + |
| 15 | +export function GetTableInfosForEachComment(comments) |
| 16 | +{ |
| 17 | + let allCommentTables = []; |
| 18 | + |
| 19 | + for (const comment of comments) { |
| 20 | + if (!comment || !comment.allUpdates) { |
| 21 | + continue; |
| 22 | + } |
| 23 | + |
| 24 | + // Ensure sorting from oldest to newest. |
| 25 | + comment.allUpdates.sort((a, b) => a.version - b.version); |
| 26 | + |
| 27 | + for (let idx = 0; idx < comment.allUpdates.length; ++idx) { |
| 28 | + const curVersion = comment.allUpdates[idx]; |
| 29 | + const prevVersion = idx !== 0 ? comment.allUpdates[idx - 1] : null; |
| 30 | + |
| 31 | + const curText = curVersion?.isDeleted ? '' : curVersion?.text; |
| 32 | + const prevText = prevVersion?.isDeleted ? '' : prevVersion?.text; |
| 33 | + const textChange = DiffHtmlText(prevText, curText); |
| 34 | + |
| 35 | + let action = ''; |
| 36 | + if (idx === 0) { |
| 37 | + action = 'created'; |
| 38 | + } |
| 39 | + else if (curVersion?.isDeleted && !prevVersion?.isDeleted) { |
| 40 | + action = 'deleted'; |
| 41 | + } |
| 42 | + else { |
| 43 | + action = 'edited'; |
| 44 | + } |
| 45 | + |
| 46 | + // For consistency with the other updates, each comment update gets its own table. So the table consists of only one row. |
| 47 | + // (Except if we merge it later on with another update.) |
| 48 | + const tableRows = [[`Comment ${action}`, textChange]]; |
| 49 | + |
| 50 | + allCommentTables.push({ |
| 51 | + authorIdentity: curVersion.modifiedBy, |
| 52 | + changedDate: curVersion.modifiedDate, |
| 53 | + tableRows: tableRows, |
| 54 | + idNumber: COMMENT_UPDATE_ID |
| 55 | + }); |
| 56 | + } |
| 57 | + } |
| 58 | + |
| 59 | + return allCommentTables; |
| 60 | +} |
| 61 | + |
| 62 | + |
| 63 | +// Returns an array 'Comment[]', as described here: https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/comments/get-comments?view=azure-devops-rest-5.1 |
| 64 | +// However, every 'Comment' element contains an additional property 'allUpdates' that is an array of all versions of the comment. |
| 65 | +export async function GetCommentsWithHistory(workItemId, projectName) |
| 66 | +{ |
| 67 | + // Note: In contrast to getUpdates(), apparently the REST request is not paged. It returns always all comments by default. |
| 68 | + const allComments = await GetCommentsRESTRequest( |
| 69 | + workItemId, projectName, /*expand*/ 'none', undefined, /*includeDeleted*/ true); |
| 70 | + |
| 71 | + if (!allComments || !allComments.comments || allComments.comments.length == 0) { |
| 72 | + return []; |
| 73 | + } |
| 74 | + |
| 75 | + let commentsAwaiting = []; |
| 76 | + let versionsPromises = []; |
| 77 | + |
| 78 | + for (const comment of allComments.comments) { |
| 79 | + // If there is more than one version, start the request for all versions of the comment. We will await the |
| 80 | + // answer for all comments simultaneously below. |
| 81 | + if (comment?.version > 1 && comment?.id) { |
| 82 | + // Note: In contrast to getUpdates(), apparently the REST request is not paged. It returns always all versions by default. |
| 83 | + const versionsPromise = GetCommentsVersionsRESTRequest(workItemId, projectName, comment.id); |
| 84 | + commentsAwaiting.push(comment); |
| 85 | + versionsPromises.push(versionsPromise); |
| 86 | + } |
| 87 | + else { |
| 88 | + comment.allUpdates = [comment]; |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + if (commentsAwaiting.length > 0) { |
| 93 | + const allVersions = await Promise.all(versionsPromises); |
| 94 | + for (let idx = 0; idx < commentsAwaiting.length; ++idx) { |
| 95 | + commentsAwaiting[idx].allUpdates = allVersions[idx]; |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + return allComments.comments; |
| 100 | +} |
| 101 | + |
| 102 | + |
| 103 | +// getComments() from the azure-devops-extension-api (at least until version 4.230.0) uses api-version=5.0-preview.2, which |
| 104 | +// doesn't allow to query deleted comments. But we need that. So we define a custom function that uses the newer REST API version 5.1. |
| 105 | +// So our function corresponds to this REST request: https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/comments/get-comments?view=azure-devops-rest-5.1 |
| 106 | +async function GetCommentsRESTRequest(id, project, expand, top, includeDeleted, order) |
| 107 | +{ |
| 108 | + return gWorkItemRESTClient.beginRequest({ |
| 109 | + apiVersion: '5.1-preview.3', |
| 110 | + routeTemplate: '{project}/_apis/wit/workItems/{id}/comments', |
| 111 | + routeValues: { |
| 112 | + project: project, |
| 113 | + id: id |
| 114 | + }, |
| 115 | + queryParams: { |
| 116 | + '$expand': expand, |
| 117 | + '$top': top, |
| 118 | + includeDeleted: includeDeleted, |
| 119 | + order: order |
| 120 | + } |
| 121 | + }); |
| 122 | +} |
| 123 | + |
| 124 | + |
| 125 | +// azure-devops-extension-api (at least until version 4.230.0) does not provide a wrapper for getting the comments versions. |
| 126 | +// So we define it ourselves. This corresponds to: https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/comments-versions/list?view=azure-devops-rest-5.1 |
| 127 | +async function GetCommentsVersionsRESTRequest(id, project, commentId) |
| 128 | +{ |
| 129 | + return gWorkItemRESTClient.beginRequest({ |
| 130 | + apiVersion: '5.1-preview.1', |
| 131 | + routeTemplate: '{project}/_apis/wit/workItems/{id}/comments/{commentId}/versions', |
| 132 | + routeValues: { |
| 133 | + project: project, |
| 134 | + id: id, |
| 135 | + commentId: commentId |
| 136 | + }, |
| 137 | + queryParams: {} |
| 138 | + }); |
| 139 | +} |
0 commit comments