Skip to content

Commit

Permalink
fix(INT-568): slack send event to event specific channel based on cha…
Browse files Browse the repository at this point in the history
…nnel webhook (#2563)

* fix: slack send event to event specific channel

* small fixes

* incorporated legacy method

* added test cases and small fixes

* fixed payload for modern webhooks for not sending app details

* feat:added blacklisted event option
  • Loading branch information
anantjain45823 authored Sep 15, 2023
1 parent 2b38e2c commit 0f3b39e
Show file tree
Hide file tree
Showing 5 changed files with 1,014 additions and 159 deletions.
173 changes: 107 additions & 66 deletions src/v0/destinations/slack/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,54 @@ const {
defaultRequestConfig,
getFieldValueFromMessage,
simpleProcessRouterDest,
isDefinedAndNotNull,
} = require('../../util');
const { InstrumentationError, ConfigurationError } = require('../../util/errorTypes');

// build the response to be sent to backend, url encoded header is required as slack accepts payload in this format
// add the username and image for Rudder
// image currently served from prod CDN
const buildResponse = (payloadJSON, message, destination) => {
const endpoint = destination.Config.webhookUrl;
const buildResponse = (
payloadJSON,
message,
destination,
channelWebhook = null,
sendAppNameAndIcon = true,
) => {
const endpoint = channelWebhook || destination.Config.webhookUrl;
const response = defaultRequestConfig();
response.endpoint = endpoint;
response.method = defaultPostRequestConfig.requestMethod;
response.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
response.userId = message.userId ? message.userId : message.anonymousId;
const payload =
sendAppNameAndIcon === true
? JSON.stringify({
...payloadJSON,
username: SLACK_USER_NAME,
icon_url: SLACK_RUDDER_IMAGE_URL,
})
: JSON.stringify({
...payloadJSON,
});
response.body.FORM = {
payload: JSON.stringify({
...payloadJSON,
username: SLACK_USER_NAME,
icon_url: SLACK_RUDDER_IMAGE_URL,
}),
payload,
};
response.statusCode = 200;
logger.debug(response);
return response;
};

const processIdentify = (message, destination) => {
// debug(JSON.stringify(destination));
const identifyTemplateConfig = destination.Config.identifyTemplate;
const traitsList = getWhiteListedTraits(destination);
const defaultIdentifyTemplate = 'Identified {{name}}';
logger.debug('defaulTraitsList:: ', traitsList);
const uName = getName(message);

// required traitlist ??
/* if (!traitsList || traitsList.length == 0) {
throw Error("traits list in config not present");
} */

const template = Handlebars.compile(
(identifyTemplateConfig
? identifyTemplateConfig.trim().length === 0
? identifyTemplateConfig.trim()?.length === 0
? undefined
: identifyTemplateConfig
: undefined) ||
Expand All @@ -69,7 +76,7 @@ const processIdentify = (message, destination) => {
logger.debug(
'identifyTemplateConfig: ',
(identifyTemplateConfig
? identifyTemplateConfig.trim().length === 0
? identifyTemplateConfig.trim()?.length === 0
? undefined
: identifyTemplateConfig
: undefined) ||
Expand All @@ -95,18 +102,40 @@ const processIdentify = (message, destination) => {
return buildResponse({ text: resultText }, message, destination);
};

function buildChannelList(channelListToSendThisEvent, eventChannelConfig, eventName) {
eventChannelConfig.forEach((channelConfig) => {
const configEventName = channelConfig.eventName
? channelConfig.eventName.trim().length > 0
? channelConfig.eventName
: undefined
: undefined;
const configEventChannel = channelConfig.eventChannel
? channelConfig.eventChannel.trim().length > 0
? channelConfig.eventChannel
: undefined
: undefined;
const isEventNameMatchesRegex = (eventName, regex) => eventName.match(regex)?.length > 0;

const getChannelForEventName = (eventChannelSettings, eventName) => {
for (const channelConfig of eventChannelSettings) {
const configEventName =
channelConfig?.eventName?.trim()?.length > 0 ? channelConfig.eventName : null;
const channelWebhook =
channelConfig?.eventChannelWebhook?.length > 0 ? channelConfig.eventChannelWebhook : null;

if (configEventName && isDefinedAndNotNull(channelWebhook)) {
if (channelConfig.eventRegex) {
logger.debug('regex: ', `${configEventName} trying to match with ${eventName}`);
logger.debug(
'match:: ',
configEventName,
eventName,
eventName.match(new RegExp(configEventName, 'g')),
);
if (isEventNameMatchesRegex(eventName, new RegExp(configEventName, 'g'))) {
return channelWebhook;
}
} else if (channelConfig.eventName === eventName) {
return channelWebhook;
}
}
}
return null;
};
const getChannelNameForEvent = (eventChannelSettings, eventName) => {
for (const channelConfig of eventChannelSettings) {
const configEventName =
channelConfig?.eventName?.trim()?.length > 0 ? channelConfig.eventName : null;
const configEventChannel =
channelConfig?.eventChannel?.trim()?.length > 0 ? channelConfig.eventChannel : null;
if (configEventName && configEventChannel) {
if (channelConfig.eventRegex) {
logger.debug('regex: ', `${configEventName} trying to match with ${eventName}`);
Expand All @@ -116,79 +145,84 @@ function buildChannelList(channelListToSendThisEvent, eventChannelConfig, eventN
eventName,
eventName.match(new RegExp(configEventName, 'g')),
);
if (
eventName.match(new RegExp(configEventName, 'g')) &&
eventName.match(new RegExp(configEventName, 'g')).length > 0
) {
channelListToSendThisEvent.add(configEventChannel);
if (isEventNameMatchesRegex(eventName, new RegExp(configEventName, 'g'))) {
return configEventChannel;
}
} else if (configEventName === eventName) {
channelListToSendThisEvent.add(configEventChannel);
return configEventChannel;
}
}
});
}
}
return null;
};

function buildtemplateList(templateListForThisEvent, eventTemplateConfig, eventName) {
eventTemplateConfig.forEach((templateConfig) => {
const configEventName = templateConfig.eventName
? templateConfig.eventName.trim().length > 0
? templateConfig.eventName
: undefined
: undefined;
const buildtemplateList = (templateListForThisEvent, eventTemplateSettings, eventName) => {
eventTemplateSettings.forEach((templateConfig) => {
const configEventName =
templateConfig?.eventName?.trim()?.length > 0 ? templateConfig.eventName : undefined;
const configEventTemplate = templateConfig.eventTemplate
? templateConfig.eventTemplate.trim().length > 0
? templateConfig.eventTemplate.trim()?.length > 0
? templateConfig.eventTemplate
: undefined
: undefined;
if (configEventName && configEventTemplate) {
if (templateConfig.eventRegex) {
if (
eventName.match(new RegExp(configEventName, 'g')) &&
eventName.match(new RegExp(configEventName, 'g')).length > 0
) {
if (isEventNameMatchesRegex(eventName, new RegExp(configEventName, 'g'))) {
templateListForThisEvent.add(configEventTemplate);
}
} else if (configEventName === eventName) {
templateListForThisEvent.add(configEventTemplate);
}
}
});
}
};

const processTrack = (message, destination) => {
// logger.debug(JSON.stringify(destination));
const eventChannelConfig = destination.Config.eventChannelSettings;
const eventTemplateConfig = destination.Config.eventTemplateSettings;
const { Config } = destination;
const { eventChannelSettings, eventTemplateSettings, incomingWebhooksType, blacklistedEvents } =
Config;
const eventName = message.event;

if (!message.event) {
if (!eventName) {
throw new InstrumentationError('Event name is required');
}
const eventName = message.event;
const channelListToSendThisEvent = new Set();
if (blacklistedEvents?.length > 0) {
const blackListedEvents = blacklistedEvents.map((item) => item.eventName);
if (blackListedEvents.includes(eventName)) {
throw new ConfigurationError('Event is blacklisted. Please check configuration.');
}
}

const templateListForThisEvent = new Set();
const traitsList = getWhiteListedTraits(destination);

// Add global context to regex always
// build the channel list and templatelist for the event, pick the first in case of multiple
// using set to filter out
// document this behaviour
/* Add global context to regex always
* build the channel list and template list for the event, pick the first in case of multiple
* using set to filter out
* document this behaviour
*/

// building channel list
buildChannelList(channelListToSendThisEvent, eventChannelConfig, eventName);
const channelListArray = Array.from(channelListToSendThisEvent);
// getting specific channel for event if available

let channelWebhook;
let channelName;
if (incomingWebhooksType && incomingWebhooksType === 'modern') {
channelWebhook = getChannelForEventName(eventChannelSettings, eventName);
} else {
// default
channelName = getChannelNameForEvent(eventChannelSettings, eventName);
}

// building templatelist
buildtemplateList(templateListForThisEvent, eventTemplateConfig, eventName);
buildtemplateList(templateListForThisEvent, eventTemplateSettings, eventName);
const templateListArray = Array.from(templateListForThisEvent);

logger.debug(
'templateListForThisEvent: ',
templateListArray,
templateListArray.length > 0 ? templateListArray[0] : undefined,
);
logger.debug('channelListToSendThisEvent: ', channelListArray);

// track event default handlebar expression
const defaultTemplate = '{{name}} did {{event}}';
const template = templateListArray
Expand Down Expand Up @@ -219,9 +253,16 @@ const processTrack = (message, destination) => {
} catch (err) {
throw new ConfigurationError(`Something is wrong with the event template: '${template}'`);
}

if (channelListArray && channelListArray.length > 0) {
return buildResponse({ channel: channelListArray[0], text: resultText }, message, destination);
if (incomingWebhooksType === 'modern' && channelWebhook) {
return buildResponse({ text: resultText }, message, destination, channelWebhook, false);
}
if (channelName) {
return buildResponse(
{ channel: channelName, text: resultText },
message,
destination,
channelWebhook,
);
}
return buildResponse({ text: resultText }, message, destination);
};
Expand Down
6 changes: 4 additions & 2 deletions src/v0/destinations/slack/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,10 @@ const stringifyJSON = (json, whiteListedTraits) => {
return output;
};

// build default identify template
// if whitelisted traits are present build on it else build the entire traits object
/* build default identify template
* if whitelisted traits are present build on it
* else build the entire traits object
*/
const buildDefaultTraitTemplate = (traitsList, traits, template) => {
let generatedStringFromTemplate = template;
// build template with whitelisted traits
Expand Down
Loading

0 comments on commit 0f3b39e

Please sign in to comment.