@atomist/sdm-core
Version:
Atomist Software Delivery Machine - Implementation
193 lines • 9.21 kB
JavaScript
/*
* Copyright © 2019 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) {
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const automation_client_1 = require("@atomist/automation-client");
const sdm_1 = require("@atomist/sdm");
const slack_messages_1 = require("@atomist/slack-messages");
const _ = require("lodash");
const array_1 = require("../../util/misc/array");
const updateGoal_1 = require("../goal-state/updateGoal");
/**
* Extension pack to send notifications on certain conditions.
* Recipients and notification messages can be customized by providing options
* with DestinationFactory and NotificationFactory.
* @param options
*/
function notificationSupport(options = {}) {
return Object.assign(Object.assign({}, sdm_1.metadata("notification")), { configure: sdm => {
const updateGoalCommand = updateGoal_1.updateGoalStateCommand();
updateGoalCommand.name = `${updateGoalCommand.name}ForNotifications`;
sdm.addCommand(updateGoalCommand);
const optsToUse = Object.assign({ destination: defaultDestinationFactory, notification: defaultNotificationFactory(updateGoalCommand) }, options);
sdm.addGoalCompletionListener(notifyGoalCompletionListener(optsToUse));
} });
}
exports.notificationSupport = notificationSupport;
/**
* Default DestinationFactory that would send every commit author a direct message in Slack / MS Teams.
*/
function defaultDestinationFactory(goal) {
return __awaiter(this, void 0, void 0, function* () {
if (goal.state === sdm_1.SdmGoalState.failure) {
return _.uniqBy(goal.push.commits.map(c => _.get(c, "author.person.chatId"))
.filter(c => !!c), r => `${r.chatTeam.id}.${r.screenName}`)
.map(r => automation_client_1.addressSlackUsers(r.chatTeam.id, r.screenName));
}
return undefined;
});
}
exports.defaultDestinationFactory = defaultDestinationFactory;
/**
* Default NotificationFactory that sends messages with restart, start and approve buttons where appropriate.
*/
function defaultNotificationFactory(updateGoalCommand) {
return (gi) => __awaiter(this, void 0, void 0, function* () {
const { completedGoal, context } = gi;
const goalSetId = completedGoal.goalSetId;
const uniqueName = completedGoal.uniqueName;
const msgId = automation_client_1.guid();
let state;
let suffix;
let msg;
switch (completedGoal.state) {
case sdm_1.SdmGoalState.failure:
state = "has failed";
suffix = "Failed";
msg = sdm_1.slackErrorMessage("", "", context, {
actions: completedGoal.retryFeasible ? [
sdm_1.actionableButton({ text: "Restart" }, updateGoalCommand, {
goalSetId,
uniqueName,
msgId,
state: sdm_1.SdmGoalState.requested,
})
] : [],
});
break;
case sdm_1.SdmGoalState.waiting_for_approval:
state = "is waiting for approval";
suffix = "Awaiting Approval";
msg = sdm_1.slackInfoMessage("", "", {
actions: [sdm_1.actionableButton({ text: "Approve" }, updateGoalCommand, {
goalSetId,
uniqueName,
msgId,
state: sdm_1.SdmGoalState.approved,
})],
});
break;
case sdm_1.SdmGoalState.waiting_for_pre_approval:
state = "is waiting to start";
suffix = "Awaiting Start";
msg = sdm_1.slackInfoMessage("", "", {
actions: [sdm_1.actionableButton({ text: "Start" }, updateGoalCommand, {
goalSetId,
uniqueName,
msgId,
state: sdm_1.SdmGoalState.pre_approved,
})],
});
break;
case sdm_1.SdmGoalState.stopped:
state = "has stopped";
suffix = "Stopped";
msg = sdm_1.slackInfoMessage("", "");
break;
default:
return undefined;
}
const author = `Goal ${suffix}`;
const commitMsg = truncateCommitMessage(completedGoal.push.after.message);
const text = `Goal ${slack_messages_1.italic(completedGoal.url ? slack_messages_1.url(completedGoal.url, completedGoal.name) : completedGoal.name)} on ${slack_messages_1.url(completedGoal.push.after.url, slack_messages_1.codeLine(completedGoal.sha.slice(0, 7)))} ${slack_messages_1.italic(commitMsg)} of ${slack_messages_1.bold(`${slack_messages_1.url(completedGoal.push.repo.url, `${completedGoal.repo.owner}/${completedGoal.repo.name}/${completedGoal.branch}`)}`)} ${state}.`;
const channels = _.get(completedGoal, "push.repo.channels") || [];
const channelLink = channels.filter(c => !!c.channelId).map(c => slack_messages_1.channel(c.channelId)).join(" \u00B7 ");
const link = `https://app.atomist.com/workspace/${context.workspaceId}/goalset/${completedGoal.goalSetId}`;
msg.attachments[0] = Object.assign(Object.assign({}, msg.attachments[0]), { author_name: author, text, footer: `${sdm_1.slackFooter()} \u00B7 ${slack_messages_1.url(link, completedGoal.goalSetId.slice(0, 7))} \u00B7 ${channelLink}` });
return { message: msg, options: { id: msgId } };
});
}
exports.defaultNotificationFactory = defaultNotificationFactory;
/**
* GoalCompletionListener that delegates to the NotificationFactory and DestinationFactory to
* create notifications and send them out.
*/
function notifyGoalCompletionListener(options) {
return (gi) => __awaiter(this, void 0, void 0, function* () {
const { completedGoal, context } = gi;
const destinations = [];
for (const destinationFactory of array_1.toArray(options.destination || [])) {
const newDestinations = yield destinationFactory(completedGoal, gi);
if (!!newDestinations) {
destinations.push(...array_1.toArray(newDestinations));
}
}
if (destinations.length > 0) {
const msg = yield options.notification(gi);
for (const destination of destinations) {
yield context.messageClient.send(msg.message, destination, msg.options);
}
}
});
}
exports.notifyGoalCompletionListener = notifyGoalCompletionListener;
function truncateCommitMessage(message) {
const title = (message || "").split("\n")[0];
const escapedTitle = slack_messages_1.escape(title);
if (escapedTitle.length <= 50) {
return escapedTitle;
}
const splitRegExp = /(&(?:[gl]t|amp);|<.*?\||>)/;
const titleParts = escapedTitle.split(splitRegExp);
let truncatedTitle = "";
let addNext = 1;
let i;
for (i = 0; i < titleParts.length; i++) {
let newTitle = truncatedTitle;
if (i % 2 === 0) {
newTitle += titleParts[i];
}
else if (/^&(?:[gl]t|amp);$/.test(titleParts[i])) {
newTitle += "&";
}
else if (/^<.*\|$/.test(titleParts[i])) {
addNext = 2;
continue;
}
else if (titleParts[i] === ">") {
addNext = 1;
continue;
}
if (newTitle.length > 50) {
const l = 50 - newTitle.length;
titleParts[i] = titleParts[i].slice(0, l) + "...";
break;
}
truncatedTitle = newTitle;
}
return titleParts.slice(0, i + addNext).join("");
}
exports.truncateCommitMessage = truncateCommitMessage;
//# sourceMappingURL=notification.js.map
;