Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
094cc09878 | ||
|
|
46e7f23df1 | ||
|
|
6f3541fd7c | ||
|
|
863f3f29a2 | ||
|
|
9264480824 | ||
|
|
5ff8c3b219 | ||
|
|
ff28547a36 | ||
|
|
dde6df7a26 | ||
|
|
92b0e07617 | ||
|
|
951ead5a1e | ||
|
|
e79f43813b |
+8
-4
@@ -48,12 +48,16 @@ const StyledSidePanelPlaceholderWrapper = styled.div`
|
||||
export const TimelineCard = () => {
|
||||
const targetRecord = useTargetRecord();
|
||||
const { isInSidePanel } = useLayoutRenderingContext();
|
||||
const { timelineActivities, loading, fetchMoreRecords } =
|
||||
useTimelineActivities(targetRecord);
|
||||
const {
|
||||
timelineActivities,
|
||||
firstQueryLoading,
|
||||
loadingMore,
|
||||
fetchMoreRecords,
|
||||
} = useTimelineActivities(targetRecord);
|
||||
|
||||
const isTimelineActivitiesEmpty = timelineActivities.length === 0;
|
||||
|
||||
if (loading === true) {
|
||||
if (firstQueryLoading === true) {
|
||||
return <SkeletonLoader withSubSections />;
|
||||
}
|
||||
|
||||
@@ -92,7 +96,7 @@ export const TimelineCard = () => {
|
||||
events={timelineActivities ?? []}
|
||||
/>
|
||||
<CustomResolverFetchMoreLoader
|
||||
loading={loading}
|
||||
loading={loadingMore}
|
||||
onLastRowVisible={fetchMoreRecords}
|
||||
/>
|
||||
</StyledMainContainer>
|
||||
|
||||
+8
-3
@@ -2,9 +2,9 @@ import { useLinkedObjectsTitle } from '@/activities/timeline-activities/hooks/us
|
||||
import { type TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
|
||||
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
// do we need to test this?
|
||||
@@ -62,11 +62,16 @@ export const useTimelineActivities = (
|
||||
|
||||
useLinkedObjectsTitle(activityIds);
|
||||
|
||||
const loading = loadingTimelineActivities;
|
||||
const firstQueryLoading =
|
||||
loadingTimelineActivities && timelineActivities.length === 0;
|
||||
|
||||
const loadingMore =
|
||||
loadingTimelineActivities && timelineActivities.length > 0;
|
||||
|
||||
return {
|
||||
timelineActivities,
|
||||
loading,
|
||||
firstQueryLoading,
|
||||
loadingMore,
|
||||
fetchMoreRecords,
|
||||
};
|
||||
};
|
||||
|
||||
+3
@@ -74,6 +74,9 @@ export const SettingsAccountsBlocklistInput = ({
|
||||
});
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.nativeEvent.isComposing || e.keyCode === 229) {
|
||||
return;
|
||||
}
|
||||
if (e.key === Key.Enter) {
|
||||
submit();
|
||||
}
|
||||
|
||||
+13
-16
@@ -24,7 +24,6 @@ import {
|
||||
TooltipDelay,
|
||||
TooltipPosition,
|
||||
} from 'twenty-ui/display';
|
||||
import { MenuItemIconBoxContainer } from 'twenty-ui/navigation';
|
||||
import {
|
||||
MOBILE_VIEWPORT,
|
||||
ThemeContext,
|
||||
@@ -162,7 +161,7 @@ const StyledLabelParent = styled.div`
|
||||
`;
|
||||
|
||||
const StyledItemLabel = styled.span`
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
`;
|
||||
|
||||
const StyledItemSecondaryLabel = styled.span`
|
||||
@@ -350,20 +349,18 @@ export const NavigationDrawerItem = ({
|
||||
</StyledIcon>
|
||||
) : (
|
||||
<StyledIcon>
|
||||
<MenuItemIconBoxContainer>
|
||||
<Icon
|
||||
style={{
|
||||
minWidth: theme.icon.size.md,
|
||||
}}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={
|
||||
showBreadcrumb && !isExpanded
|
||||
? theme.font.color.light
|
||||
: 'currentColor'
|
||||
}
|
||||
/>
|
||||
</MenuItemIconBoxContainer>
|
||||
<Icon
|
||||
style={{
|
||||
minWidth: theme.icon.size.md,
|
||||
}}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={
|
||||
showBreadcrumb && !isExpanded
|
||||
? theme.font.color.light
|
||||
: 'currentColor'
|
||||
}
|
||||
/>
|
||||
</StyledIcon>
|
||||
))}
|
||||
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ const StyledTitle = styled.div`
|
||||
justify-content: space-between;
|
||||
padding-bottom: ${themeCssVariables.spacing[1]};
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
padding-right: ${themeCssVariables.spacing['0.5']};
|
||||
padding-top: ${themeCssVariables.spacing[1]};
|
||||
|
||||
&:hover {
|
||||
background-color: ${themeCssVariables.background.transparent.light};
|
||||
|
||||
+4
@@ -48,6 +48,10 @@ export const useHotkeysOnFocusedElement = ({
|
||||
return useHotkeys(
|
||||
keys,
|
||||
(keyboardEvent, hotkeysEvent) => {
|
||||
if (keyboardEvent.isComposing || keyboardEvent.keyCode === 229) {
|
||||
return;
|
||||
}
|
||||
|
||||
callScopedHotkeyCallback({
|
||||
keyboardEvent,
|
||||
hotkeysEvent,
|
||||
|
||||
+2
-3
@@ -15,7 +15,7 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
import { HorizontalSeparator } from 'twenty-ui/display';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
import { type JsonValue } from 'type-fest';
|
||||
@@ -74,9 +74,8 @@ export const WorkflowEditActionCreateRecord = ({
|
||||
const availableMetadata: Array<SelectOption<string>> =
|
||||
activeNonSystemObjectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
canObjectBeManagedByWorkflow({
|
||||
canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadataItem.nameSingular,
|
||||
isSystem: objectMetadataItem.isSystem,
|
||||
}),
|
||||
)
|
||||
.map((item) => ({
|
||||
|
||||
+2
-3
@@ -11,7 +11,7 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS
|
||||
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
|
||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
import { HorizontalSeparator } from 'twenty-ui/display';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
import { type JsonValue } from 'type-fest';
|
||||
@@ -46,9 +46,8 @@ export const WorkflowEditActionDeleteRecord = ({
|
||||
const availableMetadata: Array<SelectOption<string>> =
|
||||
activeNonSystemObjectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
canObjectBeManagedByWorkflow({
|
||||
canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadataItem.nameSingular,
|
||||
isSystem: objectMetadataItem.isSystem,
|
||||
}),
|
||||
)
|
||||
.map((item) => ({
|
||||
|
||||
+2
-3
@@ -16,7 +16,7 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
import { HorizontalSeparator } from 'twenty-ui/display';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
import { type JsonValue } from 'type-fest';
|
||||
@@ -47,9 +47,8 @@ export const WorkflowEditActionUpdateRecord = ({
|
||||
const availableMetadata: Array<SelectOption<string>> =
|
||||
activeNonSystemObjectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
canObjectBeManagedByWorkflow({
|
||||
canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadataItem.nameSingular,
|
||||
isSystem: objectMetadataItem.isSystem,
|
||||
}),
|
||||
)
|
||||
.map((item) => ({
|
||||
|
||||
+2
-3
@@ -17,7 +17,7 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
import { HorizontalSeparator } from 'twenty-ui/display';
|
||||
import { type SelectOption } from 'twenty-ui/input';
|
||||
import { type JsonValue } from 'type-fest';
|
||||
@@ -84,9 +84,8 @@ export const WorkflowEditActionUpsertRecord = ({
|
||||
const availableMetadata: Array<SelectOption<string>> =
|
||||
activeNonSystemObjectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
canObjectBeManagedByWorkflow({
|
||||
canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadataItem.nameSingular,
|
||||
isSystem: objectMetadataItem.isSystem,
|
||||
}),
|
||||
)
|
||||
.map((item) => ({
|
||||
|
||||
@@ -141,6 +141,9 @@ export const CreateWorkspace = () => {
|
||||
);
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.nativeEvent.isComposing || event.keyCode === 229) {
|
||||
return;
|
||||
}
|
||||
if (event.key === Key.Enter) {
|
||||
event.preventDefault();
|
||||
handleSubmit(onSubmit)();
|
||||
|
||||
+3
@@ -159,6 +159,9 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
placeholder={t`E.g. backoffice integration`}
|
||||
value={formValues.name}
|
||||
onKeyDown={(e) => {
|
||||
if (e.nativeEvent.isComposing || e.keyCode === 229) {
|
||||
return;
|
||||
}
|
||||
if (e.key === Key.Enter) {
|
||||
handleSave();
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ export class DeployCommand {
|
||||
|
||||
await checkSdkVersionCompatibility(appPath);
|
||||
|
||||
console.log(chalk.blue('Deploying application...'));
|
||||
const remoteName = options.remote ?? ConfigService.getActiveRemote();
|
||||
|
||||
console.log(chalk.blue(`Deploying application on ${remoteName}...`));
|
||||
console.log(chalk.gray(`App path: ${appPath}\n`));
|
||||
|
||||
const onProgress = (message: string) => console.log(chalk.gray(message));
|
||||
@@ -85,7 +87,6 @@ export class DeployCommand {
|
||||
|
||||
const appName = packageJson?.name ?? result.data.name;
|
||||
const appVersion = packageJson?.version ?? 'unknown';
|
||||
const remoteName = ConfigService.getActiveRemote();
|
||||
|
||||
console.log(
|
||||
chalk.green(`\n✓ Published ${appName} v${appVersion} to ${remoteName}\n`),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { appInstall } from '@/cli/operations/install';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { checkSdkVersionCompatibility } from '@/cli/utilities/version/check-sdk-version-compatibility';
|
||||
import { checkServerVersionCompatibility } from '@/cli/utilities/version/check-server-version-compatibility';
|
||||
@@ -16,7 +17,9 @@ export class AppInstallCommand {
|
||||
await checkSdkVersionCompatibility(appPath);
|
||||
await checkServerVersionCompatibility();
|
||||
|
||||
console.log(chalk.blue('Installing application...'));
|
||||
const remoteName = options.remote ?? ConfigService.getActiveRemote();
|
||||
|
||||
console.log(chalk.blue(`Installing application on ${remoteName}...`));
|
||||
console.log(chalk.gray(`App path: ${appPath}\n`));
|
||||
|
||||
const result = await appInstall({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { appUninstall } from '@/cli/operations/uninstall';
|
||||
import { type ApiResponse } from '@/cli/utilities/api/api-response-type';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
@@ -12,7 +13,9 @@ export class AppUninstallCommand {
|
||||
appPath?: string;
|
||||
askForConfirmation: boolean;
|
||||
}): Promise<ApiResponse<any>> {
|
||||
console.log(chalk.blue('🚀 Uninstall Twenty Application'));
|
||||
const remoteName = ConfigService.getActiveRemote();
|
||||
|
||||
console.log(chalk.blue(`🚀 Uninstall Twenty Application on ${remoteName}`));
|
||||
console.log(chalk.gray(`📁 App Path: ${appPath}\n`));
|
||||
|
||||
if (askForConfirmation && !(await this.confirmationPrompt())) {
|
||||
|
||||
@@ -12,7 +12,9 @@ export class CatalogSyncCommand {
|
||||
ConfigService.setActiveRemote(options.remote);
|
||||
}
|
||||
|
||||
console.log(chalk.blue('Syncing marketplace catalog...'));
|
||||
const remoteName = ConfigService.getActiveRemote();
|
||||
|
||||
console.log(chalk.blue(`Syncing marketplace catalog on ${remoteName}...`));
|
||||
|
||||
const apiService = new ApiService();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { appDevOnce } from '@/cli/operations/dev-once';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { checkSdkVersionCompatibility } from '@/cli/utilities/version/check-sdk-version-compatibility';
|
||||
import chalk from 'chalk';
|
||||
@@ -14,7 +15,9 @@ export class AppDevOnceCommand {
|
||||
|
||||
await checkSdkVersionCompatibility(appPath);
|
||||
|
||||
console.log(chalk.blue('Syncing application...'));
|
||||
const remoteName = ConfigService.getActiveRemote();
|
||||
|
||||
console.log(chalk.blue(`Syncing application on ${remoteName}...`));
|
||||
console.log(chalk.gray(`App path: ${appPath}\n`));
|
||||
|
||||
const result = await appDevOnce({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { functionExecute } from '@/cli/operations/execute';
|
||||
import { APP_ERROR_CODES, FUNCTION_ERROR_CODES } from '@/cli/types';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import chalk from 'chalk';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -36,7 +37,11 @@ export class LogicFunctionExecuteCommand {
|
||||
? 'pre install'
|
||||
: (functionUniversalIdentifier ?? functionName);
|
||||
|
||||
console.log(chalk.blue(`🚀 Executing function "${identifier}"...`));
|
||||
const remoteName = ConfigService.getActiveRemote();
|
||||
|
||||
console.log(
|
||||
chalk.blue(`🚀 Executing function "${identifier}" on ${remoteName}...`),
|
||||
);
|
||||
console.log(chalk.gray(` Payload: ${JSON.stringify(parsedPayload)}\n`));
|
||||
|
||||
const executeOptions = postInstall
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import chalk from 'chalk';
|
||||
import { readManifestFromFile } from '@/cli/utilities/build/manifest/manifest-reader';
|
||||
@@ -58,8 +59,12 @@ export class LogicFunctionLogsCommand {
|
||||
? `function "${functionUniversalIdentifier || functionName}"`
|
||||
: 'functions';
|
||||
|
||||
const remoteName = ConfigService.getActiveRemote();
|
||||
|
||||
console.log(
|
||||
chalk.blue(`🚀 Watching ${appPath} ${functionIdentifier} logs:\n`),
|
||||
chalk.blue(
|
||||
`🚀 Watching ${appPath} ${functionIdentifier} logs on ${remoteName}:\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,14 +91,20 @@ export class DevModeOrchestrator {
|
||||
await ensureDir(outputDir);
|
||||
await emptyDir(outputDir);
|
||||
|
||||
this.state.addEvent({
|
||||
message: `Using remote "${ConfigService.getActiveRemote()}"`,
|
||||
status: 'info',
|
||||
});
|
||||
|
||||
if (!this.verbose) {
|
||||
this.state.addEvent({
|
||||
message: 'Add --verbose to see fully detailed logs',
|
||||
status: 'info',
|
||||
});
|
||||
this.state.notify();
|
||||
}
|
||||
|
||||
this.state.notify();
|
||||
|
||||
await this.startWatchersStep.start();
|
||||
|
||||
this.serverCheckInterval = setInterval(() => {
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Bladsyuitleg nie gevind nie."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "لم يتم العثور على تخطيط الصفحة."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "No s'ha trobat la disposició de pàgina."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Rozložení stránky nebylo nalezeno."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Sidelayoutet blev ikke fundet."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Seitenlayout nicht gefunden."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Δεν βρέθηκε η διάταξη σελίδας."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5203,6 +5203,8 @@ msgstr "Page layout not found."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Diseño de página no encontrado."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Sivuasettelua ei löytynyt."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Mise en page introuvable."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "פריסת העמוד לא נמצאה."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Az oldalelrendezés nem található."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Layout della pagina non trovato."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "ページレイアウトが見つかりません."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "페이지 레이아웃을 찾을 수 없습니다."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Pagina-indeling niet gevonden."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Sideoppsett ikke funnet."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Nie znaleziono układu strony."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5203,6 +5203,8 @@ msgstr ""
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Layout de página não encontrado."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Layout de página não encontrado."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Aspectul paginii nu a fost găsit."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Макет страницы не найден."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Распоред странице није пронађен."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5210,6 +5210,8 @@ msgstr "Sidlayouten hittades inte."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Sayfa düzeni bulunamadı."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Макет сторінки не знайдено."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "Không tìm thấy bố cục trang."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "未找到页面布局."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
@@ -5208,6 +5208,8 @@ msgstr "找不到頁面版面配置."
|
||||
#. js-lingui-id: qJ9V6q
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
#: src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-tab-validator.service.ts
|
||||
|
||||
+30
-25
@@ -41,7 +41,6 @@ import { ASSET_PATH } from 'src/constants/assets-path';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
|
||||
import { COMMON_LAYER_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/logic-function/logic-function-drivers/constants/common-layer-dependencies-dirname';
|
||||
import { callWithTimeout } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout';
|
||||
import { copyBuilder } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-builder';
|
||||
import { copyCommonLayerDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-common-layer-dependencies';
|
||||
import { copyExecutor } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-executor';
|
||||
@@ -429,17 +428,18 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
|
||||
const yarnInstallFunctionName = await this.getYarnInstallFunctionName();
|
||||
|
||||
const result = await callWithTimeout({
|
||||
callback: () =>
|
||||
lambdaClient.send(
|
||||
new InvokeCommand({
|
||||
FunctionName: yarnInstallFunctionName,
|
||||
Payload: JSON.stringify(payload),
|
||||
LogType: LogType.Tail,
|
||||
}),
|
||||
const result = await lambdaClient.send(
|
||||
new InvokeCommand({
|
||||
FunctionName: yarnInstallFunctionName,
|
||||
Payload: JSON.stringify(payload),
|
||||
LogType: LogType.Tail,
|
||||
}),
|
||||
{
|
||||
abortSignal: AbortSignal.timeout(
|
||||
YARN_INSTALL_LAMBDA_TIMEOUT_SECONDS * 1000,
|
||||
),
|
||||
timeoutMs: YARN_INSTALL_LAMBDA_TIMEOUT_SECONDS * 1000,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (result.FunctionError) {
|
||||
const parsedResult = result.Payload
|
||||
@@ -529,17 +529,16 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
builtFileName,
|
||||
};
|
||||
|
||||
const result = await callWithTimeout({
|
||||
callback: () =>
|
||||
lambdaClient.send(
|
||||
new InvokeCommand({
|
||||
FunctionName: builderFunctionName,
|
||||
Payload: JSON.stringify(payload),
|
||||
LogType: LogType.Tail,
|
||||
}),
|
||||
),
|
||||
timeoutMs: BUILDER_LAMBDA_TIMEOUT_SECONDS * 1000,
|
||||
});
|
||||
const result = await lambdaClient.send(
|
||||
new InvokeCommand({
|
||||
FunctionName: builderFunctionName,
|
||||
Payload: JSON.stringify(payload),
|
||||
LogType: LogType.Tail,
|
||||
}),
|
||||
{
|
||||
abortSignal: AbortSignal.timeout(BUILDER_LAMBDA_TIMEOUT_SECONDS * 1000),
|
||||
},
|
||||
);
|
||||
|
||||
if (result.FunctionError) {
|
||||
const parsedResult = result.Payload
|
||||
@@ -1195,9 +1194,8 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
const lambdaClient = await this.getLambdaClient();
|
||||
|
||||
const invokeStart = Date.now();
|
||||
const result = await callWithTimeout({
|
||||
callback: () => lambdaClient.send(command),
|
||||
timeoutMs,
|
||||
const result = await lambdaClient.send(command, {
|
||||
abortSignal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
const invokeSendMs = Date.now() - invokeStart;
|
||||
|
||||
@@ -1248,6 +1246,13 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.name === 'TimeoutError') {
|
||||
throw new LogicFunctionException(
|
||||
`Lambda invocation timed out for function '${flatLogicFunction.id}': ${error.message}`,
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_EXECUTION_TIMEOUT,
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof LogicFunctionException) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
export const callWithTimeout = async <T>({
|
||||
callback,
|
||||
timeoutMs,
|
||||
}: {
|
||||
callback: () => Promise<T>;
|
||||
timeoutMs: number;
|
||||
}): Promise<T> => {
|
||||
return Promise.race([
|
||||
callback(),
|
||||
new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Execution timed out')), timeoutMs),
|
||||
),
|
||||
]);
|
||||
};
|
||||
+3
-4
@@ -1,7 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
|
||||
import { CommonCreateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service';
|
||||
import {
|
||||
@@ -38,13 +38,12 @@ export class CreateManyRecordsService {
|
||||
});
|
||||
|
||||
if (
|
||||
!canObjectBeManagedByWorkflow({
|
||||
!canObjectBeManagedByAutomation({
|
||||
nameSingular: flatObjectMetadata.nameSingular,
|
||||
isSystem: flatObjectMetadata.isSystem,
|
||||
})
|
||||
) {
|
||||
throw new RecordCrudException(
|
||||
'Failed to create: Object cannot be created by workflow',
|
||||
'Failed to create: Object cannot be created by automation',
|
||||
RecordCrudExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
+3
-4
@@ -1,7 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
|
||||
import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-one-query-runner.service';
|
||||
import {
|
||||
@@ -38,13 +38,12 @@ export class CreateRecordService {
|
||||
});
|
||||
|
||||
if (
|
||||
!canObjectBeManagedByWorkflow({
|
||||
!canObjectBeManagedByAutomation({
|
||||
nameSingular: flatObjectMetadata.nameSingular,
|
||||
isSystem: flatObjectMetadata.isSystem,
|
||||
})
|
||||
) {
|
||||
throw new RecordCrudException(
|
||||
'Failed to create: Object cannot be created by workflow',
|
||||
'Failed to create: Object cannot be created by automation',
|
||||
RecordCrudExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
+3
-4
@@ -1,7 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
|
||||
import { CommonDeleteOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-one-query-runner.service';
|
||||
import { CommonDestroyOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service';
|
||||
@@ -42,13 +42,12 @@ export class DeleteRecordService {
|
||||
});
|
||||
|
||||
if (
|
||||
!canObjectBeManagedByWorkflow({
|
||||
!canObjectBeManagedByAutomation({
|
||||
nameSingular: flatObjectMetadata.nameSingular,
|
||||
isSystem: flatObjectMetadata.isSystem,
|
||||
})
|
||||
) {
|
||||
throw new RecordCrudException(
|
||||
'Failed to delete: Object cannot be deleted by workflow',
|
||||
'Failed to delete: Object cannot be deleted by automation',
|
||||
RecordCrudExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
+3
-4
@@ -1,6 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
|
||||
import { CommonUpdateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-many-query-runner.service';
|
||||
import {
|
||||
@@ -37,13 +37,12 @@ export class UpdateManyRecordsService {
|
||||
});
|
||||
|
||||
if (
|
||||
!canObjectBeManagedByWorkflow({
|
||||
!canObjectBeManagedByAutomation({
|
||||
nameSingular: flatObjectMetadata.nameSingular,
|
||||
isSystem: flatObjectMetadata.isSystem,
|
||||
})
|
||||
) {
|
||||
throw new RecordCrudException(
|
||||
'Failed to update: Object cannot be updated by workflow',
|
||||
'Failed to update: Object cannot be updated by automation',
|
||||
RecordCrudExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
+3
-4
@@ -1,7 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
|
||||
import { CommonUpdateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-one-query-runner.service';
|
||||
import {
|
||||
@@ -52,13 +52,12 @@ export class UpdateRecordService {
|
||||
});
|
||||
|
||||
if (
|
||||
!canObjectBeManagedByWorkflow({
|
||||
!canObjectBeManagedByAutomation({
|
||||
nameSingular: flatObjectMetadata.nameSingular,
|
||||
isSystem: flatObjectMetadata.isSystem,
|
||||
})
|
||||
) {
|
||||
throw new RecordCrudException(
|
||||
'Failed to update: Object cannot be updated by workflow',
|
||||
'Failed to update: Object cannot be updated by automation',
|
||||
RecordCrudExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
+3
-4
@@ -1,6 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
|
||||
import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-one-query-runner.service';
|
||||
import {
|
||||
@@ -32,13 +32,12 @@ export class UpsertRecordService {
|
||||
});
|
||||
|
||||
if (
|
||||
!canObjectBeManagedByWorkflow({
|
||||
!canObjectBeManagedByAutomation({
|
||||
nameSingular: flatObjectMetadata.nameSingular,
|
||||
isSystem: flatObjectMetadata.isSystem,
|
||||
})
|
||||
) {
|
||||
throw new RecordCrudException(
|
||||
'Failed to update: Object cannot be updated by workflow',
|
||||
'Failed to upsert: Object cannot be upserted by automation',
|
||||
RecordCrudExceptionCode.INVALID_REQUEST,
|
||||
);
|
||||
}
|
||||
|
||||
+6
-2
@@ -5,6 +5,7 @@ import {
|
||||
type ObjectsPermissionsByRoleId,
|
||||
} from 'twenty-shared/types';
|
||||
import { camelToSnakeCase, isDefined } from 'twenty-shared/utils';
|
||||
import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { type GenerateDescriptorOptions } from 'src/engine/core-modules/tool-provider/interfaces/generate-descriptor-options.type';
|
||||
@@ -116,6 +117,9 @@ export class DatabaseToolProvider implements ToolProvider {
|
||||
const restrictedFields = permission.restrictedFields;
|
||||
const snakePlural = camelToSnakeCase(objectMetadata.namePlural);
|
||||
const snakeSingular = camelToSnakeCase(objectMetadata.nameSingular);
|
||||
const canBeManagedByAutomation = canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadata.nameSingular,
|
||||
});
|
||||
|
||||
if (permission.canReadObjectRecords) {
|
||||
descriptors.push({
|
||||
@@ -182,7 +186,7 @@ export class DatabaseToolProvider implements ToolProvider {
|
||||
}
|
||||
}
|
||||
|
||||
if (permission.canUpdateObjectRecords) {
|
||||
if (permission.canUpdateObjectRecords && canBeManagedByAutomation) {
|
||||
descriptors.push({
|
||||
name: `create_${snakeSingular}`,
|
||||
description: `Create a new ${objectMetadata.labelSingular} record. Provide all required fields and any optional fields you want to set. The system will automatically handle timestamps and IDs. Returns the created record with all its data.`,
|
||||
@@ -266,7 +270,7 @@ export class DatabaseToolProvider implements ToolProvider {
|
||||
});
|
||||
}
|
||||
|
||||
if (permission.canSoftDeleteObjectRecords) {
|
||||
if (permission.canSoftDeleteObjectRecords && canBeManagedByAutomation) {
|
||||
descriptors.push({
|
||||
name: `delete_${snakeSingular}`,
|
||||
description: `Delete a ${objectMetadata.labelSingular} record by marking it as deleted. The record is hidden from normal queries. This is reversible. Use this to remove records.`,
|
||||
|
||||
+1
@@ -214,6 +214,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"position",
|
||||
"universalConfiguration",
|
||||
"deletedAt",
|
||||
"pageLayoutTabUniversalIdentifier",
|
||||
"conditionalDisplay",
|
||||
"conditionalAvailabilityExpression",
|
||||
"isActive",
|
||||
|
||||
+1
-1
@@ -930,7 +930,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
universalProperty: undefined,
|
||||
},
|
||||
pageLayoutTabId: {
|
||||
toCompare: false,
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: 'pageLayoutTabUniversalIdentifier',
|
||||
isOverridable: true,
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { PageLayoutTabLayoutMode } from 'twenty-shared/types';
|
||||
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { FlatPageLayoutWidgetTypeValidatorService } from 'src/engine/metadata-modules/flat-page-layout-widget/services/flat-page-layout-widget-type-validator.service';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { PageLayoutTabExceptionCode } from 'src/engine/metadata-modules/page-layout-tab/exceptions/page-layout-tab.exception';
|
||||
import { type UniversalFlatPageLayoutTab } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-page-layout-tab.type';
|
||||
import { FlatPageLayoutWidgetValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service';
|
||||
|
||||
const EXISTING_TAB_UNIVERSAL_IDENTIFIER =
|
||||
'00000000-0000-0000-0000-000000000aa1';
|
||||
const MISSING_TAB_UNIVERSAL_IDENTIFIER = '00000000-0000-0000-0000-000000000aa2';
|
||||
const DESTINATION_TAB_UNIVERSAL_IDENTIFIER =
|
||||
'00000000-0000-0000-0000-000000000aa3';
|
||||
const WIDGET_UNIVERSAL_IDENTIFIER = '00000000-0000-0000-0000-000000000111';
|
||||
|
||||
const tab = (
|
||||
universalIdentifier = EXISTING_TAB_UNIVERSAL_IDENTIFIER,
|
||||
): UniversalFlatPageLayoutTab =>
|
||||
({
|
||||
universalIdentifier,
|
||||
layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST,
|
||||
}) as unknown as UniversalFlatPageLayoutTab;
|
||||
|
||||
const widget = (
|
||||
universalIdentifier = WIDGET_UNIVERSAL_IDENTIFIER,
|
||||
pageLayoutTabUniversalIdentifier = EXISTING_TAB_UNIVERSAL_IDENTIFIER,
|
||||
): FlatPageLayoutWidget =>
|
||||
({
|
||||
universalIdentifier,
|
||||
pageLayoutTabUniversalIdentifier,
|
||||
title: 'widget',
|
||||
type: 'FRONT_COMPONENT',
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 12, columnSpan: 12 },
|
||||
position: { layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, index: 0 },
|
||||
}) as unknown as FlatPageLayoutWidget;
|
||||
|
||||
const mapsFrom = (entities: { universalIdentifier: string }[]): any => {
|
||||
const maps = createEmptyFlatEntityMaps() as any;
|
||||
|
||||
for (const entity of entities) {
|
||||
maps.byUniversalIdentifier[entity.universalIdentifier] = entity;
|
||||
}
|
||||
|
||||
return maps;
|
||||
};
|
||||
|
||||
const buildUpdateArgs = ({
|
||||
update,
|
||||
tabs = [tab()],
|
||||
existingWidgets = [widget()],
|
||||
}: {
|
||||
update: Record<string, unknown>;
|
||||
tabs?: UniversalFlatPageLayoutTab[];
|
||||
existingWidgets?: FlatPageLayoutWidget[];
|
||||
}) =>
|
||||
({
|
||||
universalIdentifier: WIDGET_UNIVERSAL_IDENTIFIER,
|
||||
flatEntityUpdate: update,
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
|
||||
flatPageLayoutTabMaps: mapsFrom(tabs),
|
||||
flatPageLayoutWidgetMaps: mapsFrom(existingWidgets),
|
||||
},
|
||||
additionalCacheDataMaps: { featureFlagsMap: {} },
|
||||
workspaceId: 'workspace-id',
|
||||
buildOptions: {} as never,
|
||||
}) as unknown as Parameters<
|
||||
FlatPageLayoutWidgetValidatorService['validateFlatPageLayoutWidgetUpdate']
|
||||
>[0];
|
||||
|
||||
describe('FlatPageLayoutWidgetValidatorService', () => {
|
||||
let service: FlatPageLayoutWidgetValidatorService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleRef: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
FlatPageLayoutWidgetValidatorService,
|
||||
{
|
||||
provide: FlatPageLayoutWidgetTypeValidatorService,
|
||||
useValue: {
|
||||
validateFlatPageLayoutWidgetTypeSpecificitiesForCreation: () => [],
|
||||
validateFlatPageLayoutWidgetTypeSpecificitiesForUpdate: () => [],
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = moduleRef.get(FlatPageLayoutWidgetValidatorService);
|
||||
});
|
||||
|
||||
describe('validateFlatPageLayoutWidgetUpdate', () => {
|
||||
it('rejects moving a widget to an unknown tab', async () => {
|
||||
const result = await service.validateFlatPageLayoutWidgetUpdate(
|
||||
buildUpdateArgs({
|
||||
update: {
|
||||
pageLayoutTabUniversalIdentifier: MISSING_TAB_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.errors.map((error) => error.code)).toContain(
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects moving an overridden widget to an unknown tab', async () => {
|
||||
const result = await service.validateFlatPageLayoutWidgetUpdate(
|
||||
buildUpdateArgs({
|
||||
update: {
|
||||
universalOverrides: {
|
||||
pageLayoutTabUniversalIdentifier:
|
||||
MISSING_TAB_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.errors.map((error) => error.code)).toContain(
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts moving an overridden widget to a known tab and exposes the override target as the effective tab', async () => {
|
||||
const result = await service.validateFlatPageLayoutWidgetUpdate(
|
||||
buildUpdateArgs({
|
||||
update: {
|
||||
universalOverrides: {
|
||||
pageLayoutTabUniversalIdentifier:
|
||||
DESTINATION_TAB_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
},
|
||||
tabs: [tab(), tab(DESTINATION_TAB_UNIVERSAL_IDENTIFIER)],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.errors.map((error) => error.code)).not.toContain(
|
||||
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
);
|
||||
expect(
|
||||
result.flatEntityMinimalInformation.pageLayoutTabUniversalIdentifier,
|
||||
).toBe(DESTINATION_TAB_UNIVERSAL_IDENTIFIER);
|
||||
});
|
||||
});
|
||||
});
|
||||
+28
-3
@@ -17,6 +17,7 @@ import { validatePageLayoutWidgetGridPosition } from 'src/engine/metadata-module
|
||||
import { validatePageLayoutWidgetVerticalListPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-page-layout-widget-vertical-list-position.util';
|
||||
import { validateWidgetGridPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-grid-position.util';
|
||||
import { type UniversalFlatPageLayoutTab } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-page-layout-tab.type';
|
||||
import { type UniversalFlatPageLayoutWidget } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-page-layout-widget.type';
|
||||
import {
|
||||
FailedFlatEntityValidation,
|
||||
FlatEntityValidationError,
|
||||
@@ -70,19 +71,31 @@ export class FlatPageLayoutWidgetValidatorService {
|
||||
...flatEntityUpdate,
|
||||
};
|
||||
|
||||
const effectivePageLayoutTabUniversalIdentifier =
|
||||
this.getEffectivePageLayoutTabUniversalIdentifier(
|
||||
updatedFlatPageLayoutWidget,
|
||||
);
|
||||
|
||||
validationResult.flatEntityMinimalInformation = {
|
||||
...validationResult.flatEntityMinimalInformation,
|
||||
pageLayoutTabUniversalIdentifier:
|
||||
updatedFlatPageLayoutWidget.pageLayoutTabUniversalIdentifier,
|
||||
effectivePageLayoutTabUniversalIdentifier,
|
||||
};
|
||||
|
||||
const referencedPageLayoutTab = findFlatEntityByUniversalIdentifier({
|
||||
universalIdentifier:
|
||||
updatedFlatPageLayoutWidget.pageLayoutTabUniversalIdentifier,
|
||||
universalIdentifier: effectivePageLayoutTabUniversalIdentifier,
|
||||
flatEntityMaps:
|
||||
optimisticFlatEntityMapsAndRelatedFlatEntityMaps.flatPageLayoutTabMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(referencedPageLayoutTab)) {
|
||||
validationResult.errors.push({
|
||||
code: PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
|
||||
message: t`Page layout tab not found`,
|
||||
userFriendlyMessage: msg`Page layout tab not found`,
|
||||
});
|
||||
}
|
||||
|
||||
const gridPositionErrors = this.validateGridPosition({
|
||||
gridPosition: updatedFlatPageLayoutWidget.gridPosition,
|
||||
widgetTitle: updatedFlatPageLayoutWidget.title,
|
||||
@@ -235,6 +248,18 @@ export class FlatPageLayoutWidgetValidatorService {
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
private getEffectivePageLayoutTabUniversalIdentifier(
|
||||
widget: Pick<
|
||||
UniversalFlatPageLayoutWidget,
|
||||
'pageLayoutTabUniversalIdentifier' | 'universalOverrides'
|
||||
>,
|
||||
): string {
|
||||
return (
|
||||
widget.universalOverrides?.pageLayoutTabUniversalIdentifier ??
|
||||
widget.pageLayoutTabUniversalIdentifier
|
||||
);
|
||||
}
|
||||
|
||||
private validateGridPosition({
|
||||
gridPosition,
|
||||
widgetTitle,
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export type ParsedName = {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { getFirstNameAndLastNameFromHandleAndDisplayName } from 'src/modules/contact-creation-manager/utils/get-first-name-and-last-name-from-handle-and-display-name.util';
|
||||
|
||||
type TestCase = EachTestingContext<{
|
||||
handle: string;
|
||||
displayName: string;
|
||||
expected: { firstName: string; lastName: string };
|
||||
}>;
|
||||
|
||||
describe('getFirstNameAndLastNameFromHandleAndDisplayName', () => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title: 'should parse a standard "First Last" display name',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'John Doe',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should swap "Last, First" comma format',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'Doe, John',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should de-duplicate "First.Last Last" synthesized form',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'John.Doe Doe',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should keep all trailing tokens as a multi-word last name',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'Mary Jane Watson',
|
||||
expected: { firstName: 'Mary', lastName: 'Jane Watson' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should split a single dotted token into first and last',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'john.doe',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should accept a single-word display name and fall back to handle for last',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'First',
|
||||
expected: { firstName: 'First', lastName: 'Someone' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should fall back to handle when display name is empty',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: '',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should fall back to handle even when handle has no dot',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: '',
|
||||
expected: { firstName: 'Noname', lastName: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should strip wrapping quotes before parsing',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: '"Doe, John"',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should collapse extra whitespace',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: ' John Doe ',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should expand "John.Doe Smith" into "John" + "Doe Smith"',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'John.Doe Smith',
|
||||
expected: { firstName: 'John', lastName: 'Doe Smith' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should preserve multi-dot handle local-parts on fallback',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: '',
|
||||
expected: { firstName: 'Jean', lastName: 'Luc picard' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should fall back to handle when display name contains the address',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: '[email protected] Smith',
|
||||
expected: { firstName: 'Jane', lastName: 'Smith' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should fall back to handle for single-token email-as-display-name',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: '[email protected]',
|
||||
expected: { firstName: 'Janesmith', lastName: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should strip ":XXX" group-code suffix from comma-format first name',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'Smith, Jane:GROUP',
|
||||
expected: { firstName: 'Jane', lastName: 'Smith' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should strip ":XXX" suffix from a multi-token display name',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
displayName: 'Jane:GROUP Smith',
|
||||
expected: { firstName: 'Jane', lastName: 'Smith' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)(
|
||||
'$title',
|
||||
({ context: { handle, displayName, expected } }) => {
|
||||
expect(
|
||||
getFirstNameAndLastNameFromHandleAndDisplayName(handle, displayName),
|
||||
).toEqual(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { getParsedNameFromDisplayName } from 'src/modules/contact-creation-manager/utils/get-parsed-name-from-display-name.util';
|
||||
|
||||
type TestCase = EachTestingContext<{
|
||||
displayName: string;
|
||||
expected: { firstName: string; lastName: string };
|
||||
}>;
|
||||
|
||||
describe('getParsedNameFromDisplayName', () => {
|
||||
describe('common shapes', () => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title: 'should split a standard "First Last" display name',
|
||||
context: {
|
||||
displayName: 'John Doe',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should keep all trailing tokens together as a multi-word last name',
|
||||
context: {
|
||||
displayName: 'Mary Jane Watson',
|
||||
expected: { firstName: 'Mary', lastName: 'Jane Watson' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should treat a single-word display name as the first name only',
|
||||
context: {
|
||||
displayName: 'Cher',
|
||||
expected: { firstName: 'Cher', lastName: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should collapse surrounding and inner whitespace',
|
||||
context: {
|
||||
displayName: ' John Doe ',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)('$title', ({ context: { displayName, expected } }) => {
|
||||
expect(getParsedNameFromDisplayName(displayName)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('comma-inverted form "Last, First"', () => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title: 'should swap the two halves into first and last name',
|
||||
context: {
|
||||
displayName: 'Doe, John',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should strip surrounding RFC 5322 quoted-string quotes before swapping',
|
||||
context: {
|
||||
displayName: '"Doe, John"',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)('$title', ({ context: { displayName, expected } }) => {
|
||||
expect(getParsedNameFromDisplayName(displayName)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('email-derived display names', () => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title: 'should split a single dotted token like an email local part',
|
||||
context: {
|
||||
displayName: 'john.doe',
|
||||
expected: { firstName: 'john', lastName: 'doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should de-duplicate "First.Last Last" forms synthesized by some clients',
|
||||
context: {
|
||||
displayName: 'John.Doe Doe',
|
||||
expected: { firstName: 'John', lastName: 'Doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should expand "First.Middle Last" forms into a multi-word last name',
|
||||
context: {
|
||||
displayName: 'John.Doe Smith',
|
||||
expected: { firstName: 'John', lastName: 'Doe Smith' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)('$title', ({ context: { displayName, expected } }) => {
|
||||
expect(getParsedNameFromDisplayName(displayName)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mail-server ":GROUP" tag suffix', () => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title:
|
||||
'should strip the tag from the first name in the comma-inverted form',
|
||||
context: {
|
||||
displayName: 'Smith, Jane:GROUP',
|
||||
expected: { firstName: 'Jane', lastName: 'Smith' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should strip the tag from the first token in a space-separated form',
|
||||
context: {
|
||||
displayName: 'Jane:GROUP Smith',
|
||||
expected: { firstName: 'Jane', lastName: 'Smith' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)('$title', ({ context: { displayName, expected } }) => {
|
||||
expect(getParsedNameFromDisplayName(displayName)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fallback cases (empty result)', () => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title:
|
||||
'should return empty names so the caller can fall back to handle parsing when the display name is empty',
|
||||
context: {
|
||||
displayName: '',
|
||||
expected: { firstName: '', lastName: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should return empty names when the display name is only whitespace',
|
||||
context: {
|
||||
displayName: ' ',
|
||||
expected: { firstName: '', lastName: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should return empty names when forwarders inject an email address into the display-name slot',
|
||||
context: {
|
||||
displayName: '[email protected]',
|
||||
expected: { firstName: '', lastName: '' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)('$title', ({ context: { displayName, expected } }) => {
|
||||
expect(getParsedNameFromDisplayName(displayName)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { getParsedNameFromEmailLocalPart } from 'src/modules/contact-creation-manager/utils/get-parsed-name-from-email-local-part.util';
|
||||
|
||||
type TestCase = EachTestingContext<{
|
||||
localPart: string;
|
||||
expected: { firstName: string; lastName: string };
|
||||
}>;
|
||||
|
||||
describe('getParsedNameFromEmailLocalPart', () => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title:
|
||||
'should split a dotted local part into first and last name segments',
|
||||
context: {
|
||||
localPart: 'john.doe',
|
||||
expected: { firstName: 'john', lastName: 'doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should keep the trailing dot-segments together as a multi-word last name',
|
||||
context: {
|
||||
localPart: 'jean.luc.picard',
|
||||
expected: { firstName: 'jean', lastName: 'luc picard' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should fall back to the whole local part as first name when no dot is present',
|
||||
context: {
|
||||
localPart: 'noname',
|
||||
expected: { firstName: 'noname', lastName: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should ignore consecutive dots so they do not produce empty segments',
|
||||
context: {
|
||||
localPart: 'john..doe',
|
||||
expected: { firstName: 'john', lastName: 'doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should ignore a leading dot rather than producing an empty first name',
|
||||
context: {
|
||||
localPart: '.john.doe',
|
||||
expected: { firstName: 'john', lastName: 'doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should return empty names for an empty local part',
|
||||
context: {
|
||||
localPart: '',
|
||||
expected: { firstName: '', lastName: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should strip an RFC 5233 plus-address tag so routing metadata does not end up in the name',
|
||||
context: {
|
||||
localPart: 'john.doe+sales',
|
||||
expected: { firstName: 'john', lastName: 'doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should strip a plus-address tag even when the local part has no dot',
|
||||
context: {
|
||||
localPart: 'noname+ticket-123',
|
||||
expected: { firstName: 'noname', lastName: '' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)('$title', ({ context: { localPart, expected } }) => {
|
||||
expect(getParsedNameFromEmailLocalPart(localPart)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { getParsedNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-parsed-name-from-handle.util';
|
||||
|
||||
type TestCase = EachTestingContext<{
|
||||
handle: string;
|
||||
expected: { firstName: string; lastName: string };
|
||||
}>;
|
||||
|
||||
describe('getParsedNameFromHandle', () => {
|
||||
const testCases: TestCase[] = [
|
||||
{
|
||||
title:
|
||||
'should derive first and last name from the local part of a standard email',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
expected: { firstName: 'john', lastName: 'doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should ignore the domain entirely',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
expected: { firstName: 'john', lastName: 'doe' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should return only a first name when the local part has no dot separator',
|
||||
context: {
|
||||
handle: '[email protected]',
|
||||
expected: { firstName: 'noname', lastName: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title:
|
||||
'should treat an input without an @ as a bare local part rather than rejecting it',
|
||||
context: {
|
||||
handle: 'lonely.handle',
|
||||
expected: { firstName: 'lonely', lastName: 'handle' },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'should return empty names for an empty handle',
|
||||
context: {
|
||||
handle: '',
|
||||
expected: { firstName: '', lastName: '' },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(testCases)('$title', ({ context: { handle, expected } }) => {
|
||||
expect(getParsedNameFromHandle(handle)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
+12
-11
@@ -1,17 +1,18 @@
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
export function getFirstNameAndLastNameFromHandleAndDisplayName(
|
||||
|
||||
import { type ParsedName } from 'src/modules/contact-creation-manager/types/parsed-name.type';
|
||||
import { getParsedNameFromDisplayName } from 'src/modules/contact-creation-manager/utils/get-parsed-name-from-display-name.util';
|
||||
import { getParsedNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-parsed-name-from-handle.util';
|
||||
|
||||
export const getFirstNameAndLastNameFromHandleAndDisplayName = (
|
||||
handle: string,
|
||||
displayName: string,
|
||||
): { firstName: string; lastName: string } {
|
||||
const firstName = displayName.split(' ')[0];
|
||||
const lastName = displayName.split(' ')[1];
|
||||
|
||||
const contactFullNameFromHandle = handle.split('@')[0];
|
||||
const firstNameFromHandle = contactFullNameFromHandle.split('.')[0];
|
||||
const lastNameFromHandle = contactFullNameFromHandle.split('.')[1];
|
||||
): ParsedName => {
|
||||
const fromDisplayName = getParsedNameFromDisplayName(displayName);
|
||||
const fromHandle = getParsedNameFromHandle(handle);
|
||||
|
||||
return {
|
||||
firstName: capitalize(firstName || firstNameFromHandle || ''),
|
||||
lastName: capitalize(lastName || lastNameFromHandle || ''),
|
||||
firstName: capitalize(fromDisplayName.firstName || fromHandle.firstName),
|
||||
lastName: capitalize(fromDisplayName.lastName || fromHandle.lastName),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ParsedName } from 'src/modules/contact-creation-manager/types/parsed-name.type';
|
||||
import { getParsedNameFromEmailLocalPart } from 'src/modules/contact-creation-manager/utils/get-parsed-name-from-email-local-part.util';
|
||||
|
||||
const EMPTY_NAME: ParsedName = { firstName: '', lastName: '' };
|
||||
|
||||
export const getParsedNameFromDisplayName = (
|
||||
displayName: string,
|
||||
): ParsedName => {
|
||||
const cleaned = displayName
|
||||
.trim()
|
||||
.replace(/^['"]+|['"]+$/g, '')
|
||||
.trim();
|
||||
|
||||
if (!isNonEmptyString(cleaned) || cleaned.includes('@')) return EMPTY_NAME;
|
||||
|
||||
const stripTrailingGroupTag = (input: string): string =>
|
||||
input.replace(/:[^:]+$/, '').trim();
|
||||
|
||||
const withGroupTagsStripped = (parsed: ParsedName): ParsedName => ({
|
||||
firstName: stripTrailingGroupTag(parsed.firstName),
|
||||
lastName: stripTrailingGroupTag(parsed.lastName),
|
||||
});
|
||||
|
||||
const commaMatch = cleaned.match(/^([^,]+),\s*([^,]+)$/);
|
||||
|
||||
if (isDefined(commaMatch)) {
|
||||
return withGroupTagsStripped({
|
||||
firstName: commaMatch[2].trim(),
|
||||
lastName: commaMatch[1].trim(),
|
||||
});
|
||||
}
|
||||
|
||||
const [firstToken, ...rest] = cleaned.split(/\s+/);
|
||||
const restAsLastName = rest.join(' ');
|
||||
const { firstName: head, lastName: dotTail } =
|
||||
getParsedNameFromEmailLocalPart(firstToken);
|
||||
|
||||
if (!isNonEmptyString(dotTail)) {
|
||||
return withGroupTagsStripped({
|
||||
firstName: head,
|
||||
lastName: restAsLastName,
|
||||
});
|
||||
}
|
||||
|
||||
const dotTailAlreadyInLastName = restAsLastName
|
||||
.toLowerCase()
|
||||
.startsWith(dotTail.toLowerCase());
|
||||
|
||||
return withGroupTagsStripped({
|
||||
firstName: head,
|
||||
lastName: dotTailAlreadyInLastName
|
||||
? restAsLastName
|
||||
: `${dotTail} ${restAsLastName}`.trim(),
|
||||
});
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { type ParsedName } from 'src/modules/contact-creation-manager/types/parsed-name.type';
|
||||
|
||||
export const getParsedNameFromEmailLocalPart = (
|
||||
localPart: string,
|
||||
): ParsedName => {
|
||||
const [withoutPlusAddressTag = ''] = localPart.split('+');
|
||||
const parts = withoutPlusAddressTag.split('.').filter(isNonEmptyString);
|
||||
|
||||
return {
|
||||
firstName: parts[0] ?? '',
|
||||
lastName: parts.slice(1).join(' '),
|
||||
};
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { type ParsedName } from 'src/modules/contact-creation-manager/types/parsed-name.type';
|
||||
import { getParsedNameFromEmailLocalPart } from 'src/modules/contact-creation-manager/utils/get-parsed-name-from-email-local-part.util';
|
||||
|
||||
export const getParsedNameFromHandle = (handle: string): ParsedName => {
|
||||
const [localPart = ''] = handle.split('@');
|
||||
|
||||
return getParsedNameFromEmailLocalPart(localPart);
|
||||
};
|
||||
+19
@@ -67,6 +67,25 @@ describe('parseAndFormatGmailMessage', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should preserve the display name on the FROM participant', () => {
|
||||
const result = parseAndFormatGmailMessage(
|
||||
buildMessage([
|
||||
{ name: 'From', value: '"Doe, John" <[email protected]>' },
|
||||
{ name: 'To', value: '[email protected]' },
|
||||
{ name: 'Message-ID', value: '<[email protected]>' },
|
||||
]),
|
||||
connectedAccount,
|
||||
);
|
||||
|
||||
const fromParticipant = result?.participants.find((p) => p.role === 'FROM');
|
||||
|
||||
expect(fromParticipant).toEqual({
|
||||
role: 'FROM',
|
||||
handle: '[email protected]',
|
||||
displayName: 'Doe, John',
|
||||
});
|
||||
});
|
||||
|
||||
it('should mark messages from the connected account as OUTGOING', () => {
|
||||
const result = parseAndFormatGmailMessage(
|
||||
buildMessage([
|
||||
|
||||
+2
-5
@@ -42,10 +42,7 @@ export const parseAndFormatGmailMessage = (
|
||||
: [];
|
||||
|
||||
const participants = [
|
||||
...formatAddressObjectAsParticipants(
|
||||
[{ address: from }],
|
||||
MessageParticipantRole.FROM,
|
||||
),
|
||||
...formatAddressObjectAsParticipants([from], MessageParticipantRole.FROM),
|
||||
...formatAddressObjectAsParticipants(
|
||||
toParticipants,
|
||||
MessageParticipantRole.TO,
|
||||
@@ -72,7 +69,7 @@ export const parseAndFormatGmailMessage = (
|
||||
subject: subject || '',
|
||||
messageThreadExternalId: threadId,
|
||||
receivedAt: new Date(parseInt(internalDate)),
|
||||
direction: computeMessageDirection(from || '', connectedAccount),
|
||||
direction: computeMessageDirection(from.address || '', connectedAccount),
|
||||
participants,
|
||||
text: sanitizeString(textWithoutReplyQuotations),
|
||||
attachments,
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ export const parseGmailMessage = (message: gmail_v1.Schema$Message) => {
|
||||
historyId,
|
||||
internalDate,
|
||||
subject,
|
||||
from: rawFrom ? safeParseEmailAddressAddress(rawFrom) : undefined,
|
||||
from: rawFrom ? safeParseEmailAddresses(rawFrom)[0] : undefined,
|
||||
deliveredTo: rawDeliveredTo
|
||||
? safeParseEmailAddressAddress(rawDeliveredTo)
|
||||
: undefined,
|
||||
|
||||
+89
-1
@@ -1,7 +1,10 @@
|
||||
import { createOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/create-one-page-layout-tab.util';
|
||||
import { destroyOnePageLayoutTab } from 'test/integration/metadata/suites/page-layout-tab/utils/destroy-one-page-layout-tab.util';
|
||||
import { findPageLayoutTabs } from 'test/integration/metadata/suites/page-layout-tab/utils/find-page-layout-tabs.util';
|
||||
import { findPageLayoutWidgets } from 'test/integration/metadata/suites/page-layout-widget/utils/find-page-layout-widgets.util';
|
||||
import { updateOnePageLayoutWidget } from 'test/integration/metadata/suites/page-layout-widget/utils/update-one-page-layout-widget.util';
|
||||
import { findPageLayouts } from 'test/integration/metadata/suites/page-layout/utils/find-page-layouts.util';
|
||||
import { PageLayoutTabLayoutMode } from 'twenty-shared/types';
|
||||
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
|
||||
@@ -17,7 +20,10 @@ const WIDGET_OVERRIDE_GQL_FIELDS = `
|
||||
`;
|
||||
|
||||
describe('Page layout widget override behavior', () => {
|
||||
let seededPageLayoutId: string;
|
||||
let seededWidgetId: string;
|
||||
let seededWidgetOriginalPageLayoutTabId: string;
|
||||
let seededWidgetOriginalTabLayoutMode: PageLayoutTabLayoutMode;
|
||||
let seededWidgetOriginalTitle: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -32,15 +38,27 @@ describe('Page layout widget override behavior', () => {
|
||||
|
||||
expect(recordPageLayout).toBeDefined();
|
||||
|
||||
seededPageLayoutId = recordPageLayout!.id;
|
||||
|
||||
const { data: tabsData } = await findPageLayoutTabs({
|
||||
expectToFail: false,
|
||||
input: { pageLayoutId: recordPageLayout!.id },
|
||||
input: { pageLayoutId: seededPageLayoutId },
|
||||
gqlFields: `
|
||||
id
|
||||
title
|
||||
position
|
||||
layoutMode
|
||||
pageLayoutId
|
||||
`,
|
||||
});
|
||||
|
||||
expect(tabsData.getPageLayoutTabs.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const firstTabId = tabsData.getPageLayoutTabs[0].id;
|
||||
|
||||
seededWidgetOriginalTabLayoutMode = tabsData.getPageLayoutTabs[0]
|
||||
.layoutMode as PageLayoutTabLayoutMode;
|
||||
|
||||
const { data: widgetsData } = await findPageLayoutWidgets({
|
||||
expectToFail: false,
|
||||
input: { pageLayoutTabId: firstTabId },
|
||||
@@ -52,6 +70,7 @@ describe('Page layout widget override behavior', () => {
|
||||
const firstWidget = widgetsData.getPageLayoutWidgets[0];
|
||||
|
||||
seededWidgetId = firstWidget.id;
|
||||
seededWidgetOriginalPageLayoutTabId = firstWidget.pageLayoutTabId;
|
||||
seededWidgetOriginalTitle = firstWidget.title;
|
||||
});
|
||||
|
||||
@@ -60,6 +79,7 @@ describe('Page layout widget override behavior', () => {
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: seededWidgetId,
|
||||
pageLayoutTabId: seededWidgetOriginalPageLayoutTabId,
|
||||
title: seededWidgetOriginalTitle,
|
||||
},
|
||||
gqlFields: WIDGET_OVERRIDE_GQL_FIELDS,
|
||||
@@ -81,6 +101,74 @@ describe('Page layout widget override behavior', () => {
|
||||
expect(data.updatePageLayoutWidget.title).toBe(overriddenTitle);
|
||||
});
|
||||
|
||||
it('should move a seeded widget to another tab through an override', async () => {
|
||||
let destinationTabId: string | undefined;
|
||||
|
||||
try {
|
||||
const { data: tabData } = await createOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
title: `Widget Move Override ${Date.now()}`,
|
||||
pageLayoutId: seededPageLayoutId,
|
||||
layoutMode: seededWidgetOriginalTabLayoutMode,
|
||||
},
|
||||
});
|
||||
|
||||
destinationTabId = tabData.createPageLayoutTab.id;
|
||||
|
||||
const { data } = await updateOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: seededWidgetId,
|
||||
pageLayoutTabId: destinationTabId,
|
||||
},
|
||||
gqlFields: WIDGET_OVERRIDE_GQL_FIELDS,
|
||||
});
|
||||
|
||||
expect(data.updatePageLayoutWidget.pageLayoutTabId).toBe(
|
||||
destinationTabId,
|
||||
);
|
||||
|
||||
const { data: originalTabWidgetsData } = await findPageLayoutWidgets({
|
||||
expectToFail: false,
|
||||
input: { pageLayoutTabId: seededWidgetOriginalPageLayoutTabId },
|
||||
gqlFields: WIDGET_OVERRIDE_GQL_FIELDS,
|
||||
});
|
||||
|
||||
expect(
|
||||
originalTabWidgetsData.getPageLayoutWidgets.map((widget) => widget.id),
|
||||
).not.toContain(seededWidgetId);
|
||||
|
||||
const { data: destinationTabWidgetsData } = await findPageLayoutWidgets({
|
||||
expectToFail: false,
|
||||
input: { pageLayoutTabId: destinationTabId },
|
||||
gqlFields: WIDGET_OVERRIDE_GQL_FIELDS,
|
||||
});
|
||||
|
||||
expect(
|
||||
destinationTabWidgetsData.getPageLayoutWidgets.map(
|
||||
(widget) => widget.id,
|
||||
),
|
||||
).toContain(seededWidgetId);
|
||||
} finally {
|
||||
await updateOnePageLayoutWidget({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: seededWidgetId,
|
||||
pageLayoutTabId: seededWidgetOriginalPageLayoutTabId,
|
||||
},
|
||||
gqlFields: WIDGET_OVERRIDE_GQL_FIELDS,
|
||||
});
|
||||
|
||||
if (destinationTabId !== undefined) {
|
||||
await destroyOnePageLayoutTab({
|
||||
expectToFail: false,
|
||||
input: { id: destinationTabId },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the overridden title when querying the widget', async () => {
|
||||
const { data: tabsData } = await findPageLayoutTabs({
|
||||
expectToFail: false,
|
||||
|
||||
+4
@@ -42,12 +42,16 @@ export const updateOnePageLayoutWidgetQueryFactory = ({
|
||||
variables: {
|
||||
id: input.id,
|
||||
input: {
|
||||
pageLayoutTabId: input.pageLayoutTabId,
|
||||
title: input.title,
|
||||
type: input.type,
|
||||
objectMetadataId: input.objectMetadataId,
|
||||
gridPosition: input.gridPosition,
|
||||
position: input.position,
|
||||
configuration: input.configuration,
|
||||
conditionalDisplay: input.conditionalDisplay,
|
||||
conditionalAvailabilityExpression:
|
||||
input.conditionalAvailabilityExpression,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+94
@@ -229,4 +229,98 @@ describe('Page layout with tabs update should succeed', () => {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('should move a widget to another tab when saving layout tabs and widgets', async () => {
|
||||
const widgetId = v4();
|
||||
|
||||
await updateOnePageLayoutWithTabsAndWidgets({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: testPageLayoutId,
|
||||
name: 'Layout Before Widget Move',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: null,
|
||||
tabs: [
|
||||
{
|
||||
id: testTabId1,
|
||||
title: 'Source Tab',
|
||||
position: 0,
|
||||
widgets: [
|
||||
{
|
||||
id: widgetId,
|
||||
pageLayoutTabId: testTabId1,
|
||||
title: 'Iframe Widget',
|
||||
type: WidgetType.IFRAME,
|
||||
objectMetadataId: null,
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
configuration: MOCK_IFRAME_CONFIGURATION,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: testTabId2,
|
||||
title: 'Destination Tab',
|
||||
position: 1,
|
||||
widgets: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const { data } = await updateOnePageLayoutWithTabsAndWidgets({
|
||||
expectToFail: false,
|
||||
input: {
|
||||
id: testPageLayoutId,
|
||||
name: 'Layout After Widget Move',
|
||||
type: PageLayoutType.RECORD_PAGE,
|
||||
objectMetadataId: null,
|
||||
tabs: [
|
||||
{
|
||||
id: testTabId1,
|
||||
title: 'Source Tab',
|
||||
position: 0,
|
||||
widgets: [],
|
||||
},
|
||||
{
|
||||
id: testTabId2,
|
||||
title: 'Destination Tab',
|
||||
position: 1,
|
||||
widgets: [
|
||||
{
|
||||
id: widgetId,
|
||||
pageLayoutTabId: testTabId2,
|
||||
title: 'Iframe Widget',
|
||||
type: WidgetType.IFRAME,
|
||||
objectMetadataId: null,
|
||||
gridPosition: {
|
||||
row: 0,
|
||||
column: 0,
|
||||
rowSpan: 1,
|
||||
columnSpan: 1,
|
||||
},
|
||||
configuration: MOCK_IFRAME_CONFIGURATION,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const updatedTabs = data.updatePageLayoutWithTabsAndWidgets.tabs ?? [];
|
||||
const sourceTab = updatedTabs.find((tab) => tab.id === testTabId1);
|
||||
const destinationTab = updatedTabs.find((tab) => tab.id === testTabId2);
|
||||
|
||||
expect(sourceTab?.widgets).toHaveLength(0);
|
||||
expect(destinationTab?.widgets).toHaveLength(1);
|
||||
expect(destinationTab?.widgets?.[0]).toMatchObject({
|
||||
id: widgetId,
|
||||
pageLayoutTabId: testTabId2,
|
||||
title: 'Iframe Widget',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,7 +81,10 @@ export type {
|
||||
WorkflowRunStepInfos,
|
||||
} from './types/WorkflowRunStateStepInfos';
|
||||
export { StepStatus } from './types/WorkflowRunStateStepInfos';
|
||||
export { canObjectBeManagedByWorkflow } from './utils/canObjectBeManagedByWorkflow';
|
||||
export {
|
||||
OBJECTS_BLOCKED_FROM_AUTOMATION,
|
||||
canObjectBeManagedByAutomation,
|
||||
} from './utils/canObjectBeManagedByAutomation';
|
||||
export { extractRawVariableNamePart } from './utils/extractRawVariableNameParts';
|
||||
export { getFunctionInputFromInputSchema } from './utils/getFunctionInputFromInputSchema';
|
||||
export { getWorkflowRunContext } from './utils/getWorkflowRunContext';
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { canObjectBeManagedByAutomation } from '@/workflow/utils/canObjectBeManagedByAutomation';
|
||||
|
||||
describe('canObjectBeManagedByAutomation', () => {
|
||||
it('should return true for a standard non-blocked object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByAutomation({ nameSingular: 'company' }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for noteTarget and taskTarget', () => {
|
||||
expect(
|
||||
canObjectBeManagedByAutomation({ nameSingular: 'noteTarget' }),
|
||||
).toBe(true);
|
||||
expect(
|
||||
canObjectBeManagedByAutomation({ nameSingular: 'taskTarget' }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for attachment and timelineActivity', () => {
|
||||
expect(
|
||||
canObjectBeManagedByAutomation({ nameSingular: 'attachment' }),
|
||||
).toBe(true);
|
||||
expect(
|
||||
canObjectBeManagedByAutomation({ nameSingular: 'timelineActivity' }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'workflow',
|
||||
'workflowVersion',
|
||||
'workflowRun',
|
||||
'workflowAutomatedTrigger',
|
||||
'workspaceMember',
|
||||
'dashboard',
|
||||
'message',
|
||||
'messageThread',
|
||||
'messageChannelMessageAssociation',
|
||||
'messageParticipant',
|
||||
'calendarEvent',
|
||||
'calendarEventParticipant',
|
||||
'calendarChannelEventAssociation',
|
||||
])('should return false for %s', (nameSingular) => {
|
||||
expect(canObjectBeManagedByAutomation({ nameSingular })).toBe(false);
|
||||
});
|
||||
});
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
import { canObjectBeManagedByWorkflow } from '@/workflow/utils/canObjectBeManagedByWorkflow';
|
||||
|
||||
describe('canObjectBeManagedByWorkflow', () => {
|
||||
it('should return true for non-system, non-excluded objects', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'company',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for system objects', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'company',
|
||||
isSystem: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for workflow object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'workflow',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for workflowVersion object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'workflowVersion',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for workflowRun object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'workflowRun',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for dashboard object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'dashboard',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// Objects whose records must not be created, updated, or deleted by
|
||||
// automation callers (workflows and AI tools). Either they back the
|
||||
// automation runtime itself (recursion risk), gate access/permissions,
|
||||
// or are owned by background sync (writing to them corrupts state).
|
||||
export const OBJECTS_BLOCKED_FROM_AUTOMATION = [
|
||||
'workflow',
|
||||
'workflowVersion',
|
||||
'workflowRun',
|
||||
'workflowAutomatedTrigger',
|
||||
'workspaceMember',
|
||||
'dashboard',
|
||||
'message',
|
||||
'messageThread',
|
||||
'messageChannelMessageAssociation',
|
||||
'messageParticipant',
|
||||
'calendarEvent',
|
||||
'calendarEventParticipant',
|
||||
'calendarChannelEventAssociation',
|
||||
] as const;
|
||||
|
||||
export const canObjectBeManagedByAutomation = ({
|
||||
nameSingular,
|
||||
}: {
|
||||
nameSingular: string;
|
||||
}): boolean => {
|
||||
return !OBJECTS_BLOCKED_FROM_AUTOMATION.includes(
|
||||
nameSingular as (typeof OBJECTS_BLOCKED_FROM_AUTOMATION)[number],
|
||||
);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
export const canObjectBeManagedByWorkflow = ({
|
||||
nameSingular,
|
||||
isSystem,
|
||||
}: {
|
||||
nameSingular: string;
|
||||
isSystem: boolean;
|
||||
}) => {
|
||||
const excludedNonSystemObjectMetadataItemNames = [
|
||||
'workflow',
|
||||
'workflowVersion',
|
||||
'workflowRun',
|
||||
'dashboard',
|
||||
];
|
||||
|
||||
return (
|
||||
!excludedNonSystemObjectMetadataItemNames.includes(nameSingular) &&
|
||||
!isSystem
|
||||
);
|
||||
};
|
||||
@@ -24,7 +24,6 @@
|
||||
"@lottiefiles/dotlottie-react": "^0.18.10",
|
||||
"@tabler/icons-react": "^3.41.1",
|
||||
"@wyw-in-js/babel-preset": "^0.8.1",
|
||||
"axios": "^1.14.0",
|
||||
"framer-motion": "^11.18.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"next": "16.1.7",
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import axios from 'axios';
|
||||
import { unstable_cache } from 'next/cache';
|
||||
|
||||
export const fetchDiscordMemberCount = unstable_cache(
|
||||
async (): Promise<number | null> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
'https://discord.com/api/v10/invites/cx5n4Jzs57',
|
||||
{
|
||||
params: { with_counts: true },
|
||||
},
|
||||
const response = await fetch(
|
||||
'https://discord.com/api/v10/invites/cx5n4Jzs57?with_counts=true',
|
||||
);
|
||||
|
||||
return response.data.profile.member_count;
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = (await response.json()) as {
|
||||
profile?: { member_count?: number };
|
||||
};
|
||||
return data.profile?.member_count ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import axios from 'axios';
|
||||
import { unstable_cache } from 'next/cache';
|
||||
|
||||
export const fetchGithubStarCount = unstable_cache(
|
||||
@@ -13,12 +12,15 @@ export const fetchGithubStarCount = unstable_cache(
|
||||
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
||||
}
|
||||
|
||||
const response = await axios.get(
|
||||
const response = await fetch(
|
||||
'https://api.github.com/repos/twentyhq/twenty',
|
||||
{ headers },
|
||||
);
|
||||
|
||||
return response.data.stargazers_count;
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = (await response.json()) as { stargazers_count?: number };
|
||||
return data.stargazers_count ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,12 @@ export const getStripeClient = (): Stripe => {
|
||||
throw new Error('STRIPE_SECRET_KEY is not configured');
|
||||
}
|
||||
|
||||
stripeInstance = new Stripe(secretKey, {});
|
||||
// On Cloudflare Workers the Stripe SDK's default Node http transport hangs
|
||||
// on outbound TLS — even with nodejs_compat — and aborts at the SDK's own
|
||||
// 80s timeout. Forcing the fetch-based client uses workerd's native fetch.
|
||||
stripeInstance = new Stripe(secretKey, {
|
||||
httpClient: Stripe.createFetchHttpClient(),
|
||||
});
|
||||
}
|
||||
|
||||
return stripeInstance;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export async function fetchLatestGithubReleaseTag(): Promise<string | null> {
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
@@ -11,12 +9,15 @@ export async function fetchLatestGithubReleaseTag(): Promise<string | null> {
|
||||
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
||||
}
|
||||
|
||||
const response = await axios.get(
|
||||
const response = await fetch(
|
||||
'https://api.github.com/repos/twentyhq/twenty/releases/latest',
|
||||
{ headers },
|
||||
);
|
||||
|
||||
return response.data.tag_name;
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = (await response.json()) as { tag_name?: string };
|
||||
return data.tag_name ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -28661,17 +28661,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.14.0":
|
||||
version: 1.14.0
|
||||
resolution: "axios@npm:1.14.0"
|
||||
dependencies:
|
||||
follow-redirects: "npm:^1.15.11"
|
||||
form-data: "npm:^4.0.5"
|
||||
proxy-from-env: "npm:^2.1.0"
|
||||
checksum: 10c0/2541f4aa215a7d1842429dad006fc682d82bc0e74bd14500823f7d8cce3bbae0e0a8c328c8538946718f366ab8ce5a4c12e9ad40e5a0f3482ff8bff0cd115d45
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^1.9.0":
|
||||
version: 1.13.6
|
||||
resolution: "axios@npm:1.13.6"
|
||||
@@ -49878,13 +49867,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proxy-from-env@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "proxy-from-env@npm:2.1.0"
|
||||
checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proxy-memoize@npm:3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "proxy-memoize@npm:3.0.1"
|
||||
@@ -57101,7 +57083,6 @@ __metadata:
|
||||
"@types/react-dom": "npm:^19"
|
||||
"@types/three": "npm:^0.183.1"
|
||||
"@wyw-in-js/babel-preset": "npm:^0.8.1"
|
||||
axios: "npm:^1.14.0"
|
||||
babel-plugin-react-compiler: "npm:1.0.0"
|
||||
framer-motion: "npm:^11.18.0"
|
||||
gray-matter: "npm:^4.0.3"
|
||||
|
||||
Reference in New Issue
Block a user