Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b82ae2bb8b | |||
| 6e206bc29b |
Binary file not shown.
@@ -1728,7 +1728,6 @@ enum FeatureFlagKey {
|
||||
IS_PUBLIC_DOMAIN_ENABLED
|
||||
IS_EMAILING_DOMAIN_ENABLED
|
||||
IS_JUNCTION_RELATIONS_ENABLED
|
||||
IS_DRAFT_EMAIL_ENABLED
|
||||
IS_USAGE_ANALYTICS_ENABLED
|
||||
IS_RICH_TEXT_V1_MIGRATED
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
|
||||
|
||||
@@ -1424,7 +1424,7 @@ export interface PublicFeatureFlag {
|
||||
__typename: 'PublicFeatureFlag'
|
||||
}
|
||||
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
|
||||
|
||||
export interface ClientConfigMaintenanceMode {
|
||||
startAt: Scalars['DateTime']
|
||||
@@ -9096,7 +9096,6 @@ export const enumFeatureFlagKey = {
|
||||
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
|
||||
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
|
||||
IS_DRAFT_EMAIL_ENABLED: 'IS_DRAFT_EMAIL_ENABLED' as const,
|
||||
IS_USAGE_ANALYTICS_ENABLED: 'IS_USAGE_ANALYTICS_ENABLED' as const,
|
||||
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
|
||||
|
||||
@@ -1724,7 +1724,6 @@ export enum FeatureFlagKey {
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED',
|
||||
IS_DATASOURCE_MIGRATED = 'IS_DATASOURCE_MIGRATED',
|
||||
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
|
||||
+1
-8
@@ -27,9 +27,6 @@ export const SidePanelWorkflowSelectAction = ({
|
||||
onActionSelected: (selection: WorkflowActionSelection) => void;
|
||||
}) => {
|
||||
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
|
||||
const isDraftEmailEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_DRAFT_EMAIL_ENABLED,
|
||||
);
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
@@ -37,10 +34,6 @@ export const SidePanelWorkflowSelectAction = ({
|
||||
|
||||
const toolFunctions = logicFunctions.filter((fn) => fn.isTool === true);
|
||||
|
||||
const coreActions = isDraftEmailEnabled
|
||||
? CORE_ACTIONS
|
||||
: CORE_ACTIONS.filter((action) => action.type !== 'DRAFT_EMAIL');
|
||||
|
||||
const handleActionClick = (actionType: WorkflowActionType) => {
|
||||
onActionSelected({ type: actionType });
|
||||
};
|
||||
@@ -88,7 +81,7 @@ export const SidePanelWorkflowSelectAction = ({
|
||||
{t`Core`}
|
||||
</SidePanelWorkflowSelectStepTitle>
|
||||
<WorkflowActionMenuItems
|
||||
actions={coreActions}
|
||||
actions={CORE_ACTIONS}
|
||||
onClick={handleActionClick}
|
||||
/>
|
||||
|
||||
|
||||
-10
@@ -3,7 +3,6 @@ import { AuthGuard } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
@@ -13,7 +12,6 @@ import { GoogleAPIsOauthExchangeCodeForTokenStrategy } from 'src/engine/core-mod
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -29,7 +27,6 @@ export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -54,15 +51,8 @@ export class GoogleAPIsOauthExchangeCodeForTokenGuard extends AuthGuard(
|
||||
state.transientToken,
|
||||
);
|
||||
|
||||
const isDraftEmailEnabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_DRAFT_EMAIL_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
new GoogleAPIsOauthExchangeCodeForTokenStrategy(
|
||||
this.twentyConfigService,
|
||||
isDraftEmailEnabled,
|
||||
);
|
||||
|
||||
setRequestExtraParams(request, {
|
||||
|
||||
+1
-13
@@ -3,7 +3,6 @@ import { AuthGuard } from '@nestjs/passport';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
@@ -13,7 +12,6 @@ import { GoogleAPIsOauthRequestCodeStrategy } from 'src/engine/core-modules/auth
|
||||
import { TransientTokenService } from 'src/engine/core-modules/auth/token/services/transient-token.service';
|
||||
import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util';
|
||||
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { GuardRedirectService } from 'src/engine/core-modules/guard-redirect/services/guard-redirect.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -27,7 +25,6 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly workspaceDomainsService: WorkspaceDomainsService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {
|
||||
super({
|
||||
prompt: 'select_account',
|
||||
@@ -71,16 +68,7 @@ export class GoogleAPIsOauthRequestCodeGuard extends AuthGuard('google-apis') {
|
||||
);
|
||||
}
|
||||
|
||||
const isDraftEmailEnabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_DRAFT_EMAIL_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
new GoogleAPIsOauthRequestCodeStrategy(
|
||||
this.twentyConfigService,
|
||||
isDraftEmailEnabled,
|
||||
);
|
||||
new GoogleAPIsOauthRequestCodeStrategy(this.twentyConfigService);
|
||||
|
||||
return (await super.canActivate(context)) as boolean;
|
||||
} catch (err) {
|
||||
|
||||
@@ -30,7 +30,6 @@ export class GoogleAPIScopesService {
|
||||
|
||||
public async getScopesFromGoogleAccessTokenAndCheckIfExpectedScopesArePresent(
|
||||
accessToken: string,
|
||||
isDraftEmailEnabled = false,
|
||||
): Promise<{ scopes: string[]; isValid: boolean }> {
|
||||
try {
|
||||
const httpClient = this.secureHttpClientService.getHttpClient();
|
||||
@@ -41,7 +40,7 @@ export class GoogleAPIScopesService {
|
||||
);
|
||||
|
||||
const scopes = response.data.scope.split(' ');
|
||||
const expectedScopes = getGoogleApisOauthScopes(isDraftEmailEnabled);
|
||||
const expectedScopes = getGoogleApisOauthScopes();
|
||||
|
||||
return {
|
||||
scopes,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountProvider, FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
@@ -13,7 +13,6 @@ import { CreateMessageChannelService } from 'src/engine/core-modules/auth/servic
|
||||
import { GoogleAPIScopesService } from 'src/engine/core-modules/auth/services/google-apis-scopes';
|
||||
import { GoogleApisServiceAvailabilityService } from 'src/engine/core-modules/auth/services/google-apis-service-availability.service';
|
||||
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
@@ -65,7 +64,6 @@ export class GoogleAPIsService {
|
||||
private readonly updateConnectedAccountOnReconnectService: UpdateConnectedAccountOnReconnectService,
|
||||
private readonly googleAPIScopesService: GoogleAPIScopesService,
|
||||
private readonly googleApisServiceAvailabilityService: GoogleApisServiceAvailabilityService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
@@ -98,15 +96,9 @@ export class GoogleAPIsService {
|
||||
'MESSAGING_PROVIDER_GMAIL_ENABLED',
|
||||
);
|
||||
|
||||
const isDraftEmailEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_DRAFT_EMAIL_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const { scopes, isValid } =
|
||||
await this.googleAPIScopesService.getScopesFromGoogleAccessTokenAndCheckIfExpectedScopesArePresent(
|
||||
input.accessToken,
|
||||
isDraftEmailEnabled,
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
|
||||
+2
-5
@@ -14,11 +14,8 @@ export abstract class GoogleAPIsOauthCommonStrategy extends PassportStrategy(
|
||||
Strategy,
|
||||
'google-apis',
|
||||
) {
|
||||
constructor(
|
||||
twentyConfigService: TwentyConfigService,
|
||||
isDraftEmailEnabled = false,
|
||||
) {
|
||||
const scopes = getGoogleApisOauthScopes(isDraftEmailEnabled);
|
||||
constructor(twentyConfigService: TwentyConfigService) {
|
||||
const scopes = getGoogleApisOauthScopes();
|
||||
|
||||
super({
|
||||
clientID: twentyConfigService.get('AUTH_GOOGLE_CLIENT_ID'),
|
||||
|
||||
+2
-5
@@ -13,11 +13,8 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent
|
||||
|
||||
@Injectable()
|
||||
export class GoogleAPIsOauthExchangeCodeForTokenStrategy extends GoogleAPIsOauthCommonStrategy {
|
||||
constructor(
|
||||
twentyConfigService: TwentyConfigService,
|
||||
isDraftEmailEnabled = false,
|
||||
) {
|
||||
super(twentyConfigService, isDraftEmailEnabled);
|
||||
constructor(twentyConfigService: TwentyConfigService) {
|
||||
super(twentyConfigService);
|
||||
}
|
||||
|
||||
async validate(
|
||||
|
||||
+2
-5
@@ -12,11 +12,8 @@ export type GoogleAPIScopeConfig = {
|
||||
|
||||
@Injectable()
|
||||
export class GoogleAPIsOauthRequestCodeStrategy extends GoogleAPIsOauthCommonStrategy {
|
||||
constructor(
|
||||
twentyConfigService: TwentyConfigService,
|
||||
isDraftEmailEnabled = false,
|
||||
) {
|
||||
super(twentyConfigService, isDraftEmailEnabled);
|
||||
constructor(twentyConfigService: TwentyConfigService) {
|
||||
super(twentyConfigService);
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line @typescripttypescript/no-explicit-any
|
||||
|
||||
+2
-4
@@ -1,7 +1,7 @@
|
||||
/** email, profile and openid permission can be called without the https://www.googleapis.com/auth/ prefix
|
||||
* see https://developers.google.com/identity/protocols/oauth2/scopes
|
||||
*/
|
||||
export const getGoogleApisOauthScopes = (isDraftEmailEnabled = false) => {
|
||||
export const getGoogleApisOauthScopes = () => {
|
||||
return [
|
||||
'email',
|
||||
'profile',
|
||||
@@ -9,8 +9,6 @@ export const getGoogleApisOauthScopes = (isDraftEmailEnabled = false) => {
|
||||
'https://www.googleapis.com/auth/calendar.events',
|
||||
'https://www.googleapis.com/auth/profile.emails.read',
|
||||
'https://www.googleapis.com/auth/gmail.send',
|
||||
...(isDraftEmailEnabled
|
||||
? ['https://www.googleapis.com/auth/gmail.compose']
|
||||
: []),
|
||||
'https://www.googleapis.com/auth/gmail.compose',
|
||||
];
|
||||
};
|
||||
|
||||
+5
-10
@@ -1,9 +1,8 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { render, toPlainText } from '@react-email/render';
|
||||
import { toPlainText } from '@react-email/render';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { reactMarkupFromJSON } from 'twenty-emails';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { WorkflowAttachment } from 'twenty-shared/workflow';
|
||||
@@ -19,14 +18,13 @@ import {
|
||||
import { EmailComposerResult } from 'src/engine/core-modules/tool/tools/email-tool/types/email-composer-result.type';
|
||||
import { EmailToolInput } from 'src/engine/core-modules/tool/tools/email-tool/types/email-tool-input.type';
|
||||
import { parseCommaSeparatedEmails } from 'src/engine/core-modules/tool/tools/email-tool/utils/parse-comma-separated-emails.util';
|
||||
import { ToolExecutionContext } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
import { type ToolExecutionContext } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service';
|
||||
import { type MessageAttachment } from 'src/modules/messaging/message-import-manager/types/message';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { parseEmailBody } from 'src/utils/parse-email-body';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
@Injectable()
|
||||
export class EmailComposerService {
|
||||
@@ -288,15 +286,12 @@ export class EmailComposerService {
|
||||
|
||||
const attachments = await this.getAttachments(files || [], workspaceId);
|
||||
|
||||
const parsedBody = parseEmailBody(body);
|
||||
const reactMarkup = reactMarkupFromJSON(parsedBody);
|
||||
const htmlBody = await render(reactMarkup);
|
||||
const plainTextBody = toPlainText(htmlBody);
|
||||
|
||||
const { JSDOM } = await import('jsdom');
|
||||
const window = new JSDOM('').window;
|
||||
const purify = DOMPurify(window);
|
||||
const sanitizedHtmlBody = purify.sanitize(htmlBody || '');
|
||||
|
||||
const sanitizedHtmlBody = purify.sanitize(body || '');
|
||||
const plainTextBody = toPlainText(sanitizedHtmlBody);
|
||||
const sanitizedSubject = purify.sanitize(subject || '');
|
||||
|
||||
return {
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ export const EmailToolInputZodSchema = z.object({
|
||||
'Recipients object with to, cc, and bcc fields (comma-separated)',
|
||||
),
|
||||
subject: z.string().describe('The email subject line'),
|
||||
body: z.string().describe('The email body content (HTML or plain text)'),
|
||||
body: z.string().describe('The email body content in HTML format'),
|
||||
connectedAccountId: z
|
||||
.string()
|
||||
.refine((val) => isValidUuid(val))
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { render } from '@react-email/render';
|
||||
import { type JSONContent, reactMarkupFromJSON } from 'twenty-emails';
|
||||
|
||||
export const renderRichTextToHtml = async (
|
||||
jsonContent: JSONContent,
|
||||
): Promise<string> => {
|
||||
const reactMarkup = reactMarkupFromJSON(jsonContent);
|
||||
|
||||
return render(reactMarkup);
|
||||
};
|
||||
-1
@@ -238,7 +238,6 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_PUBLIC_DOMAIN_ENABLED: false,
|
||||
IS_EMAILING_DOMAIN_ENABLED: false,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: false,
|
||||
IS_DRAFT_EMAIL_ENABLED: false,
|
||||
IS_USAGE_ANALYTICS_ENABLED: false,
|
||||
IS_RICH_TEXT_V1_MIGRATED: false,
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: false,
|
||||
|
||||
+10
-2
@@ -5,8 +5,9 @@ import { resolveInput, resolveRichTextVariables } from 'twenty-shared/utils';
|
||||
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
||||
|
||||
import { DraftEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/draft-email-tool';
|
||||
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
|
||||
import { SendEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/send-email-tool';
|
||||
import { renderRichTextToHtml } from 'src/engine/core-modules/tool/tools/email-tool/utils/render-rich-text-to-html.util';
|
||||
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
|
||||
import { type ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
|
||||
import { type Tool } from 'src/engine/core-modules/tool/types/tool.type';
|
||||
import {
|
||||
@@ -67,9 +68,16 @@ export class ToolExecutorWorkflowAction implements WorkflowAction {
|
||||
const emailInput = toolInput as WorkflowSendEmailActionInput;
|
||||
|
||||
if (emailInput.body) {
|
||||
const resolvedBody = resolveRichTextVariables(
|
||||
emailInput.body,
|
||||
context,
|
||||
);
|
||||
const bodyJson = JSON.parse(resolvedBody);
|
||||
const htmlBody = await renderRichTextToHtml(bodyJson);
|
||||
|
||||
toolInput = {
|
||||
...emailInput,
|
||||
body: resolveRichTextVariables(emailInput.body, context),
|
||||
body: htmlBody,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { parseEmailBody } from 'src/utils/parse-email-body';
|
||||
|
||||
describe('parseEmailBody', () => {
|
||||
it('should parse valid JSON content', () => {
|
||||
const jsonContent = {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Hello World' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = parseEmailBody(JSON.stringify(jsonContent));
|
||||
|
||||
expect(result).toEqual(jsonContent);
|
||||
});
|
||||
|
||||
it('should return plain string when JSON parsing fails', () => {
|
||||
const plainText = 'This is plain text, not JSON';
|
||||
|
||||
const result = parseEmailBody(plainText);
|
||||
|
||||
expect(result).toBe(plainText);
|
||||
});
|
||||
|
||||
it('should parse JSON content with hardBreak nodes', () => {
|
||||
const jsonWithHardBreaks = {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: 'Line 1' },
|
||||
{ type: 'hardBreak' },
|
||||
{ type: 'text', text: 'Line 2' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = parseEmailBody(JSON.stringify(jsonWithHardBreaks));
|
||||
|
||||
expect(result).toEqual(jsonWithHardBreaks);
|
||||
expect(
|
||||
(result as typeof jsonWithHardBreaks).content[0].content,
|
||||
).toContainEqual({
|
||||
type: 'hardBreak',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const result = parseEmailBody('');
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle JSON array format', () => {
|
||||
const arrayContent = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: 'Content' }],
|
||||
},
|
||||
];
|
||||
|
||||
const result = parseEmailBody(JSON.stringify(arrayContent));
|
||||
|
||||
expect(result).toEqual(arrayContent);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +0,0 @@
|
||||
import { type JSONContent } from 'twenty-emails';
|
||||
|
||||
export const parseEmailBody = (body: string): JSONContent | string => {
|
||||
try {
|
||||
const json = JSON.parse(body);
|
||||
|
||||
return json;
|
||||
} catch {
|
||||
return body;
|
||||
}
|
||||
};
|
||||
@@ -8,7 +8,6 @@ export enum FeatureFlagKey {
|
||||
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
|
||||
IS_USAGE_ANALYTICS_ENABLED = 'IS_USAGE_ANALYTICS_ENABLED',
|
||||
IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED',
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED',
|
||||
|
||||
Reference in New Issue
Block a user