snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import type { ILivechatContactChannel, Serialized } from '@rocket.chat/core-typings';
|
||||
|
||||
export const findLastChatFromChannel = (channels: Serialized<ILivechatContactChannel>[] = [], providerId: string) => {
|
||||
const channel = channels.find((channel) => channel.name === providerId);
|
||||
return channel?.lastChat?.ts;
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import type {
|
||||
IOutboundMessage,
|
||||
IOutboundProviderTemplate,
|
||||
TemplateComponent,
|
||||
TemplateParameter as CoreTemplateParameter,
|
||||
} from '@rocket.chat/core-typings';
|
||||
|
||||
import type { SubmitPayload } from '../components/OutboundMessageWizard/forms';
|
||||
import type { MessageFormSubmitPayload } from '../components/OutboundMessageWizard/forms/MessageForm';
|
||||
import type { RecipientFormSubmitPayload } from '../components/OutboundMessageWizard/forms/RecipientForm/RecipientForm';
|
||||
import type { RepliesFormSubmitPayload } from '../components/OutboundMessageWizard/forms/RepliesForm';
|
||||
import type { TemplateParameter, TemplateParameters } from '../types/template';
|
||||
|
||||
export const isRecipientStepValid = (data: Partial<SubmitPayload>): data is Required<RecipientFormSubmitPayload> => {
|
||||
return !!(data.contactId && data.contact && data.provider && data.providerId && data.recipient && data.sender);
|
||||
};
|
||||
|
||||
export const isMessageStepValid = (data: Partial<SubmitPayload>): data is Required<MessageFormSubmitPayload> => {
|
||||
return !!(data.templateId && data.template && data.templateParameters);
|
||||
};
|
||||
|
||||
export const isRepliesStepValid = (data: Partial<SubmitPayload>): data is RepliesFormSubmitPayload => {
|
||||
return (!data.departmentId || !!data.department) && (!data.agentId || (!!data.agent && !!data.departmentId));
|
||||
};
|
||||
|
||||
const formatParameterForOutboundMessage = (parameter: TemplateParameter): CoreTemplateParameter => {
|
||||
switch (parameter.type) {
|
||||
case 'media':
|
||||
return { type: 'media', link: parameter.value, format: parameter.format };
|
||||
default:
|
||||
return { type: 'text', text: parameter.value };
|
||||
}
|
||||
};
|
||||
|
||||
const formatOutboundMessageComponents = (
|
||||
components: IOutboundProviderTemplate['components'],
|
||||
parameters: TemplateParameters,
|
||||
): TemplateComponent[] => {
|
||||
return components.map((component) => {
|
||||
const values = parameters[component.type] || [];
|
||||
return {
|
||||
type: component.type,
|
||||
parameters: values.map(formatParameterForOutboundMessage),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const formatOutboundMessagePayload = ({
|
||||
recipient,
|
||||
sender,
|
||||
template,
|
||||
type = 'template',
|
||||
templateParameters,
|
||||
departmentId,
|
||||
agentId,
|
||||
}: {
|
||||
type: IOutboundMessage['type'];
|
||||
recipient: string;
|
||||
sender: string;
|
||||
template: IOutboundProviderTemplate;
|
||||
templateParameters: TemplateParameters;
|
||||
departmentId?: string;
|
||||
agentId?: string;
|
||||
}): IOutboundMessage => {
|
||||
return {
|
||||
to: recipient,
|
||||
type,
|
||||
templateProviderPhoneNumber: sender,
|
||||
departmentId,
|
||||
agentId,
|
||||
template: {
|
||||
name: template.name,
|
||||
language: {
|
||||
code: template.language,
|
||||
},
|
||||
components: formatOutboundMessageComponents(template.components, templateParameters),
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { IOutboundProviderTemplate } from '@rocket.chat/apps-engine/definition/outboundCommunication';
|
||||
import { capitalize } from '@rocket.chat/tools';
|
||||
|
||||
import { extractParameterMetadata, processTemplatePreviewText } from './template';
|
||||
import type { TemplateMediaParameter, TemplateComponent, TemplateParameterMetadata } from '../types/template';
|
||||
|
||||
const createMockTextMetadata = (templateId: string, type: TemplateComponent['type'], index = 0): TemplateParameterMetadata => {
|
||||
const placeholder = `{{${index + 1}}}`;
|
||||
return {
|
||||
id: `${templateId}.${type}.${placeholder}`,
|
||||
type: 'text',
|
||||
placeholder,
|
||||
name: capitalize(type),
|
||||
index,
|
||||
componentType: type,
|
||||
format: 'text',
|
||||
};
|
||||
};
|
||||
|
||||
const createMockMediaMetadata = (
|
||||
templateId: string,
|
||||
type: TemplateComponent['type'],
|
||||
format: TemplateMediaParameter['format'],
|
||||
): TemplateParameterMetadata => ({
|
||||
id: `${templateId}.${type}.mediaUrl`,
|
||||
type: 'media',
|
||||
placeholder: '',
|
||||
name: 'Media_URL',
|
||||
index: 0,
|
||||
componentType: type,
|
||||
format,
|
||||
});
|
||||
|
||||
const variations: [IOutboundProviderTemplate['components'], TemplateParameterMetadata[]][] = [
|
||||
[[{ type: 'header', text: 'Parameter {{1}}', format: 'image' }], [createMockMediaMetadata('template-1', 'header', 'image')]],
|
||||
[
|
||||
[{ type: 'body', text: 'Parameter {{1}} and {{2}}' }],
|
||||
[createMockTextMetadata('template-1', 'body', 0), createMockTextMetadata('template-1', 'body', 1)],
|
||||
],
|
||||
[[{ type: 'footer', text: 'Parameter {{1}}' }], [createMockTextMetadata('template-1', 'footer', 0)]],
|
||||
];
|
||||
|
||||
describe('extractParameterMetadata', () => {
|
||||
test.each(variations)('should return the parameters metadata for "%i" component type', (components, expected) => {
|
||||
const parametersMetadata = extractParameterMetadata({ id: 'template-1', components });
|
||||
|
||||
expect(parametersMetadata).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processTemplatePreviewText', () => {
|
||||
it('should replate placeholder with the parameter value', () => {
|
||||
const text = processTemplatePreviewText('Hello {{1}}', [{ type: 'text', value: 'World', format: 'text' }]);
|
||||
|
||||
expect(text).toBe('Hello World');
|
||||
});
|
||||
|
||||
it('should keep the placeholder in case the parameter is an empty string', () => {
|
||||
const text = processTemplatePreviewText('Hello {{1}}', [{ type: 'text', value: '', format: 'text' }]);
|
||||
|
||||
expect(text).toBe('Hello {{1}}');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { IOutboundProviderTemplate } from '@rocket.chat/core-typings';
|
||||
import { capitalize } from '@rocket.chat/tools';
|
||||
|
||||
import type { ComponentType, TemplateParameterMetadata, TemplateParameter } from '../types/template';
|
||||
|
||||
const placeholderPattern = new RegExp('{{(.*?)}}', 'g'); // e.g {{1}} or {{text}}
|
||||
|
||||
export const extractParameterMetadata = (template: Pick<IOutboundProviderTemplate, 'id' | 'components'>) => {
|
||||
if (!template.components?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return template.components.flatMap((component) => {
|
||||
const format = component.type === 'header' ? component.format : 'text';
|
||||
return parseComponentText(template.id, component.type, component.text, format);
|
||||
});
|
||||
};
|
||||
|
||||
export const parseComponentText = (
|
||||
templateId: string,
|
||||
componentType: ComponentType,
|
||||
text: string | undefined,
|
||||
format: TemplateParameter['format'] = 'text',
|
||||
): TemplateParameterMetadata[] => {
|
||||
if (format !== 'text') {
|
||||
return [
|
||||
{
|
||||
id: `${templateId}.${componentType}.mediaUrl`,
|
||||
placeholder: '',
|
||||
name: 'Media_URL',
|
||||
type: 'media',
|
||||
componentType,
|
||||
format,
|
||||
index: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matches = text.match(placeholderPattern) || [];
|
||||
const placeholders = new Set(matches);
|
||||
|
||||
return Array.from(placeholders).map((placeholder, index) => ({
|
||||
id: `${templateId}.${componentType}.${placeholder}`,
|
||||
placeholder,
|
||||
name: capitalize(componentType),
|
||||
type: 'text',
|
||||
componentType,
|
||||
format,
|
||||
index,
|
||||
}));
|
||||
};
|
||||
|
||||
export const replacePlaceholders = (text = '', replacer: (substring: string, captured: number) => string) => {
|
||||
return text.replace(placeholderPattern, (match, captured) => replacer(match, captured));
|
||||
};
|
||||
|
||||
const replaceLineBreaks = (text: string) => {
|
||||
return text.replace(/([^\n])\n(?!\n)/g, '$1 \n');
|
||||
};
|
||||
|
||||
export const processTemplatePreviewText = (text: string, parameters: TemplateParameter[] = []): string => {
|
||||
if (!text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const processedText = replaceLineBreaks(text);
|
||||
|
||||
if (!parameters?.length) {
|
||||
return processedText;
|
||||
}
|
||||
|
||||
return replacePlaceholders(processedText, (placeholder, captured) => {
|
||||
const index = Number(captured) - 1;
|
||||
|
||||
if (!Number.isFinite(index) || index < 0) {
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
const parameter = parameters[index];
|
||||
return parameter?.value || placeholder;
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user