Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(INT-568): slack send event to event specific channel based on channel webhook #2563

Merged
merged 7 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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