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

This commit is contained in:
upstream-mirror
2026-09-16 01:21:02 +05:00
commit 30810eecd7
10161 changed files with 987110 additions and 0 deletions

View File

@@ -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;
};

View File

@@ -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),
},
};
};

View File

@@ -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}}');
});
});

View File

@@ -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;
});
};