@atomist/automation-client
Version:
Atomist API for software low-level client
361 lines • 17 kB
JavaScript
;
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 stringify = require("json-stringify-safe");
const _ = require("lodash");
const globals_1 = require("../../globals");
const HandlerResult_1 = require("../../HandlerResult");
const MessageClientSupport_1 = require("../../spi/message/MessageClientSupport");
const logger_1 = require("../../util/logger");
const disposable_1 = require("../invoker/disposable");
const namespace = require("../util/cls");
const string_1 = require("../util/string");
class AbstractRequestProcessor {
constructor(automations, configuration, listeners = []) {
this.automations = automations;
this.configuration = configuration;
this.listeners = listeners;
}
processCommand(command,
// tslint:disable-next-line:no-empty
callback = () => {
}) {
// setup context
const ses = namespace.create();
const cls = this.setupNamespace(command, this.automations);
ses.run(() => {
namespace.set(cls);
this.listeners.forEach(l => l.commandIncoming(command));
const np = namespace.get();
const ci = {
name: command.command,
args: command.parameters,
mappedParameters: command.mapped_parameters,
secrets: command.secrets,
};
const ctx = {
workspaceId: command.team.id,
source: command.source,
correlationId: command.correlation_id,
invocationId: np ? np.invocationId : undefined,
messageClient: undefined,
context: cls,
trigger: _.cloneDeep(command),
configuration: this.configuration,
};
ctx.graphClient = this.createGraphClient(command, ctx);
ctx.messageClient = this.createAndWrapMessageClient(command, ctx);
ctx.lifecycle = {
registerDisposable: disposable_1.registerDisposable(ctx),
dispose: disposable_1.dispose(ctx),
};
this.listeners.forEach(l => l.contextCreated(ctx));
this.listeners.forEach(l => l.commandStarting(ci, ctx));
this.invokeCommand(ci, ctx, command, callback);
});
}
processEvent(event,
// tslint:disable-next-line:no-empty
callback = () => {
}) {
// setup context
const ses = namespace.create();
const cls = this.setupNamespace(event, this.automations);
ses.run(() => {
namespace.set(cls);
this.listeners.forEach(l => l.eventIncoming(event));
const np = namespace.get();
const ef = {
data: event.data,
extensions: {
operationName: event.extensions.operationName,
},
secrets: event.secrets,
};
const ctx = {
workspaceId: event.extensions.team_id,
correlationId: event.extensions.correlation_id,
invocationId: np ? np.invocationId : undefined,
messageClient: undefined,
context: cls,
trigger: _.cloneDeep(event),
configuration: this.configuration,
};
ctx.graphClient = this.createGraphClient(event, ctx);
ctx.messageClient = this.createAndWrapMessageClient(event, ctx);
ctx.lifecycle = {
registerDisposable: disposable_1.registerDisposable(ctx),
dispose: disposable_1.dispose(ctx),
};
this.listeners.forEach(l => l.contextCreated(ctx));
this.listeners.forEach(l => l.eventStarting(ef, ctx));
this.invokeEvent(ef, ctx, event, callback);
});
}
sendCommandStatus(success, code, request, ctx) {
const source = _.cloneDeep(request.source);
if (source && source.slack) {
delete source.slack.user;
}
const response = {
api_version: "1",
correlation_id: request.correlation_id,
team: request.team,
command: request.command,
source: request.source,
destinations: [source],
status: {
code,
reason: `${success ? "Successfully" : "Unsuccessfully"} invoked command` +
` ${request.command} of ${this.automations.automations.name}@${this.automations.automations.version}`,
},
};
return this.sendStatusMessage(response, ctx);
}
sendEventStatus(success, request, event, ctx) {
const response = {
api_version: "1",
correlation_id: event.extensions.correlation_id,
team: {
id: event.extensions.team_id,
name: event.extensions.team_name,
},
event: request.extensions.operationName,
status: {
code: success ? 0 : 1,
reason: `${success ? "Successfully" : "Unsuccessfully"} invoked event subscription` +
` ${request.extensions.operationName} of ${this.automations.automations.name}@${this.automations.automations.version}`,
},
};
return this.sendStatusMessage(response, ctx);
}
invokeCommand(ci, ctx, command, callback) {
const finalize = (result) => {
this.sendCommandStatus(result.code === 0, result.code, command, ctx)
.catch(err => logger_1.logger.warn("Unable to send status for command '%s': %s", command.command, err.message))
.then(() => {
callback(Promise.resolve(result));
logger_1.logger.debug(`Finished invocation of command '%s': %s`, command.command, stringify(result, possibleAxiosObjectReplacer));
this.clearNamespace();
});
};
logger_1.logger.debug("Incoming command invocation '%s'", stringify(command, string_1.replacer));
try {
this.automations.invokeCommand(ci, ctx)
.then(result => {
if (!result || !result.hasOwnProperty("code")) {
return Object.assign(Object.assign({}, defaultResult(ctx)), result);
}
else {
return result;
}
})
.then(result => ctx.lifecycle ? ctx.lifecycle.dispose().then(() => result) : result)
.then(result => {
if (result.code === 0) {
result = Object.assign(Object.assign({}, defaultResult(ctx)), result);
this.listeners.map(l => () => l.commandSuccessful(ci, ctx, result))
.reduce((p, f) => p.then(f), Promise.resolve())
.then(() => finalize(result));
}
else {
result = Object.assign(Object.assign({}, defaultErrorResult(ctx)), result);
this.listeners.map(l => () => l.commandFailed(ci, ctx, result))
.reduce((p, f) => p.then(f), Promise.resolve())
.then(() => finalize(result));
}
})
.catch(err => {
this.handleCommandError(err, command, ci, ctx, callback);
});
}
catch (err) {
this.handleCommandError(err, command, ci, ctx, callback);
}
}
invokeEvent(ef, ctx, event, callback) {
const finalize = (results) => {
let noncircularResults = results;
try {
JSON.stringify(noncircularResults);
}
catch (err) {
logger_1.logger.error("Circular object returned from event handler: %s", stringify(results));
noncircularResults = results.map(r => ({ code: r.code, message: stringify(r.message) }));
logger_1.logger.error("Substituting for circular object: %j", noncircularResults);
}
this.sendEventStatus(!noncircularResults.some(r => r.code !== 0), ef, event, ctx)
.catch(err => logger_1.logger.warn("Unable to send status for event subscription'%s': %s", event.extensions.operationName, err.message))
.then(() => {
callback(Promise.resolve(noncircularResults));
logger_1.logger.debug(`Finished invocation of event subscription '%s': %s`, event.extensions.operationName, stringify(noncircularResults, possibleAxiosObjectReplacer));
this.clearNamespace();
});
};
logger_1.logger.debug("Incoming event subscription '%s'", stringify(event, string_1.replacer));
try {
this.automations.onEvent(ef, ctx)
.then(result => {
if (!result || result.length === 0) {
return [defaultResult(ctx)];
}
else {
return result;
}
})
.then(result => ctx.lifecycle ? ctx.lifecycle.dispose().then(() => result) : result)
.then(result => {
if (!result.some(r => r.code !== 0)) {
this.listeners.map(l => () => l.eventSuccessful(ef, ctx, result))
.reduce((p, f) => p.then(f), Promise.resolve())
.then(() => finalize(result));
}
else {
this.listeners.map(l => () => l.eventFailed(ef, ctx, result))
.reduce((p, f) => p.then(f), Promise.resolve())
.then(() => finalize(result));
}
})
.catch(err => {
this.handleEventError(err, event, ef, ctx, callback);
});
}
catch (err) {
this.handleEventError(err, event, ef, ctx, callback);
}
}
createAndWrapMessageClient(event, context) {
return new MessageClientSupport_1.DefaultSlackMessageClient(new AutomationEventListenerEnabledMessageClient(context, this.createMessageClient(event, context), this.listeners), context.graphClient);
}
setupNamespace(request, automations, invocationId = string_1.guid(), ts = Date.now()) {
return {
correlationId: _.get(request, "correlation_id") || _.get(request, "extensions.correlation_id"),
workspaceId: _.get(request, "team.id") || _.get(request, "extensions.team_id"),
workspaceName: _.get(request, "team.name") || _.get(request, "extensions.team_name"),
operation: _.get(request, "command") || _.get(request, "extensions.operationName"),
name: automations.automations.name,
version: automations.automations.version,
invocationId,
ts,
};
}
clearNamespace() {
namespace.set({
correlationId: null,
workspaceId: null,
workspaceName: null,
operation: null,
name: null,
version: null,
invocationId: null,
ts: null,
});
}
handleCommandError(err, command, ci, ctx, callback) {
const result = Object.assign(Object.assign({}, defaultErrorResult(ctx)), HandlerResult_1.failure(err));
this.listeners.map(l => () => l.commandFailed(ci, ctx, err))
.reduce((p, f) => p.then(f), Promise.resolve())
.then(() => {
return this.sendCommandStatus(false, result.code, command, ctx)
.then(() => {
if (callback) {
callback(Promise.resolve(result));
}
if (err instanceof Error) {
logger_1.logger.error(`Failed invocation of command '%s': %s`, command.command, err.message);
logger_1.logger.error(err.stack);
}
else {
logger_1.logger.error(`Failed invocation of command '%s'`, command.command);
}
this.clearNamespace();
})
.catch(error => logger_1.logger.warn("Unable to send status for command: " + stringify(command)));
});
}
handleEventError(err, event, ef, ctx, callback) {
const result = Object.assign(Object.assign({}, defaultErrorResult(ctx)), HandlerResult_1.failure(err));
this.listeners.map(l => () => l.eventFailed(ef, ctx, err))
.reduce((p, f) => p.then(f), Promise.resolve())
.then(() => {
return this.sendEventStatus(false, ef, event, ctx)
.then(() => {
if (callback) {
callback(Promise.resolve(result));
}
if (err instanceof Error) {
logger_1.logger.error(`Failed invocation of event subscription '%s': %s`, event.extensions.operationName, err.message);
logger_1.logger.error(err.stack);
}
else {
logger_1.logger.error(`Failed invocation of event subscription '%s'`, event.extensions.operationName);
}
this.clearNamespace();
})
.catch(error => logger_1.logger.warn("Unable to send status for event subscription: " + stringify(event)));
});
}
}
exports.AbstractRequestProcessor = AbstractRequestProcessor;
class AutomationEventListenerEnabledMessageClient {
constructor(ctx, delegate, listeners = []) {
this.ctx = ctx;
this.delegate = delegate;
this.listeners = listeners;
}
respond(msg, options) {
return __awaiter(this, void 0, void 0, function* () {
const newMsg = yield this.listeners.map(l => m => l.messageSending(m.message || msg, [], m.options || options, this.ctx))
.reduce((p, f) => p.then(f), Promise.resolve({ message: msg, destinations: [], options }));
globals_1.eventStore().recordMessage(newMsg.options && newMsg.options.id ? newMsg.options.id : string_1.guid(), this.ctx.correlationId, newMsg.message);
yield this.delegate.respond(newMsg.message, newMsg.options);
return Promise.all(this.listeners.map(l => l.messageSent(newMsg.message, [], newMsg.options, this.ctx)));
});
}
send(msg, destinations, options) {
return __awaiter(this, void 0, void 0, function* () {
const newMsg = yield this.listeners.map(l => m => l.messageSending(m.message || msg, m.destinations || destinations, m.options || options, this.ctx))
.reduce((p, f) => p.then(f), Promise.resolve({ message: msg, destinations, options }));
globals_1.eventStore().recordMessage(newMsg.options && newMsg.options.id ? newMsg.options.id : string_1.guid(), this.ctx.correlationId, newMsg.message);
yield this.delegate.send(newMsg.message, newMsg.destinations, newMsg.options);
return Promise.all(this.listeners.map(l => l.messageSent(newMsg.message, newMsg.destinations, newMsg.options, this.ctx)));
});
}
delete(destinations, options) {
return __awaiter(this, void 0, void 0, function* () {
return this.delegate.delete(destinations, options);
});
}
}
function defaultResult(context) {
const result = {
code: 0,
correlation_id: context.context.correlationId,
invocation_id: context.context.invocationId,
};
return result;
}
exports.defaultResult = defaultResult;
function defaultErrorResult(context) {
const result = Object.assign(Object.assign({}, defaultResult(context)), { code: 1, message: `Command '${context.context.operation}' failed` });
return result;
}
exports.defaultErrorResult = defaultErrorResult;
function possibleAxiosObjectReplacer(key, value) {
if ((key === "request" || key === "response") && !!value && stringify(value).length > 200) {
return `<...elided because it might be a really long axios ${key}...>`;
}
else {
return value;
}
}
exports.possibleAxiosObjectReplacer = possibleAxiosObjectReplacer;
//# sourceMappingURL=AbstractRequestProcessor.js.map