UNPKG

@atomist/sample-sdm

Version:

Sample Atomist automation for software delivery

186 lines 8.6 kB
"use strict"; /* * Copyright © 2018 Atomist, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const automation_client_1 = require("@atomist/automation-client"); const ghub_1 = require("@atomist/sdm/util/github/ghub"); const slack = require("@atomist/slack-messages"); const axios_1 = require("axios"); const stringify = require("json-stringify-safe"); const _ = require("lodash"); /** * Manage a single issue for a subset of review problems * @param commentFilter filter for relevant review comments * @param title title of the issue to manage * @param bodyFormatter function to create body from comments * @return {Promise<void>} * @constructor */ function singleIssueManagingReviewListener(commentFilter, title, bodyFormatter) { return (ri) => __awaiter(this, void 0, void 0, function* () { if (ri.push.branch !== ri.push.repo.defaultBranch) { // We only care about pushes to the default branch return; } const relevantComments = ri.review.comments.filter(commentFilter); const existingIssue = yield findIssue(ri.credentials, ri.id, title); if (relevantComments.length === 0) { if (existingIssue) { automation_client_1.logger.info("Closing issue %d because all comments have been addressed", existingIssue.number); const congrats = `The last review problem was fixed by ${who(ri.push)} when they pushed ${linkToSha(ri.id)}`; yield updateIssue(ri.credentials, ri.id, Object.assign({}, existingIssue, { state: "closed", body: congrats })); } return; } // there are some comments if (!existingIssue) { const issue = { title, body: bodyFormatter(relevantComments, ri.id), }; automation_client_1.logger.info("Creating issue %j from review comments", issue); yield createIssue(ri.credentials, ri.id, issue); } else { // Update the issue if necessary, reopening it if need be const body = bodyFormatter(relevantComments, ri.id); if (body !== existingIssue.body) { automation_client_1.logger.info("Updating issue %d with the latest comments", existingIssue.number); yield updateIssue(ri.credentials, ri.id, Object.assign({}, existingIssue, { state: "open", body })); } else { automation_client_1.logger.info("Not updating issue %d as body has not changed", existingIssue.number); } } // Should we catch exceptions and not fail the Goal if this doesn't work? }); } exports.singleIssueManagingReviewListener = singleIssueManagingReviewListener; /** * Take this subset of issues and maintain an issue for each * @param {CommentFilter} commentFilter * @param {CommentsFormatter} bodyFormatter * @return {ReviewListener} */ function multiIssueManagingReviewListener(commentFilter, bodyFormatter) { return (ri) => __awaiter(this, void 0, void 0, function* () { if (ri.push.branch !== ri.push.repo.defaultBranch) { // We only care about pushes to the default branch return; } const relevantComments = ri.review.comments.filter(commentFilter); for (const comment of relevantComments) { // TODO disambiguate const title = comment.detail; const existingIssue = yield findIssue(ri.credentials, ri.id, title); // there are some comments if (!existingIssue) { const issue = { title, body: bodyFormatter(comment, ri.id), }; automation_client_1.logger.info("Creating issue %j from review comment", issue); yield createIssue(ri.credentials, ri.id, issue); } else { // Update the issue if necessary, reopening it if need be const body = bodyFormatter(comment, ri.id); if (body !== existingIssue.body) { automation_client_1.logger.info("Updating issue %d with the latest ", existingIssue.number); yield updateIssue(ri.credentials, ri.id, Object.assign({}, existingIssue, { state: "open", body })); } else { automation_client_1.logger.info("Not updating issue %d as body has not changed", existingIssue.number); } } // Should we catch exceptions and not fail the Goal if this doesn't work? } }); } exports.multiIssueManagingReviewListener = multiIssueManagingReviewListener; function who(push) { const screenName = _.get(push, "after.committer.person.chatId.screenName"); if (screenName) { return slack.user(screenName); } return _.get(push, "after.committer.token", "someone"); } function linkToSha(id) { return slack.url(id.url + "/tree/" + id.sha, id.sha.substr(0, 7)); } // update the state and body of an issue. function updateIssue(credentials, rr, issue) { return __awaiter(this, void 0, void 0, function* () { const safeIssue = { state: issue.state, body: issue.body, }; const token = credentials.token; const grr = rr; const url = encodeURI(`${grr.scheme}${grr.apiBase}/repos/${rr.owner}/${rr.repo}/issues/${issue.number}`); automation_client_1.logger.info(`Request to '${url}' to update issue`); yield axios_1.default.patch(url, safeIssue, ghub_1.authHeaders(token)).catch(err => { automation_client_1.logger.error("Failure updating issue. response: %s", stringify(err.response.data)); throw err; }); }); } function createIssue(credentials, rr, issue) { return __awaiter(this, void 0, void 0, function* () { const token = credentials.token; const grr = rr; const url = `${grr.scheme}${grr.apiBase}/repos/${rr.owner}/${rr.repo}/issues`; automation_client_1.logger.info(`Request to '${url}' to create issue`); yield axios_1.default.post(url, issue, ghub_1.authHeaders(token)); }); } // find the most recent open (or closed, if none are open) issue with precisely this title function findIssue(credentials, rr, title) { return __awaiter(this, void 0, void 0, function* () { const token = credentials.token; const grr = rr; const url = encodeURI(`${grr.scheme}${grr.apiBase}/search/issues?q=is:issue+user:${rr.owner}+repo:${rr.repo}+"${title}"`); automation_client_1.logger.info(`Request to '${url}' to get issues`); const returnedIssues = yield axios_1.default.get(url, ghub_1.authHeaders(token)).then(r => r.data.items); return returnedIssues.filter(i => i.title === title && i.url.includes(`/${rr.owner}/${rr.repo}/issues/`)) .sort(openFirst)[0]; }); } /** * Compare giving open issues a lower sort order * @param {KnownIssue} a * @param {KnownIssue} b * @return {number} */ function openFirst(a, b) { if (a.state === "open" && b.state === "closed") { return -1; } if (b.state === "open" && a.state === "closed") { return 1; } return b.number - a.number; // if same state, most recent one first. } //# sourceMappingURL=issueManagingReviewListeners.js.map