@onereach/channel-transformer
Version:
Convert data model between different channels (Slack, Microsoft Teams, Generic...)
274 lines (235 loc) • 7.8 kB
text/typescript
import { IGenericSection, IGenericText, IGenericFields } from '../../../generic/types';
import { ISlackSection, ISlackActions, ISlackInput, ISlackImage } from '../../types';
import { getMethods } from '../methods';
function transformSection ({ props, children }: IGenericSection): ISlackActions;
function transformSection ({ props, children }: IGenericSection): Array<ISlackSection|ISlackActions>;
function transformSection ({ props, children }: IGenericSection) {
const flattenedSections = flattenSection(props, children);
const result = flattenedSections.reduce((acc, item) => {
const { type, props, children } = item;
const itemType = props?.slack?.type || type;
if (itemType === 'input' && children.length === 1) { // if no label => transform as actions
return acc.concat(getMethods('inputWrapper')({ props, children }));
} else if (itemType === 'context' || itemType === 'header') {
return acc.concat(getMethods(itemType)({ props, children }));
} else if (itemType === 'actions') {
return acc.concat(transformActions({ props, children }));
} else if (itemType === 'fields') {
const result = getMethods(itemType)({ props, children });
return Array.isArray(result)
? acc.concat({
type: 'section',
fields: result
})
: acc.concat(result);
} else {
return acc.concat(transformRegularSection({ props, children }));
}
}, []);
const block_id = props?.slack?.block_id;
if (block_id && result[0] && !result[0].block_id) {
result[0].block_id = block_id;
}
return result;
}
// HELPER FUNCTIONS
function flat (arr) {
if (!Array.isArray(arr)) return arr;
return arr.reduce((acc, item) => {
return acc.concat(flat(item));
}, []);
}
function flattenSection (props, children) {
const isSection = item => {
const sectionTypes = ['section', 'context', 'header'];
return sectionTypes.includes(item.type);
};
const result = [];
let section = { props, children: [] };
children.forEach(item => {
if (isSection(item)) {
if (section.children.length > 0) {
result.push(section);
result.push(item);
section = { props: { type: 'section' }, children: [] };
} else {
result.push(item);
}
} else {
section.children.push(item);
}
});
if (section.children.length > 0) {
result.push(section);
}
return result;
}
function transformActions ({ props, children }): ISlackActions {
const section: ISlackActions = {
type: 'actions',
elements: children.map(child => getMethods(child.type)(child))
};
const block_id = props?.slack?.block_id;
if (block_id) section.block_id = block_id;
return section;
}
function isInteractiveType (type: string): boolean {
return [
'button',
'checkbox',
'datePicker',
'radio',
'select',
'timePicker'
].includes(type);
}
function transformRegularSection ({ props, children }): Array<ISlackSection|ISlackActions|ISlackInput> {
const additionalSections: Array<ISlackSection|ISlackActions> = [];
const block_id = props?.slack?.block_id;
const result: Array<ISlackSection|ISlackActions|ISlackInput> = [];
let res;
for (let i = 0; i < children.length; i++) {
const el = children[i];
if (el.type === 'text') {
if (!res) {
res = {
type: 'section'
} as ISlackSection;
}
if (res.type === 'section' && res.text || res.type !== 'section') {
result.push(res);
res = {
type: 'section'
};
}
res.text = getMethods(el.type)(el);
} else if (isInteractiveType(el.type) || el.type === 'input') {
const slackElement = getMethods(el.type)(el);
if (slackElement.type === 'input') {
res && result.push(res);
result.push(slackElement);
res = null;
} else if (res?.type === 'section' && !res.accessory) {
res.accessory = slackElement;
} else {
if (res?.type === 'section') {
result.push(res);
res = {
type: 'actions',
elements: []
} as ISlackActions;
res.elements.push(slackElement);
} else if (res?.type === 'actions') {
res.elements.push(slackElement);
} else {
res = {
type: 'actions',
elements: [slackElement]
} as ISlackActions;
}
}
} else if (el.type === 'image') {
const slackElement = getMethods(el.type)(el);
if (!res) {
result.push(slackElement);
} else if (res?.type === 'section' && res.accessory || res?.type !== 'section') {
result.push(res, slackElement);
res = null;
} else {
delete slackElement.title;
res.accessory = slackElement;
}
} else if (el.type === 'section') {
const convertedSection = getMethods(el.type)(el);
result.push(...convertedSection);
}
}
if (res) {
result.push(res);
}
if (block_id && result[0]) {
result[0].block_id = block_id;
}
// console.log('transformRegularSection', { children, result });
return result;
const textObjects: Array<IGenericText> = children.filter(item => item.type === 'text');
// TODO: implement fields converting
const fields: Array<IGenericFields> = children.filter(
item => item.type === 'fields' || item.props?.slack?.type === 'fields'
);
const elements = children.filter(
item => !['text', 'fields'].includes(item.type) && !['text', 'fields'].includes(item.props?.slack?.type)
);
if (textObjects.length) {
res.text = concatSectionTextObjects(textObjects);
}
if (fields.length) {
const transformed = transformFieldElements(fields);
if (transformed.fields) {
res.fields = transformed.fields;
}
additionalSections.push(...transformed.sections);
}
if (elements.length === 1) {
res.accessory = getMethods(elements[0].type)(elements[0]);
} else if (elements.length > 1) {
const actSection: ISlackActions = {
type: 'actions',
elements: children.map(child => getMethods(child.type)(child))
};
additionalSections.push(actSection);
}
if (!res.text && !res.fields && !res.accessory) {
return flat(additionalSections);
}
return [res, ...flat(additionalSections)];
}
function concatSectionTextObjects (children, init=null) {
const transformed = children.map(item => getMethods(item.type)(item));
// concatenate values from all text objects
const initial = Object.assign({ type: 'mrkdwn' }, transformed[0], { text: '' }, init);
return transformed
.reduce(
(acc, item) => {
if(acc.type === 'plain_text' && item.type === 'mrkdwn') {
delete acc.emoji;
acc.type = 'mrkdwn';
}
acc.text = `${acc.text} ${item.text}`.trim();
return acc;
},
initial
);
}
function transformFieldElements (elements) {
const fields = Array.isArray(elements) ? [...elements] : [elements];
const result = {
fields: null,
sections: []
};
const slackFields = fields.map(item => getMethods(item.type)(item));
if (
Array.isArray(slackFields[0]) &&
['mrkdwn', 'plain_text'].includes(slackFields[0][0].type)
) {
result.fields = slackFields[0];
if (slackFields.length > 1) {
// create a new section for each additional field
result.sections.push(
...slackFields.slice(1).map(item => {
const section: ISlackSection = {
type: 'section',
fields: item
};
return section;
})
);
}
} else {
result.sections.push(...slackFields);
}
return result;
}
export {
transformSection
};