danger
Version:
Unit tests for Team Culture
538 lines • 28.9 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.gitlabJSONToGitLabDSL = void 0;
var inlinePositionParser_1 = require("./gitlab/inlinePositionParser");
var debug_1 = require("../debug");
var githubIssueTemplate_1 = require("../runner/templates/githubIssueTemplate");
var d = (0, debug_1.debug)("GitLab");
/**
* Determines whether Danger should use threads for the "main" Danger comment.
*/
var useThreads = function () {
return process.env.DANGER_GITLAB_USE_THREADS === "1" || process.env.DANGER_GITLAB_USE_THREADS === "true";
};
var GitLab = /** @class */ (function () {
function GitLab(api) {
var _this = this;
this.api = api;
this.getReviewInfo = function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.api.getMergeRequestInfo()];
});
}); };
// returns the `danger.gitlab` object
this.getPlatformReviewDSLRepresentation = function () { return __awaiter(_this, void 0, void 0, function () {
var mr, commits, approvals;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getReviewInfo()];
case 1:
mr = _a.sent();
return [4 /*yield*/, this.api.getMergeRequestCommits()];
case 2:
commits = _a.sent();
return [4 /*yield*/, this.api.getMergeRequestApprovals()];
case 3:
approvals = _a.sent();
return [2 /*return*/, {
metadata: this.api.repoMetadata,
mr: mr,
commits: commits,
approvals: approvals,
}];
}
});
}); };
// TODO: test
this.getPlatformGitRepresentation = function () { return __awaiter(_this, void 0, void 0, function () {
var diffs, commits, mappedCommits, modified_files, created_files, deleted_files;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.api.getMergeRequestDiffs()];
case 1:
diffs = _a.sent();
return [4 /*yield*/, this.api.getMergeRequestCommits()];
case 2:
commits = _a.sent();
mappedCommits = commits.map(function (commit) {
return {
sha: commit.id,
author: {
name: commit.author_name,
email: commit.author_email,
date: commit.authored_date,
},
committer: {
name: commit.committer_name,
email: commit.committer_email,
date: commit.committed_date,
},
message: commit.message,
parents: commit.parent_ids,
url: "".concat(_this.api.projectURL, "/commit/").concat(commit.id),
tree: null,
};
});
modified_files = diffs
.filter(function (diff) { return !diff.new_file && !diff.deleted_file; })
.map(function (diff) { return diff.new_path; });
created_files = diffs.filter(function (diff) { return diff.new_file; }).map(function (diff) { return diff.new_path; });
deleted_files = diffs.filter(function (diff) { return diff.deleted_file; }).map(function (diff) { return diff.new_path; });
return [2 /*return*/, {
modified_files: modified_files,
created_files: created_files,
deleted_files: deleted_files,
commits: mappedCommits,
}];
}
});
}); };
this.getInlineComments = function (dangerID) { return __awaiter(_this, void 0, void 0, function () {
var dangerUserID, dangerDiscussions, diffNotes;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
d("getInlineComments", { dangerID: dangerID });
return [4 /*yield*/, this.api.getUser()];
case 1:
dangerUserID = (_a.sent()).id;
return [4 /*yield*/, this.getDangerDiscussions(dangerID)
// Remove system resolved danger discussions to not end up deleting danger inline comments
// on old versions of the diff. This is preferred as otherwise the discussion ends up in a state where
// the auto resolve system note can become the first note on the discussion resulting in poor change history on the MR.
];
case 2:
dangerDiscussions = _a.sent();
// Remove system resolved danger discussions to not end up deleting danger inline comments
// on old versions of the diff. This is preferred as otherwise the discussion ends up in a state where
// the auto resolve system note can become the first note on the discussion resulting in poor change history on the MR.
dangerDiscussions = dangerDiscussions.filter(function (discussion) { return !_this.isDangerDiscussionSystemResolved(discussion); });
d("getInlineComments found danger discussions", dangerDiscussions);
diffNotes = this.getDiffNotesFromDiscussions(dangerDiscussions);
return [2 /*return*/, diffNotes.map(function (note) {
return {
id: "".concat(note.id),
body: note.body,
ownedByDanger: note.author.id === dangerUserID && note.body.includes(dangerID),
};
})];
}
});
}); };
this.updateOrCreateComment = function (dangerID, newComment) { return __awaiter(_this, void 0, void 0, function () {
var discussions, firstDiscussion, existingNote, newOrUpdatedNote, notes, note, _i, notes_1, deleteme;
var _a, _b, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
d("updateOrCreateComment", { dangerID: dangerID, newComment: newComment });
if (!useThreads()) return [3 /*break*/, 7];
return [4 /*yield*/, this.getDangerDiscussions(dangerID)];
case 1:
discussions = _d.sent();
d("updateOrCreateComment using threads, discussions", discussions);
firstDiscussion = discussions.shift();
existingNote = (_a = firstDiscussion === null || firstDiscussion === void 0 ? void 0 : firstDiscussion.notes) === null || _a === void 0 ? void 0 : _a[0];
return [4 /*yield*/, this.deleteDiscussions(discussions)];
case 2:
_d.sent();
newOrUpdatedNote = void 0;
if (!existingNote) return [3 /*break*/, 4];
return [4 /*yield*/, this.api.updateMergeRequestNote(existingNote.id, newComment)];
case 3:
// update the existing comment
newOrUpdatedNote = _d.sent();
return [3 /*break*/, 6];
case 4: return [4 /*yield*/, this.api.createMergeRequestDiscussion(newComment)];
case 5:
// create a new comment
newOrUpdatedNote = (_c = (_b = (_d.sent())) === null || _b === void 0 ? void 0 : _b.notes) === null || _c === void 0 ? void 0 : _c[0];
_d.label = 6;
case 6:
if (!newOrUpdatedNote) {
throw new Error("Could not update or create comment");
}
// create URL from note
// "https://gitlab.com/group/project/merge_requests/154#note_132143425"
return [2 /*return*/, "".concat(this.api.mergeRequestURL, "#note_").concat(newOrUpdatedNote.id)];
case 7: return [4 /*yield*/, this.getMainDangerNotes(dangerID)];
case 8:
notes = _d.sent();
d("updateOrCreateComment not using threads, main danger notes", notes);
note = void 0;
if (!notes.length) return [3 /*break*/, 14];
return [4 /*yield*/, this.api.updateMergeRequestNote(notes[0].id, newComment)
// delete the rest
];
case 9:
// update the first
note = _d.sent();
_i = 0, notes_1 = notes;
_d.label = 10;
case 10:
if (!(_i < notes_1.length)) return [3 /*break*/, 13];
deleteme = notes_1[_i];
if (deleteme === notes[0]) {
return [3 /*break*/, 12];
}
return [4 /*yield*/, this.api.deleteMergeRequestNote(deleteme.id)];
case 11:
_d.sent();
_d.label = 12;
case 12:
_i++;
return [3 /*break*/, 10];
case 13: return [3 /*break*/, 16];
case 14: return [4 /*yield*/, this.api.createMergeRequestNote(newComment)];
case 15:
// create a new note
note = _d.sent();
_d.label = 16;
case 16:
// create URL from note
// "https://gitlab.com/group/project/merge_requests/154#note_132143425"
return [2 /*return*/, "".concat(this.api.mergeRequestURL, "#note_").concat(note.id)];
}
});
}); };
this.createComment = function (_dangerID, comment) { return __awaiter(_this, void 0, void 0, function () {
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
d("createComment", { comment: comment });
if (!useThreads()) return [3 /*break*/, 2];
return [4 /*yield*/, this.api.createMergeRequestDiscussion(comment)];
case 1: return [2 /*return*/, (_b = (_a = (_c.sent())) === null || _a === void 0 ? void 0 : _a.notes) === null || _b === void 0 ? void 0 : _b[0]];
case 2: return [2 /*return*/, this.api.createMergeRequestNote(comment)];
}
});
}); };
this.createInlineComment = function (git, comment, path, line) { return __awaiter(_this, void 0, void 0, function () {
var structuredDiffForFile, inlinePosition, mr, position;
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
d("createInlineComment", { git: git, comment: comment, path: path, line: line });
return [4 /*yield*/, git.structuredDiffForFile(path)];
case 1:
structuredDiffForFile = _c.sent();
if (!structuredDiffForFile) {
return [2 /*return*/, Promise.reject("Unable to find diff for file ".concat(path))];
}
inlinePosition = (0, inlinePositionParser_1.inlinePositionParser)(structuredDiffForFile, path, line);
d("createInlineComment inlinePosition", inlinePosition);
return [4 /*yield*/, this.api.getMergeRequestInfo()];
case 2:
mr = _c.sent();
position = {
positionType: "text",
baseSha: mr.diff_refs.base_sha,
startSha: mr.diff_refs.start_sha,
headSha: mr.diff_refs.head_sha,
oldPath: inlinePosition.pathDiff.oldPath,
newPath: inlinePosition.pathDiff.newPath,
oldLine: (_a = inlinePosition.lineDiff.oldLine) === null || _a === void 0 ? void 0 : _a.toString(),
newLine: (_b = inlinePosition.lineDiff.newLine) === null || _b === void 0 ? void 0 : _b.toString(),
};
return [2 /*return*/, this.api.createMergeRequestDiscussion(comment, {
position: position,
})];
}
});
}); };
this.updateInlineComment = function (comment, id) { return __awaiter(_this, void 0, void 0, function () {
var nid;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
d("updateInlineComment", { comment: comment, id: id });
nid = parseInt(id) // fingers crossed
;
return [4 /*yield*/, this.api.updateMergeRequestNote(nid, comment)];
case 1: // fingers crossed
return [2 /*return*/, _a.sent()];
}
});
}); };
this.deleteInlineComment = function (id) { return __awaiter(_this, void 0, void 0, function () {
var nid;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
d("deleteInlineComment", { id: id });
nid = parseInt(id) // fingers crossed
;
return [4 /*yield*/, this.api.deleteMergeRequestNote(nid)];
case 1: // fingers crossed
return [2 /*return*/, _a.sent()];
}
});
}); };
/**
* Attempts to delete the "main" Danger comment. If the "main" Danger
* comment has any comments on it then that comment will not be deleted.
*/
this.deleteMainComment = function (dangerID) { return __awaiter(_this, void 0, void 0, function () {
var discussions, notes, _i, notes_2, note;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!useThreads()) return [3 /*break*/, 3];
return [4 /*yield*/, this.getDangerDiscussions(dangerID)];
case 1:
discussions = _a.sent();
return [4 /*yield*/, this.deleteDiscussions(discussions)];
case 2: return [2 /*return*/, _a.sent()];
case 3: return [4 /*yield*/, this.getMainDangerNotes(dangerID)];
case 4:
notes = _a.sent();
_i = 0, notes_2 = notes;
_a.label = 5;
case 5:
if (!(_i < notes_2.length)) return [3 /*break*/, 8];
note = notes_2[_i];
d("deleteMainComment", { id: note.id });
return [4 /*yield*/, this.api.deleteMergeRequestNote(note.id)];
case 6:
_a.sent();
_a.label = 7;
case 7:
_i++;
return [3 /*break*/, 5];
case 8: return [2 /*return*/, notes.length > 0];
}
});
}); };
this.deleteDiscussions = function (discussions) { return __awaiter(_this, void 0, void 0, function () {
var _i, discussions_1, discussion;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
d("deleteDiscussions", { length: discussions.length });
_i = 0, discussions_1 = discussions;
_a.label = 1;
case 1:
if (!(_i < discussions_1.length)) return [3 /*break*/, 4];
discussion = discussions_1[_i];
return [4 /*yield*/, this.deleteDiscussion(discussion)];
case 2:
_a.sent();
_a.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/, discussions.length > 0];
}
});
}); };
this.deleteDiscussion = function (discussion) { return __awaiter(_this, void 0, void 0, function () {
var discussionNotes, _i, discussionNotes_1, discussionNote;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
d("deleteDiscussion", { discussionId: discussion.id });
discussionNotes = (_a = discussion.notes) !== null && _a !== void 0 ? _a : [];
_i = 0, discussionNotes_1 = discussionNotes;
_b.label = 1;
case 1:
if (!(_i < discussionNotes_1.length)) return [3 /*break*/, 4];
discussionNote = discussionNotes_1[_i];
return [4 /*yield*/, this.api.deleteMergeRequestNote(discussionNote.id)];
case 2:
_b.sent();
_b.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4: return [2 /*return*/, discussionNotes.length > 0];
}
});
}); };
/**
* Only fetches the discussions where danger owns the top note
*/
this.getDangerDiscussions = function (dangerID) { return __awaiter(_this, void 0, void 0, function () {
var noteFilter, discussions;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getDangerDiscussionNoteFilter(dangerID)];
case 1:
noteFilter = _a.sent();
return [4 /*yield*/, this.api.getMergeRequestDiscussions()];
case 2:
discussions = _a.sent();
return [2 /*return*/, discussions.filter(function (_a) {
var notes = _a.notes;
return notes && notes.length && noteFilter(notes[0]);
})];
}
});
}); };
this.isDangerDiscussionSystemResolved = function (discussion) {
var notes = discussion.notes;
if (!notes) {
return false;
}
var dangerNote = notes[0];
if (!dangerNote || !dangerNote.resolved) {
return false;
}
// Check for a system note that resolved it
return notes.some(function (note) {
return note.system === true;
});
};
this.getDiffNotesFromDiscussions = function (discussions) {
var diffNotes = discussions.map(function (discussion) {
var _a;
var note = (_a = discussion.notes) === null || _a === void 0 ? void 0 : _a[0];
if (!note || note.type != "DiffNote") {
return undefined;
}
return note;
});
return diffNotes.filter(Boolean);
};
/**
* Attempts to find the "main" Danger note and should return at most
* one item. If the "main" Danger note has any comments on it then that
* note will not be returned.
*/
this.getMainDangerNotes = function (dangerID) { return __awaiter(_this, void 0, void 0, function () {
var noteFilter, notes;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getDangerMainNoteFilter(dangerID)];
case 1:
noteFilter = _a.sent();
return [4 /*yield*/, this.api.getMergeRequestNotes()];
case 2:
notes = _a.sent();
return [2 /*return*/, notes.filter(noteFilter)];
}
});
}); };
/**
* Filters a note to determine if it was created by Danger.
*/
this.getDangerDiscussionNoteFilter = function (dangerID) { return __awaiter(_this, void 0, void 0, function () {
var dangerUserId;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.api.getUser()];
case 1:
dangerUserId = (_a.sent()).id;
return [2 /*return*/, function (_a) {
var id = _a.author.id, body = _a.body, system = _a.system;
return (!system && // system notes are generated when the user interacts with the UI e.g. changing a PR title
id === dangerUserId &&
body.includes((0, githubIssueTemplate_1.dangerIDToString)(dangerID)));
}];
}
});
}); };
/**
* Filters a note to the "main" Danger note. If that note has any
* comments on it then it will not be found.
*/
this.getDangerMainNoteFilter = function (dangerID) { return __awaiter(_this, void 0, void 0, function () {
var dangerUserId;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.api.getUser()];
case 1:
dangerUserId = (_a.sent()).id;
return [2 /*return*/, function (_a) {
var id = _a.author.id, body = _a.body, system = _a.system, type = _a.type;
return (!system && // system notes are generated when the user interacts with the UI e.g. changing a PR title
id === dangerUserId &&
// This check for the type being `null` seems to be the best option
// we have to determine whether this note is the "main" Danger note.
// This assumption does not hold if there are any comments on the
// "main" Danger note and in that case a new "main" Danger note will
// be created instead of updating the existing note. This behavior is better
// than the current alternative which is the bug described here:
// https://github.com/danger/danger-js/issues/1351
type == null &&
body.includes((0, githubIssueTemplate_1.dangerIDToString)(dangerID)));
}];
}
});
}); };
this.updateStatus = function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
d("updateStatus", {});
return [2 /*return*/, true];
});
}); };
this.getFileContents = this.api.getFileContents;
this.name = "GitLab";
}
GitLab.prototype.supportsCommenting = function () {
return true;
};
GitLab.prototype.supportsInlineComments = function () {
return true;
};
return GitLab;
}());
exports.default = GitLab;
var gitlabJSONToGitLabDSL = function (gl, api) { return (__assign(__assign({}, gl), { utils: {
fileContents: api.getFileContents,
addLabels: api.addLabels,
removeLabels: api.removeLabels,
}, api: api.apiInstance })); };
exports.gitlabJSONToGitLabDSL = gitlabJSONToGitLabDSL;
//# sourceMappingURL=GitLab.js.map