fix: Filter Selection Icon Missing #13901 (#13950)

Fixed issue #13901



https://github.com/user-attachments/assets/4aa7d0f3-88b8-474f-85e5-b2989ed8afdd

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Kailash Rajput
2025-08-18 20:24:26 +05:30
committed by GitHub
parent 43bb8d6043
commit 5e905d3004
27 changed files with 505 additions and 2110 deletions
@@ -5,13 +5,14 @@ import { ApolloError } from '@apollo/client';
import { verifyEmailRedirectPathState } from '@/app/states/verifyEmailRedirectPathState';
import { useVerifyLogin } from '@/auth/hooks/useVerifyLogin';
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { useIsCurrentLocationOnAWorkspace } from '@/domain-manager/hooks/useIsCurrentLocationOnAWorkspace';
import { useRedirectToWorkspaceDomain } from '@/domain-manager/hooks/useRedirectToWorkspaceDomain';
import { Modal } from '@/ui/layout/modal/components/Modal';
import { useLingui } from '@lingui/react/macro';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
@@ -40,6 +41,7 @@ export const VerifyEmailEffect = () => {
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const { verifyLoginToken } = useVerifyLogin();
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState);
const { t } = useLingui();
useEffect(() => {
@@ -112,10 +114,15 @@ export const VerifyEmailEffect = () => {
}
};
if (!clientConfigApiStatus.isLoadedOnce) {
return;
}
verifyEmailToken();
// Verify email only needs to run once at mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [clientConfigApiStatus.isLoadedOnce]);
if (isError) {
return (
@@ -1,105 +1,14 @@
import { useClientConfig } from '@/client-config/hooks/useClientConfig';
import { aiModelsState } from '@/client-config/states/aiModelsState';
import { apiConfigState } from '@/client-config/states/apiConfigState';
import { appVersionState } from '@/client-config/states/appVersionState';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { captchaState } from '@/client-config/states/captchaState';
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState';
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState';
import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState';
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
import { isGoogleCalendarEnabledState } from '@/client-config/states/isGoogleCalendarEnabledState';
import { isGoogleMessagingEnabledState } from '@/client-config/states/isGoogleMessagingEnabledState';
import { isImapSmtpCaldavEnabledState } from '@/client-config/states/isImapSmtpCaldavEnabledState';
import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicrosoftCalendarEnabledState';
import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const ClientConfigProviderEffect = () => {
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);
const setDomainConfiguration = useSetRecoilState(domainConfigurationState);
const setAuthProviders = useSetRecoilState(authProvidersState);
const setAiModels = useSetRecoilState(aiModelsState);
const setIsDeveloperDefaultSignInPrefilled = useSetRecoilState(
isDeveloperDefaultSignInPrefilledState,
);
const setIsMultiWorkspaceEnabled = useSetRecoilState(
isMultiWorkspaceEnabledState,
);
const setIsEmailVerificationRequired = useSetRecoilState(
isEmailVerificationRequiredState,
);
const setBilling = useSetRecoilState(billingState);
const setSupportChat = useSetRecoilState(supportChatState);
const setSentryConfig = useSetRecoilState(sentryConfigState);
const [clientConfigApiStatus, setClientConfigApiStatus] = useRecoilState(
clientConfigApiStatusState,
);
const setCaptcha = useSetRecoilState(captchaState);
const setChromeExtensionId = useSetRecoilState(chromeExtensionIdState);
const setApiConfig = useSetRecoilState(apiConfigState);
const setCanManageFeatureFlags = useSetRecoilState(
canManageFeatureFlagsState,
);
const setLabPublicFeatureFlags = useSetRecoilState(
labPublicFeatureFlagsState,
);
const setMicrosoftMessagingEnabled = useSetRecoilState(
isMicrosoftMessagingEnabledState,
);
const setMicrosoftCalendarEnabled = useSetRecoilState(
isMicrosoftCalendarEnabledState,
);
const setGoogleMessagingEnabled = useSetRecoilState(
isGoogleMessagingEnabledState,
);
const setGoogleCalendarEnabled = useSetRecoilState(
isGoogleCalendarEnabledState,
);
const setIsAttachmentPreviewEnabled = useSetRecoilState(
isAttachmentPreviewEnabledState,
);
const setIsConfigVariablesInDbEnabled = useSetRecoilState(
isConfigVariablesInDbEnabledState,
);
const setCalendarBookingPageId = useSetRecoilState(
calendarBookingPageIdState,
);
const setIsImapSmtpCaldavEnabled = useSetRecoilState(
isImapSmtpCaldavEnabledState,
);
const setAppVersion = useSetRecoilState(appVersionState);
const { data, loading, error, fetchClientConfig } = useClientConfig();
useEffect(() => {
@@ -130,98 +39,7 @@ export const ClientConfigProviderEffect = () => {
if (!isDefined(data?.clientConfig)) {
return;
}
setClientConfigApiStatus((currentStatus) => ({
...currentStatus,
isErrored: false,
error: undefined,
}));
setAppVersion(data.clientConfig.appVersion);
setAuthProviders({
google: data?.clientConfig.authProviders.google,
microsoft: data?.clientConfig.authProviders.microsoft,
password: data?.clientConfig.authProviders.password,
magicLink: false,
sso: data?.clientConfig.authProviders.sso,
});
setAiModels(data?.clientConfig.aiModels || []);
setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
setIsDeveloperDefaultSignInPrefilled(data?.clientConfig.signInPrefilled);
setIsMultiWorkspaceEnabled(data?.clientConfig.isMultiWorkspaceEnabled);
setIsEmailVerificationRequired(
data?.clientConfig.isEmailVerificationRequired,
);
setBilling(data?.clientConfig.billing);
setSupportChat(data?.clientConfig.support);
setSentryConfig({
dsn: data?.clientConfig?.sentry?.dsn,
release: data?.clientConfig?.sentry?.release,
environment: data?.clientConfig?.sentry?.environment,
});
setCaptcha({
provider: data?.clientConfig?.captcha?.provider,
siteKey: data?.clientConfig?.captcha?.siteKey,
});
setChromeExtensionId(data?.clientConfig?.chromeExtensionId);
setApiConfig(data?.clientConfig?.api);
setDomainConfiguration({
defaultSubdomain: data?.clientConfig?.defaultSubdomain,
frontDomain: data?.clientConfig?.frontDomain,
});
setCanManageFeatureFlags(data?.clientConfig?.canManageFeatureFlags);
setLabPublicFeatureFlags(data?.clientConfig?.publicFeatureFlags);
setMicrosoftMessagingEnabled(
data?.clientConfig?.isMicrosoftMessagingEnabled,
);
setMicrosoftCalendarEnabled(data?.clientConfig?.isMicrosoftCalendarEnabled);
setGoogleMessagingEnabled(data?.clientConfig?.isGoogleMessagingEnabled);
setGoogleCalendarEnabled(data?.clientConfig?.isGoogleCalendarEnabled);
setIsAttachmentPreviewEnabled(
data?.clientConfig?.isAttachmentPreviewEnabled,
);
setIsConfigVariablesInDbEnabled(
data?.clientConfig?.isConfigVariablesInDbEnabled,
);
setClientConfigApiStatus((currentStatus) => ({
...currentStatus,
isSaved: true,
}));
setCalendarBookingPageId(data?.clientConfig?.calendarBookingPageId ?? null);
setIsImapSmtpCaldavEnabled(data?.clientConfig?.isImapSmtpCaldavEnabled);
}, [
data,
loading,
error,
setIsDeveloperDefaultSignInPrefilled,
setIsMultiWorkspaceEnabled,
setIsEmailVerificationRequired,
setSupportChat,
setBilling,
setSentryConfig,
setClientConfigApiStatus,
setCaptcha,
setChromeExtensionId,
setApiConfig,
setIsAnalyticsEnabled,
setDomainConfiguration,
setAuthProviders,
setAiModels,
setCanManageFeatureFlags,
setLabPublicFeatureFlags,
setMicrosoftMessagingEnabled,
setMicrosoftCalendarEnabled,
setGoogleMessagingEnabled,
setGoogleCalendarEnabled,
setIsAttachmentPreviewEnabled,
setIsConfigVariablesInDbEnabled,
setCalendarBookingPageId,
setIsImapSmtpCaldavEnabled,
setAppVersion,
]);
}, [data?.clientConfig, error, loading, setClientConfigApiStatus]);
return <></>;
};
@@ -1,6 +1,30 @@
import { aiModelsState } from '@/client-config/states/aiModelsState';
import { apiConfigState } from '@/client-config/states/apiConfigState';
import { appVersionState } from '@/client-config/states/appVersionState';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { calendarBookingPageIdState } from '@/client-config/states/calendarBookingPageIdState';
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
import { captchaState } from '@/client-config/states/captchaState';
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState';
import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState';
import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState';
import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState';
import { isGoogleCalendarEnabledState } from '@/client-config/states/isGoogleCalendarEnabledState';
import { isGoogleMessagingEnabledState } from '@/client-config/states/isGoogleMessagingEnabledState';
import { isImapSmtpCaldavEnabledState } from '@/client-config/states/isImapSmtpCaldavEnabledState';
import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicrosoftCalendarEnabledState';
import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { type ClientConfig } from '@/client-config/types/ClientConfig';
import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState';
import { useCallback } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { clientConfigApiStatusState } from '../states/clientConfigApiStatusState';
import { getClientConfig } from '../utils/getClientConfig';
@@ -13,11 +37,77 @@ type UseClientConfigResult = {
};
export const useClientConfig = (): UseClientConfigResult => {
const clientConfigApiStatus = useRecoilValue(clientConfigApiStatusState);
const setClientConfigApiStatus = useSetRecoilState(
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);
const setDomainConfiguration = useSetRecoilState(domainConfigurationState);
const setAuthProviders = useSetRecoilState(authProvidersState);
const setAiModels = useSetRecoilState(aiModelsState);
const setIsDeveloperDefaultSignInPrefilled = useSetRecoilState(
isDeveloperDefaultSignInPrefilledState,
);
const setIsMultiWorkspaceEnabled = useSetRecoilState(
isMultiWorkspaceEnabledState,
);
const setIsEmailVerificationRequired = useSetRecoilState(
isEmailVerificationRequiredState,
);
const setBilling = useSetRecoilState(billingState);
const setSupportChat = useSetRecoilState(supportChatState);
const setSentryConfig = useSetRecoilState(sentryConfigState);
const [clientConfigApiStatus, setClientConfigApiStatus] = useRecoilState(
clientConfigApiStatusState,
);
const setCaptcha = useSetRecoilState(captchaState);
const setChromeExtensionId = useSetRecoilState(chromeExtensionIdState);
const setApiConfig = useSetRecoilState(apiConfigState);
const setCanManageFeatureFlags = useSetRecoilState(
canManageFeatureFlagsState,
);
const setLabPublicFeatureFlags = useSetRecoilState(
labPublicFeatureFlagsState,
);
const setMicrosoftMessagingEnabled = useSetRecoilState(
isMicrosoftMessagingEnabledState,
);
const setMicrosoftCalendarEnabled = useSetRecoilState(
isMicrosoftCalendarEnabledState,
);
const setGoogleMessagingEnabled = useSetRecoilState(
isGoogleMessagingEnabledState,
);
const setGoogleCalendarEnabled = useSetRecoilState(
isGoogleCalendarEnabledState,
);
const setIsAttachmentPreviewEnabled = useSetRecoilState(
isAttachmentPreviewEnabledState,
);
const setIsConfigVariablesInDbEnabled = useSetRecoilState(
isConfigVariablesInDbEnabledState,
);
const setCalendarBookingPageId = useSetRecoilState(
calendarBookingPageIdState,
);
const setIsImapSmtpCaldavEnabled = useSetRecoilState(
isImapSmtpCaldavEnabledState,
);
const setAppVersion = useSetRecoilState(appVersionState);
const fetchClientConfig = useCallback(async () => {
setClientConfigApiStatus((prev) => ({
...prev,
@@ -34,6 +124,61 @@ export const useClientConfig = (): UseClientConfigResult => {
error: undefined,
data: { clientConfig },
}));
setClientConfigApiStatus((currentStatus) => ({
...currentStatus,
isErrored: false,
error: undefined,
}));
setAppVersion(clientConfig.appVersion);
setAuthProviders({
google: clientConfig.authProviders.google,
microsoft: clientConfig.authProviders.microsoft,
password: clientConfig.authProviders.password,
magicLink: false,
sso: clientConfig.authProviders.sso,
});
setAiModels(clientConfig.aiModels || []);
setIsAnalyticsEnabled(clientConfig.analyticsEnabled);
setIsDeveloperDefaultSignInPrefilled(clientConfig.signInPrefilled);
setIsMultiWorkspaceEnabled(clientConfig.isMultiWorkspaceEnabled);
setIsEmailVerificationRequired(clientConfig.isEmailVerificationRequired);
setBilling(clientConfig.billing);
setSupportChat(clientConfig.support);
setSentryConfig({
dsn: clientConfig?.sentry?.dsn,
release: clientConfig?.sentry?.release,
environment: clientConfig?.sentry?.environment,
});
setCaptcha({
provider: clientConfig?.captcha?.provider,
siteKey: clientConfig?.captcha?.siteKey,
});
setChromeExtensionId(clientConfig?.chromeExtensionId);
setApiConfig(clientConfig?.api);
setDomainConfiguration({
defaultSubdomain: clientConfig?.defaultSubdomain,
frontDomain: clientConfig?.frontDomain,
});
setCanManageFeatureFlags(clientConfig?.canManageFeatureFlags);
setLabPublicFeatureFlags(clientConfig?.publicFeatureFlags);
setMicrosoftMessagingEnabled(clientConfig?.isMicrosoftMessagingEnabled);
setMicrosoftCalendarEnabled(clientConfig?.isMicrosoftCalendarEnabled);
setGoogleMessagingEnabled(clientConfig?.isGoogleMessagingEnabled);
setGoogleCalendarEnabled(clientConfig?.isGoogleCalendarEnabled);
setIsAttachmentPreviewEnabled(clientConfig?.isAttachmentPreviewEnabled);
setIsConfigVariablesInDbEnabled(
clientConfig?.isConfigVariablesInDbEnabled,
);
setClientConfigApiStatus((currentStatus) => ({
...currentStatus,
isSaved: true,
}));
setCalendarBookingPageId(clientConfig?.calendarBookingPageId ?? null);
setIsImapSmtpCaldavEnabled(clientConfig?.isImapSmtpCaldavEnabled);
} catch (err) {
const error =
err instanceof Error ? err : new Error('Failed to fetch client config');
@@ -45,7 +190,33 @@ export const useClientConfig = (): UseClientConfigResult => {
error,
}));
}
}, [setClientConfigApiStatus]);
}, [
setAiModels,
setApiConfig,
setAppVersion,
setAuthProviders,
setBilling,
setCalendarBookingPageId,
setCanManageFeatureFlags,
setCaptcha,
setChromeExtensionId,
setClientConfigApiStatus,
setDomainConfiguration,
setGoogleCalendarEnabled,
setGoogleMessagingEnabled,
setIsAnalyticsEnabled,
setIsAttachmentPreviewEnabled,
setIsConfigVariablesInDbEnabled,
setIsDeveloperDefaultSignInPrefilled,
setIsEmailVerificationRequired,
setIsImapSmtpCaldavEnabled,
setIsMultiWorkspaceEnabled,
setLabPublicFeatureFlags,
setMicrosoftCalendarEnabled,
setMicrosoftMessagingEnabled,
setSentryConfig,
setSupportChat,
]);
return {
data: clientConfigApiStatus.data,
@@ -2,7 +2,7 @@ import { SelectControl } from '@/ui/input/components/SelectControl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useWorkflowVersionIdOrThrow } from '@/workflow/hooks/useWorkflowVersionIdOrThrow';
import { stepsOutputSchemaFamilySelector } from '@/workflow/states/selectors/stepsOutputSchemaFamilySelector';
import { useGetFilterFieldMetadataItem } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useGetFilterFieldMetadataItem';
import { useFilterFieldMetadataItem } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterFieldMetadataItem';
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { getViewFilterOperands } from '@/workflow/workflow-steps/workflow-actions/filter-action/utils/getStepFilterOperands';
@@ -15,6 +15,7 @@ import { useContext } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { type StepFilter } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type WorkflowStepFilterFieldSelectProps = {
@@ -50,7 +51,12 @@ export const WorkflowStepFilterFieldSelect = ({
}),
);
const { getFilterFieldMetadataItem } = useGetFilterFieldMetadataItem();
const { getIcon } = useIcons();
const {
fieldMetadataItem: filterFieldMetadataItem,
objectMetadataItem: filterObjectMetadataItem,
} = useFilterFieldMetadataItem(stepFilter.fieldMetadataId ?? '');
const availableVariablesInWorkflowStep = useAvailableVariablesInWorkflowStep({
shouldDisplayRecordFields,
@@ -66,7 +72,7 @@ export const WorkflowStepFilterFieldSelect = ({
rawVariableName: variableName,
part: 'stepId',
});
const currentStepOutputSchema = snapshot
const [currentStepOutputSchema] = snapshot
.getLoadable(
stepsOutputSchemaFamilySelector({
workflowVersionId,
@@ -81,35 +87,25 @@ export const WorkflowStepFilterFieldSelect = ({
fieldMetadataId,
compositeFieldSubFieldName,
} = searchVariableThroughOutputSchema({
stepOutputSchema: currentStepOutputSchema?.[0],
stepOutputSchema: currentStepOutputSchema,
rawVariableName: variableName,
isFullRecord: false,
});
const {
fieldMetadataItem: selectedFieldMetadataItem,
objectMetadataItem,
} = isDefined(fieldMetadataId)
? getFilterFieldMetadataItem(fieldMetadataId)
: {
fieldMetadataItem: undefined,
objectMetadataItem: undefined,
};
const filterType = isDefined(fieldMetadataId)
? (selectedFieldMetadataItem?.type ?? 'unknown')
? (filterFieldMetadataItem?.type ?? 'unknown')
: variableType;
const isFullRecord =
selectedFieldMetadataItem?.name === 'id' &&
isDefined(objectMetadataItem?.labelSingular);
filterFieldMetadataItem?.name === 'id' &&
isDefined(filterObjectMetadataItem?.labelSingular);
upsertStepFilterSettings({
stepFilterToUpsert: {
...stepFilter,
stepOutputKey: variableName,
displayValue: isFullRecord
? objectMetadataItem.labelSingular
? filterObjectMetadataItem.labelSingular
: (variableLabel ?? ''),
type: filterType ?? 'unknown',
value: '',
@@ -126,7 +122,8 @@ export const WorkflowStepFilterFieldSelect = ({
upsertStepFilterSettings,
stepFilter,
workflowVersionId,
getFilterFieldMetadataItem,
filterFieldMetadataItem,
filterObjectMetadataItem,
],
);
@@ -144,13 +141,11 @@ export const WorkflowStepFilterFieldSelect = ({
const label =
isSelectedFieldNotFound || !isDefined(stepFilter.displayValue)
? t`Select a field from a previous step`
: stepFilter.displayValue;
: variableLabel;
const dropdownId = `step-filter-field-${stepFilter.id}`;
const isReadonly = readonly ?? false;
if (isReadonly || noAvailableVariables) {
if (noAvailableVariables) {
return (
<Dropdown
dropdownId={dropdownId}
@@ -158,9 +153,28 @@ export const WorkflowStepFilterFieldSelect = ({
<SelectControl
selectedOption={{
value: stepFilter.stepOutputKey,
label: isReadonly
? (label ?? '')
: t`No available fields to select`,
label: t`No available fields to select`,
}}
isDisabled={true}
/>
}
dropdownComponents={[]}
/>
);
}
if (readonly === true) {
return (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<SelectControl
selectedOption={{
value: stepFilter.stepOutputKey,
label,
Icon: filterFieldMetadataItem?.icon
? getIcon(filterFieldMetadataItem.icon)
: undefined,
}}
isDisabled={true}
/>
@@ -174,12 +188,14 @@ export const WorkflowStepFilterFieldSelect = ({
<WorkflowVariablesDropdown
instanceId={dropdownId}
onVariableSelect={handleChange}
disabled={readonly}
clickableComponent={
<SelectControl
selectedOption={{
value: stepFilter.stepOutputKey,
label,
label: label,
Icon: filterFieldMetadataItem?.icon
? getIcon(filterFieldMetadataItem.icon)
: undefined,
}}
textAccent={isSelectedFieldNotFound ? 'placeholder' : 'default'}
/>
@@ -7,7 +7,7 @@ import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/c
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { WorkflowStepFilterValueCompositeInput } from '@/workflow/workflow-steps/workflow-actions/filter-action/components/WorkflowStepFilterValueCompositeInput';
import { useGetFilterFieldMetadataItem } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useGetFilterFieldMetadataItem';
import { useFilterFieldMetadataItem } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useFilterFieldMetadataItem';
import { useUpsertStepFilterSettings } from '@/workflow/workflow-steps/workflow-actions/filter-action/hooks/useUpsertStepFilterSettings';
import { WorkflowStepFilterContext } from '@/workflow/workflow-steps/workflow-actions/filter-action/states/context/WorkflowStepFilterContext';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
@@ -62,7 +62,6 @@ export const WorkflowStepFilterValueInput = ({
const { readonly } = useContext(WorkflowStepFilterContext);
const { upsertStepFilterSettings } = useUpsertStepFilterSettings();
const { getFilterFieldMetadataItem } = useGetFilterFieldMetadataItem();
const handleValueChange = (value: JsonValue) => {
const valueToUpsert = isString(value)
@@ -85,10 +84,6 @@ export const WorkflowStepFilterValueInput = ({
(stepFilter && !configurableViewFilterOperands.has(stepFilter.operand)) ??
true;
if (isDisabled || operandHasNoInput) {
return null;
}
const {
fieldMetadataId,
type: variableType,
@@ -96,12 +91,11 @@ export const WorkflowStepFilterValueInput = ({
} = stepFilter;
const { fieldMetadataItem: selectedFieldMetadataItem, objectMetadataItem } =
isDefined(fieldMetadataId)
? getFilterFieldMetadataItem(fieldMetadataId)
: {
fieldMetadataItem: undefined,
objectMetadataItem: undefined,
};
useFilterFieldMetadataItem(fieldMetadataId ?? '');
if (isDisabled || operandHasNoInput) {
return null;
}
const isFilterableByMultiSelectValue =
variableType === FieldMetadataType.MULTI_SELECT ||
@@ -0,0 +1,18 @@
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
export const useFilterFieldMetadataItem = (fieldMetadataId: string) => {
const { objectMetadataItems } = useObjectMetadataItems();
const objectMetadataItem = objectMetadataItems.find((objectMetadataItem) =>
objectMetadataItem.fields.some((field) => field.id === fieldMetadataId),
);
const fieldMetadataItem = objectMetadataItem?.fields.find(
(field) => field.id === fieldMetadataId,
);
return {
objectMetadataItem,
fieldMetadataItem,
};
};
@@ -1,35 +0,0 @@
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isDefined } from 'twenty-shared/utils';
export const useGetFilterFieldMetadataItem = () => {
const { objectMetadataItems } = useObjectMetadataItems();
const getFilterFieldMetadataItem = (
fieldMetadataId: string,
): {
fieldMetadataItem: FieldMetadataItem | undefined;
objectMetadataItem: ObjectMetadataItem | undefined;
} => {
for (const objectMetadataItem of objectMetadataItems) {
const field = objectMetadataItem.fields.find(
(field) => field.id === fieldMetadataId,
);
if (isDefined(field)) {
return {
fieldMetadataItem: field,
objectMetadataItem: objectMetadataItem,
};
}
}
return {
fieldMetadataItem: undefined,
objectMetadataItem: undefined,
};
};
return {
getFilterFieldMetadataItem,
};
};
@@ -62,17 +62,17 @@ export const WorkflowVariablesDropdownFieldItems = ({
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{filteredOptions.map(([key, subStep]) => (
{filteredOptions.map(([key, option]) => (
<MenuItemSelect
key={key}
selected={false}
focused={false}
onClick={() => handleSelectField(key)}
text={subStep.label || key}
hasSubMenu={!subStep.isLeaf}
LeftIcon={subStep.icon ? getIcon(subStep.icon) : undefined}
text={option.label || key}
hasSubMenu={!option.isLeaf}
LeftIcon={option.icon ? getIcon(option.icon) : undefined}
contextualText={
subStep.isLeaf ? subStep?.value?.toString() : undefined
option.isLeaf ? option?.value?.toString() : undefined
}
/>
))}
@@ -33,6 +33,7 @@ type UseVariableDropdownReturn = {
setSearchInputValue: (value: string) => void;
handleSelectField: (key: string) => void;
goBack: () => void;
// TODO: fix typing here
filteredOptions: [string, any][];
};
@@ -31,6 +31,10 @@ type Link = {
export type BaseOutputSchema = Record<string, Leaf | Node>;
export type FieldOutputSchema = (Leaf | Node) & {
fieldMetadataId: string;
};
export type RecordOutputSchema = {
object: {
nameSingular: string;
@@ -38,7 +42,7 @@ export type RecordOutputSchema = {
objectMetadataId: string;
isRelationField?: boolean;
} & Leaf;
fields: BaseOutputSchema;
fields: Record<string, FieldOutputSchema>;
_outputSchemaType: 'RECORD';
};
@@ -19,7 +19,12 @@ const mockStep = {
objectMetadataId: '123',
},
fields: {
name: { label: 'Name', value: 'Twenty', isLeaf: true },
name: {
label: 'Name',
value: 'Twenty',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
},
_outputSchemaType: 'RECORD',
},
@@ -19,16 +19,42 @@ const mockStep = {
objectMetadataId: '123',
},
fields: {
name: { label: 'Name', value: 'Twenty', isLeaf: true },
name: {
label: 'Name',
value: 'Twenty',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174001',
},
address: {
label: 'Address',
value: {
street: { label: 'Street', value: '123 Main St', isLeaf: true },
city: { label: 'City', value: 'New York', isLeaf: true },
state: { label: 'State', value: 'NY', isLeaf: true },
zip: { label: 'Zip', value: '10001', isLeaf: true },
},
isLeaf: false,
label: 'Address',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
value: {
street: {
label: 'Street',
value: '123 Main St',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
city: {
label: 'City',
value: 'New York',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
state: {
label: 'State',
value: 'NY',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
zip: {
label: 'Zip',
value: '10001',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
},
},
},
_outputSchemaType: 'RECORD',
@@ -22,8 +22,18 @@ describe('searchVariableThroughOutputSchema', () => {
objectMetadataId: '123',
},
fields: {
name: { label: 'Name', value: 'Twenty', isLeaf: true },
address: { label: 'Address', value: '123 Main St', isLeaf: true },
name: {
label: 'Name',
value: 'Twenty',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
address: {
label: 'Address',
value: '123 Main St',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
},
_outputSchemaType: 'RECORD',
},
@@ -42,12 +52,23 @@ describe('searchVariableThroughOutputSchema', () => {
objectMetadataId: '123',
},
fields: {
firstName: { label: 'First Name', value: 'Jane', isLeaf: true },
lastName: { label: 'Last Name', value: 'Doe', isLeaf: true },
firstName: {
label: 'First Name',
value: 'Jane',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
lastName: {
label: 'Last Name',
value: 'Doe',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
email: {
label: 'Email',
value: 'jane@example.com',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
},
_outputSchemaType: 'RECORD',
@@ -90,6 +111,8 @@ describe('searchVariableThroughOutputSchema', () => {
});
expect(result).toEqual({
compositeFieldSubFieldName: undefined,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
variableLabel: 'Name',
variablePathLabel: 'Step 1 > Company > Name',
variableType: 'unknown',
@@ -104,6 +127,8 @@ describe('searchVariableThroughOutputSchema', () => {
});
expect(result).toEqual({
compositeFieldSubFieldName: undefined,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
variableLabel: 'Email',
variablePathLabel: 'Step 1 > Person > Email',
variableType: 'unknown',
@@ -237,6 +262,7 @@ describe('searchVariableThroughOutputSchema', () => {
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
'properties.after.name': {
icon: 'IconBuildingSkyscraper',
@@ -244,22 +270,28 @@ describe('searchVariableThroughOutputSchema', () => {
label: 'Name',
value: 'My text',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
},
'properties.after.annualRecurringRevenue': {
icon: 'IconMoneybag',
label: 'ARR',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
value: {
amountMicros: {
type: FieldMetadataType.NUMERIC,
label: ' Amount Micros',
value: null,
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
isCompositeSubField: true,
},
currencyCode: {
type: FieldMetadataType.TEXT,
label: ' Currency Code',
value: 'My text',
isLeaf: true,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
isCompositeSubField: true,
},
},
isLeaf: false,
@@ -285,6 +317,8 @@ describe('searchVariableThroughOutputSchema', () => {
});
expect(result).toEqual({
compositeFieldSubFieldName: undefined,
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
variableLabel: 'Name',
variablePathLabel: 'Record is Created > Name',
variableType: FieldMetadataType.TEXT,
@@ -300,6 +334,8 @@ describe('searchVariableThroughOutputSchema', () => {
});
expect(result).toEqual({
compositeFieldSubFieldName: 'amountMicros',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
variableLabel: ' Amount Micros',
variablePathLabel: 'Record is Created > ARR > Amount Micros',
variableType: FieldMetadataType.NUMERIC,
@@ -1,6 +1,7 @@
import { type InputSchemaPropertyType } from '@/workflow/types/InputSchema';
import {
type BaseOutputSchema,
type FieldOutputSchema,
type OutputSchema,
type RecordOutputSchema,
} from '@/workflow/workflow-variables/types/StepOutputSchema';
@@ -35,7 +36,7 @@ const filterRecordOutputSchema = ({
shouldDisplayRecordFields: boolean;
shouldDisplayRecordObjects: boolean;
}): RecordOutputSchema | undefined => {
const filteredFields: BaseOutputSchema = {};
const filteredFields: Record<string, FieldOutputSchema> = {};
let hasValidFields = false;
for (const key in outputSchema.fields) {
@@ -135,8 +136,8 @@ const filterRecordOutputSchemaFieldsByType = ({
}: {
outputSchema: RecordOutputSchema;
fieldTypesToExclude: InputSchemaPropertyType[];
}) => {
const filteredFields: BaseOutputSchema = {};
}): RecordOutputSchema => {
const filteredFields: Record<string, FieldOutputSchema> = {};
for (const key in outputSchema.fields) {
const field = outputSchema.fields[key];
@@ -1,991 +0,0 @@
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
type WorkflowDataSeed = {
id: string;
name: string;
lastPublishedVersionId: string;
statuses: WorkflowStatus[];
position: number;
createdBySource: string;
createdByWorkspaceMemberId: string;
createdByName: string;
createdByContext: object;
};
type WorkflowVersionDataSeed = {
id: string;
name: string;
trigger: string;
steps: string;
status: WorkflowVersionStatus;
position: number;
workflowId: string;
};
export const WORKFLOW_DATA_SEED_COLUMNS: (keyof WorkflowDataSeed)[] = [
'id',
'name',
'lastPublishedVersionId',
'statuses',
'position',
'createdBySource',
'createdByWorkspaceMemberId',
'createdByName',
'createdByContext',
];
export const WORKFLOW_VERSION_DATA_SEED_COLUMNS: (keyof WorkflowVersionDataSeed)[] =
['id', 'name', 'trigger', 'steps', 'status', 'position', 'workflowId'];
export const WORKFLOW_DATA_SEED_IDS = {
ID_1: '20202020-8532-47bd-a4fe-2c1e5c4aeed3',
};
export const WORKFLOW_VERSION_DATA_SEED_IDS = {
ID_1: '20202020-9cd5-44a8-8b2f-1160b01f06dd',
};
export const WORKFLOW_DATA_SEEDS: WorkflowDataSeed[] = [
{
id: WORKFLOW_DATA_SEED_IDS.ID_1,
name: 'Quick Lead',
lastPublishedVersionId: WORKFLOW_VERSION_DATA_SEED_IDS.ID_1,
statuses: [WorkflowStatus.ACTIVE],
position: 1,
createdBySource: 'MANUAL',
createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM,
createdByName: 'Tim A',
createdByContext: {},
},
];
export const WORKFLOW_VERSION_DATA_SEEDS: WorkflowVersionDataSeed[] = [
{
id: WORKFLOW_VERSION_DATA_SEED_IDS.ID_1,
name: 'v1',
trigger: JSON.stringify({
name: 'Launch manually',
type: 'MANUAL',
position: {
x: 0,
y: 0,
},
settings: { outputSchema: {} },
nextStepIds: ['6e089bc9-aabd-435f-865f-f31c01c8f4a7'],
}),
steps: JSON.stringify([
{
id: '6e089bc9-aabd-435f-865f-f31c01c8f4a7',
name: 'Quick Lead Form',
type: 'FORM',
valid: false,
position: {
x: 0,
y: 141,
},
settings: {
input: [
{
id: '14d669f0-5249-4fa4-b0bb-f8bd408328d5',
name: 'firstName',
type: 'TEXT',
label: 'First name',
placeholder: 'Tim',
},
{
id: '4eb6ce85-d231-4aef-9837-744490c026d0',
name: 'lastName',
type: 'TEXT',
label: 'Last Name',
placeholder: 'Apple',
},
{
id: 'adbf0e9f-1427-49be-b4fb-092b34d97350',
name: 'email',
type: 'TEXT',
label: 'Email',
placeholder: 'timapple@apple.com',
},
{
id: '4ffc7992-9e65-4a4d-9baf-b52e62f2c273',
name: 'jobTitle',
type: 'TEXT',
label: 'Job title',
placeholder: 'CEO',
},
{
id: '42f11926-04ea-4924-94a4-2293cc748362',
name: 'companyName',
type: 'TEXT',
label: 'Company name',
placeholder: 'Apple',
},
{
id: 'd6ca80ee-26cd-466d-91bf-984d7205451c',
name: 'companyDomain',
type: 'TEXT',
label: 'Company domain',
placeholder: 'https://www.apple.com',
},
],
outputSchema: {
email: {
type: 'TEXT',
label: 'Email',
value: 'My text',
isLeaf: true,
},
jobTitle: {
type: 'TEXT',
label: 'Job title',
value: 'My text',
isLeaf: true,
},
lastName: {
type: 'TEXT',
label: 'Last Name',
value: 'My text',
isLeaf: true,
},
firstName: {
type: 'TEXT',
label: 'First name',
value: 'My text',
isLeaf: true,
},
companyName: {
type: 'TEXT',
label: 'Company name',
value: 'My text',
isLeaf: true,
},
companyDomain: {
type: 'TEXT',
label: 'Company domain',
value: 'My text',
isLeaf: true,
},
},
errorHandlingOptions: {
retryOnFailure: { value: false },
continueOnFailure: { value: false },
},
},
__typename: 'WorkflowAction',
nextStepIds: ['0715b6cd-7cc1-4b98-971b-00f54dfe643b'],
},
{
id: '0715b6cd-7cc1-4b98-971b-00f54dfe643b',
name: 'Create Company',
type: 'CREATE_RECORD',
valid: false,
position: {
x: 0.5,
y: 282,
},
settings: {
input: {
objectName: 'company',
objectRecord: {
name: '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.companyName}}',
domainName: {
primaryLinkUrl:
'{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.companyDomain}}',
primaryLinkLabel: '',
},
},
},
outputSchema: {
fields: {
id: {
icon: 'Icon123',
type: 'UUID',
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
name: {
icon: 'IconBuildingSkyscraper',
type: 'TEXT',
label: 'Name',
value: 'My text',
isLeaf: true,
},
xLink: {
icon: 'IconBrandX',
label: 'X',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
address: {
icon: 'IconMap',
label: 'Address',
value: {
addressLat: {
type: 'NUMERIC',
label: ' Address Lat',
value: null,
isLeaf: true,
},
addressLng: {
type: 'NUMERIC',
label: ' Address Lng',
value: null,
isLeaf: true,
},
addressCity: {
type: 'TEXT',
label: ' Address City',
value: 'My text',
isLeaf: true,
},
addressState: {
type: 'TEXT',
label: ' Address State',
value: 'My text',
isLeaf: true,
},
addressCountry: {
type: 'TEXT',
label: ' Address Country',
value: 'My text',
isLeaf: true,
},
addressStreet1: {
type: 'TEXT',
label: ' Address Street1',
value: 'My text',
isLeaf: true,
},
addressStreet2: {
type: 'TEXT',
label: ' Address Street2',
value: 'My text',
isLeaf: true,
},
addressPostcode: {
type: 'TEXT',
label: ' Address Postcode',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
createdAt: {
icon: 'IconCalendar',
type: 'DATE_TIME',
label: 'Creation date',
value: '01/23/2025 15:16',
isLeaf: true,
},
createdBy: {
icon: 'IconCreativeCommonsSa',
label: 'Created by',
value: {
name: {
type: 'TEXT',
label: ' Name',
value: 'My text',
isLeaf: true,
},
source: {
type: 'SELECT',
label: ' Source',
value: null,
isLeaf: true,
},
context: {
type: 'RAW_JSON',
label: ' Context',
value: null,
isLeaf: true,
},
workspaceMemberId: {
type: 'UUID',
label: ' Workspace Member Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
},
isLeaf: false,
},
deletedAt: {
icon: 'IconCalendarMinus',
type: 'DATE_TIME',
label: 'Deleted at',
value: '01/23/2025 15:16',
isLeaf: true,
},
employees: {
icon: 'IconUsers',
type: 'NUMBER',
label: 'Employees',
value: 20,
isLeaf: true,
},
updatedAt: {
icon: 'IconCalendarClock',
type: 'DATE_TIME',
label: 'Last update',
value: '01/23/2025 15:16',
isLeaf: true,
},
domainName: {
icon: 'IconLink',
label: 'Domain Name',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
accountOwner: {
icon: 'IconUserCircle',
label: 'Account Owner',
value: {
id: {
icon: 'Icon123',
type: 'UUID',
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
name: {
icon: 'IconCircleUser',
label: 'Name',
value: {
lastName: {
type: 'TEXT',
label: ' Last Name',
value: 'My text',
isLeaf: true,
},
firstName: {
type: 'TEXT',
label: ' First Name',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
createdAt: {
icon: 'IconCalendar',
type: 'DATE_TIME',
label: 'Creation date',
value: '01/23/2025 15:16',
isLeaf: true,
},
deletedAt: {
icon: 'IconCalendarMinus',
type: 'DATE_TIME',
label: 'Deleted at',
value: '01/23/2025 15:16',
isLeaf: true,
},
updatedAt: {
icon: 'IconCalendarClock',
type: 'DATE_TIME',
label: 'Last update',
value: '01/23/2025 15:16',
isLeaf: true,
},
userEmail: {
icon: 'IconMail',
type: 'TEXT',
label: 'User Email',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
linkedinLink: {
icon: 'IconBrandLinkedin',
label: 'Linkedin',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
idealCustomerProfile: {
icon: 'IconTarget',
type: 'BOOLEAN',
label: 'ICP',
value: true,
isLeaf: true,
},
annualRecurringRevenue: {
icon: 'IconMoneybag',
label: 'ARR',
value: {
amountMicros: {
type: 'NUMERIC',
label: ' Amount Micros',
value: null,
isLeaf: true,
},
currencyCode: {
type: 'TEXT',
label: ' Currency Code',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
},
object: {
icon: 'IconBuildingSkyscraper',
label: 'Company',
value: 'A company',
isLeaf: true,
fieldIdName: 'id',
nameSingular: 'company',
},
_outputSchemaType: 'RECORD',
},
errorHandlingOptions: {
retryOnFailure: { value: false },
continueOnFailure: { value: false },
},
},
__typename: 'WorkflowAction',
nextStepIds: ['6f553ea7-b00e-4371-9d88-d8298568a246'],
},
{
id: '6f553ea7-b00e-4371-9d88-d8298568a246',
name: 'Create Person',
type: 'CREATE_RECORD',
valid: false,
position: {
x: 8.5,
y: 423,
},
settings: {
input: {
objectName: 'person',
objectRecord: {
name: {
lastName: '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.lastName}}',
firstName: '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.firstName}}',
},
emails: {
primaryEmail: '{{6e089bc9-aabd-435f-865f-f31c01c8f4a7.email}}',
additionalEmails: [],
},
company: {
id: '{{0715b6cd-7cc1-4b98-971b-00f54dfe643b.id}}',
},
},
},
outputSchema: {
fields: {
id: {
icon: 'Icon123',
type: 'UUID',
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
city: {
icon: 'IconMap',
type: 'TEXT',
label: 'City',
value: 'My text',
isLeaf: true,
},
name: {
icon: 'IconUser',
label: 'Name',
value: {
lastName: {
type: 'TEXT',
label: ' Last Name',
value: 'My text',
isLeaf: true,
},
firstName: {
type: 'TEXT',
label: ' First Name',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
xLink: {
icon: 'IconBrandX',
label: 'X',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
emails: {
icon: 'IconMail',
label: 'Emails',
value: {
primaryEmail: {
type: 'TEXT',
label: ' Primary Email',
value: 'My text',
isLeaf: true,
},
additionalEmails: {
type: 'RAW_JSON',
label: ' Additional Emails',
value: null,
isLeaf: true,
},
},
isLeaf: false,
},
phones: {
icon: 'IconPhone',
label: 'Phones',
value: {
additionalPhones: {
type: 'RAW_JSON',
label: ' Additional Phones',
value: null,
isLeaf: true,
},
primaryPhoneNumber: {
type: 'TEXT',
label: ' Primary Phone Number',
value: 'My text',
isLeaf: true,
},
primaryPhoneCallingCode: {
type: 'TEXT',
label: ' Primary Phone Calling Code',
value: 'My text',
isLeaf: true,
},
primaryPhoneCountryCode: {
type: 'TEXT',
label: ' Primary Phone Country Code',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
company: {
icon: 'IconBuildingSkyscraper',
label: 'Company',
value: {
id: {
icon: 'Icon123',
type: 'UUID',
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
name: {
icon: 'IconBuildingSkyscraper',
type: 'TEXT',
label: 'Name',
value: 'My text',
isLeaf: true,
},
xLink: {
icon: 'IconBrandX',
label: 'X',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
address: {
icon: 'IconMap',
label: 'Address',
value: {
addressLat: {
type: 'NUMERIC',
label: ' Address Lat',
value: null,
isLeaf: true,
},
addressLng: {
type: 'NUMERIC',
label: ' Address Lng',
value: null,
isLeaf: true,
},
addressCity: {
type: 'TEXT',
label: ' Address City',
value: 'My text',
isLeaf: true,
},
addressState: {
type: 'TEXT',
label: ' Address State',
value: 'My text',
isLeaf: true,
},
addressCountry: {
type: 'TEXT',
label: ' Address Country',
value: 'My text',
isLeaf: true,
},
addressStreet1: {
type: 'TEXT',
label: ' Address Street1',
value: 'My text',
isLeaf: true,
},
addressStreet2: {
type: 'TEXT',
label: ' Address Street2',
value: 'My text',
isLeaf: true,
},
addressPostcode: {
type: 'TEXT',
label: ' Address Postcode',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
createdAt: {
icon: 'IconCalendar',
type: 'DATE_TIME',
label: 'Creation date',
value: '01/23/2025 15:16',
isLeaf: true,
},
createdBy: {
icon: 'IconCreativeCommonsSa',
label: 'Created by',
value: {
name: {
type: 'TEXT',
label: ' Name',
value: 'My text',
isLeaf: true,
},
source: {
type: 'SELECT',
label: ' Source',
value: null,
isLeaf: true,
},
context: {
type: 'RAW_JSON',
label: ' Context',
value: null,
isLeaf: true,
},
workspaceMemberId: {
type: 'UUID',
label: ' Workspace Member Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
},
isLeaf: false,
},
deletedAt: {
icon: 'IconCalendarMinus',
type: 'DATE_TIME',
label: 'Deleted at',
value: '01/23/2025 15:16',
isLeaf: true,
},
employees: {
icon: 'IconUsers',
type: 'NUMBER',
label: 'Employees',
value: 20,
isLeaf: true,
},
updatedAt: {
icon: 'IconCalendarClock',
type: 'DATE_TIME',
label: 'Last update',
value: '01/23/2025 15:16',
isLeaf: true,
},
domainName: {
icon: 'IconLink',
label: 'Domain Name',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
linkedinLink: {
icon: 'IconBrandLinkedin',
label: 'Linkedin',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
idealCustomerProfile: {
icon: 'IconTarget',
type: 'BOOLEAN',
label: 'ICP',
value: true,
isLeaf: true,
},
annualRecurringRevenue: {
icon: 'IconMoneybag',
label: 'ARR',
value: {
amountMicros: {
type: 'NUMERIC',
label: ' Amount Micros',
value: null,
isLeaf: true,
},
currencyCode: {
type: 'TEXT',
label: ' Currency Code',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
},
isLeaf: false,
},
jobTitle: {
icon: 'IconBriefcase',
type: 'TEXT',
label: 'Job Title',
value: 'My text',
isLeaf: true,
},
createdAt: {
icon: 'IconCalendar',
type: 'DATE_TIME',
label: 'Creation date',
value: '01/23/2025 15:16',
isLeaf: true,
},
createdBy: {
icon: 'IconCreativeCommonsSa',
label: 'Created by',
value: {
name: {
type: 'TEXT',
label: ' Name',
value: 'My text',
isLeaf: true,
},
source: {
type: 'SELECT',
label: ' Source',
value: null,
isLeaf: true,
},
context: {
type: 'RAW_JSON',
label: ' Context',
value: null,
isLeaf: true,
},
workspaceMemberId: {
type: 'UUID',
label: ' Workspace Member Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
},
isLeaf: false,
},
deletedAt: {
icon: 'IconCalendarMinus',
type: 'DATE_TIME',
label: 'Deleted at',
value: '01/23/2025 15:16',
isLeaf: true,
},
updatedAt: {
icon: 'IconCalendarClock',
type: 'DATE_TIME',
label: 'Last update',
value: '01/23/2025 15:16',
isLeaf: true,
},
linkedinLink: {
icon: 'IconBrandLinkedin',
label: 'Linkedin',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
},
object: {
icon: 'IconUser',
label: 'Person',
value: 'A person',
isLeaf: true,
fieldIdName: 'id',
nameSingular: 'person',
},
_outputSchemaType: 'RECORD',
},
errorHandlingOptions: {
retryOnFailure: { value: false },
continueOnFailure: { value: false },
},
},
__typename: 'WorkflowAction',
nextStepIds: null,
},
]),
status: WorkflowVersionStatus.ACTIVE,
position: 1,
workflowId: WORKFLOW_DATA_SEED_IDS.ID_1,
},
];
@@ -81,18 +81,13 @@ import {
TASK_TARGET_DATA_SEED_COLUMNS,
TASK_TARGET_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/task-target-data-seeds.constant';
import {
WORKFLOW_DATA_SEED_COLUMNS,
WORKFLOW_DATA_SEEDS,
WORKFLOW_VERSION_DATA_SEED_COLUMNS,
WORKFLOW_VERSION_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/workflow-data-seeds.constants';
import {
WORKSPACE_MEMBER_DATA_SEED_COLUMNS,
WORKSPACE_MEMBER_DATA_SEEDS,
} from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant';
import { TimelineActivitySeederService } from 'src/engine/workspace-manager/dev-seeder/data/services/timeline-activity-seeder.service';
import { prefillViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-views';
import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows';
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites';
const RECORD_SEEDS_CONFIGS = [
@@ -176,16 +171,6 @@ const RECORD_SEEDS_CONFIGS = [
pgColumns: MESSAGE_PARTICIPANT_DATA_SEED_COLUMNS,
recordSeeds: MESSAGE_PARTICIPANT_DATA_SEEDS,
},
{
tableName: 'workflow',
pgColumns: WORKFLOW_DATA_SEED_COLUMNS,
recordSeeds: WORKFLOW_DATA_SEEDS,
},
{
tableName: 'workflowVersion',
pgColumns: WORKFLOW_VERSION_DATA_SEED_COLUMNS,
recordSeeds: WORKFLOW_VERSION_DATA_SEEDS,
},
{
tableName: '_pet',
pgColumns: PET_DATA_SEED_COLUMNS,
@@ -261,16 +246,14 @@ export class DevSeederDataService {
workspaceId,
});
// For now views/favorites are auto-created for custom
// objects but not for standard objects.
// This is probably something we want to fix in the future.
const viewDefinitionsWithId = await prefillViews(
entityManager,
schemaName,
objectMetadataItems.filter((item) => !item.isCustom),
);
await prefillWorkflows(entityManager, schemaName, objectMetadataItems);
await prefillWorkspaceFavorites(
viewDefinitionsWithId
.filter(
@@ -1,5 +1,10 @@
import { isDefined } from 'twenty-shared/utils';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.util';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
const QUICK_LEAD_WORKFLOW_ID = '8b213cac-a68b-4ffe-817a-3ec994e9932d';
const QUICK_LEAD_WORKFLOW_VERSION_ID = 'ac67974f-c524-4288-9d88-af8515400b68';
@@ -7,7 +12,30 @@ const QUICK_LEAD_WORKFLOW_VERSION_ID = 'ac67974f-c524-4288-9d88-af8515400b68';
export const prefillWorkflows = async (
entityManager: WorkspaceEntityManager,
schemaName: string,
objectMetadataItems: ObjectMetadataEntity[],
) => {
const objectMetadataMaps = generateObjectMetadataMaps(objectMetadataItems);
const companyObjectMetadataId =
objectMetadataMaps.idByNameSingular['company'];
const personObjectMetadataId = objectMetadataMaps.idByNameSingular['person'];
if (
!isDefined(companyObjectMetadataId) ||
!isDefined(personObjectMetadataId)
) {
throw new Error('Company or person object metadata not found');
}
const companyObjectMetadata =
objectMetadataMaps.byId[companyObjectMetadataId];
const personObjectMetadata = objectMetadataMaps.byId[personObjectMetadataId];
if (!isDefined(companyObjectMetadata) || !isDefined(personObjectMetadata)) {
throw new Error('Company or person object metadata not found');
}
await entityManager
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
@@ -181,298 +209,6 @@ export const prefillWorkflows = async (
},
},
outputSchema: {
fields: {
id: {
icon: 'Icon123',
type: 'UUID',
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
name: {
icon: 'IconBuildingSkyscraper',
type: 'TEXT',
label: 'Name',
value: 'My text',
isLeaf: true,
},
xLink: {
icon: 'IconBrandX',
label: 'X',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
address: {
icon: 'IconMap',
label: 'Address',
value: {
addressLat: {
type: 'NUMERIC',
label: ' Address Lat',
value: null,
isLeaf: true,
},
addressLng: {
type: 'NUMERIC',
label: ' Address Lng',
value: null,
isLeaf: true,
},
addressCity: {
type: 'TEXT',
label: ' Address City',
value: 'My text',
isLeaf: true,
},
addressState: {
type: 'TEXT',
label: ' Address State',
value: 'My text',
isLeaf: true,
},
addressCountry: {
type: 'TEXT',
label: ' Address Country',
value: 'My text',
isLeaf: true,
},
addressStreet1: {
type: 'TEXT',
label: ' Address Street1',
value: 'My text',
isLeaf: true,
},
addressStreet2: {
type: 'TEXT',
label: ' Address Street2',
value: 'My text',
isLeaf: true,
},
addressPostcode: {
type: 'TEXT',
label: ' Address Postcode',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
createdAt: {
icon: 'IconCalendar',
type: 'DATE_TIME',
label: 'Creation date',
value: '01/23/2025 15:16',
isLeaf: true,
},
createdBy: {
icon: 'IconCreativeCommonsSa',
label: 'Created by',
value: {
name: {
type: 'TEXT',
label: ' Name',
value: 'My text',
isLeaf: true,
},
source: {
type: 'SELECT',
label: ' Source',
value: null,
isLeaf: true,
},
context: {
type: 'RAW_JSON',
label: ' Context',
value: null,
isLeaf: true,
},
workspaceMemberId: {
type: 'UUID',
label: ' Workspace Member Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
},
isLeaf: false,
},
deletedAt: {
icon: 'IconCalendarMinus',
type: 'DATE_TIME',
label: 'Deleted at',
value: '01/23/2025 15:16',
isLeaf: true,
},
employees: {
icon: 'IconUsers',
type: 'NUMBER',
label: 'Employees',
value: 20,
isLeaf: true,
},
updatedAt: {
icon: 'IconCalendarClock',
type: 'DATE_TIME',
label: 'Last update',
value: '01/23/2025 15:16',
isLeaf: true,
},
domainName: {
icon: 'IconLink',
label: 'Domain Name',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
accountOwner: {
icon: 'IconUserCircle',
label: 'Account Owner',
value: {
id: {
icon: 'Icon123',
type: 'UUID',
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
name: {
icon: 'IconCircleUser',
label: 'Name',
value: {
lastName: {
type: 'TEXT',
label: ' Last Name',
value: 'My text',
isLeaf: true,
},
firstName: {
type: 'TEXT',
label: ' First Name',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
createdAt: {
icon: 'IconCalendar',
type: 'DATE_TIME',
label: 'Creation date',
value: '01/23/2025 15:16',
isLeaf: true,
},
deletedAt: {
icon: 'IconCalendarMinus',
type: 'DATE_TIME',
label: 'Deleted at',
value: '01/23/2025 15:16',
isLeaf: true,
},
updatedAt: {
icon: 'IconCalendarClock',
type: 'DATE_TIME',
label: 'Last update',
value: '01/23/2025 15:16',
isLeaf: true,
},
userEmail: {
icon: 'IconMail',
type: 'TEXT',
label: 'User Email',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
linkedinLink: {
icon: 'IconBrandLinkedin',
label: 'Linkedin',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
idealCustomerProfile: {
icon: 'IconTarget',
type: 'BOOLEAN',
label: 'ICP',
value: true,
isLeaf: true,
},
annualRecurringRevenue: {
icon: 'IconMoneybag',
label: 'ARR',
value: {
amountMicros: {
type: 'NUMERIC',
label: ' Amount Micros',
value: null,
isLeaf: true,
},
currencyCode: {
type: 'TEXT',
label: ' Currency Code',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
},
object: {
icon: 'IconBuildingSkyscraper',
label: 'Company',
@@ -482,6 +218,13 @@ export const prefillWorkflows = async (
nameSingular: 'company',
},
_outputSchemaType: 'RECORD',
fields: generateObjectRecordFields({
objectMetadataInfo: {
objectMetadataItemWithFieldsMaps: companyObjectMetadata,
objectMetadataMaps,
},
depth: 0,
}),
},
errorHandlingOptions: {
retryOnFailure: { value: false },
@@ -517,445 +260,13 @@ export const prefillWorkflows = async (
},
},
outputSchema: {
fields: {
id: {
icon: 'Icon123',
type: 'UUID',
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
fields: generateObjectRecordFields({
objectMetadataInfo: {
objectMetadataItemWithFieldsMaps: personObjectMetadata,
objectMetadataMaps,
},
city: {
icon: 'IconMap',
type: 'TEXT',
label: 'City',
value: 'My text',
isLeaf: true,
},
name: {
icon: 'IconUser',
label: 'Name',
value: {
lastName: {
type: 'TEXT',
label: ' Last Name',
value: 'My text',
isLeaf: true,
},
firstName: {
type: 'TEXT',
label: ' First Name',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
xLink: {
icon: 'IconBrandX',
label: 'X',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
emails: {
icon: 'IconMail',
label: 'Emails',
value: {
primaryEmail: {
type: 'TEXT',
label: ' Primary Email',
value: 'My text',
isLeaf: true,
},
additionalEmails: {
type: 'RAW_JSON',
label: ' Additional Emails',
value: null,
isLeaf: true,
},
},
isLeaf: false,
},
phones: {
icon: 'IconPhone',
label: 'Phones',
value: {
additionalPhones: {
type: 'RAW_JSON',
label: ' Additional Phones',
value: null,
isLeaf: true,
},
primaryPhoneNumber: {
type: 'TEXT',
label: ' Primary Phone Number',
value: 'My text',
isLeaf: true,
},
primaryPhoneCallingCode: {
type: 'TEXT',
label: ' Primary Phone Calling Code',
value: 'My text',
isLeaf: true,
},
primaryPhoneCountryCode: {
type: 'TEXT',
label: ' Primary Phone Country Code',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
company: {
icon: 'IconBuildingSkyscraper',
label: 'Company',
value: {
id: {
icon: 'Icon123',
type: 'UUID',
label: 'Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
name: {
icon: 'IconBuildingSkyscraper',
type: 'TEXT',
label: 'Name',
value: 'My text',
isLeaf: true,
},
xLink: {
icon: 'IconBrandX',
label: 'X',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
address: {
icon: 'IconMap',
label: 'Address',
value: {
addressLat: {
type: 'NUMERIC',
label: ' Address Lat',
value: null,
isLeaf: true,
},
addressLng: {
type: 'NUMERIC',
label: ' Address Lng',
value: null,
isLeaf: true,
},
addressCity: {
type: 'TEXT',
label: ' Address City',
value: 'My text',
isLeaf: true,
},
addressState: {
type: 'TEXT',
label: ' Address State',
value: 'My text',
isLeaf: true,
},
addressCountry: {
type: 'TEXT',
label: ' Address Country',
value: 'My text',
isLeaf: true,
},
addressStreet1: {
type: 'TEXT',
label: ' Address Street1',
value: 'My text',
isLeaf: true,
},
addressStreet2: {
type: 'TEXT',
label: ' Address Street2',
value: 'My text',
isLeaf: true,
},
addressPostcode: {
type: 'TEXT',
label: ' Address Postcode',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
createdAt: {
icon: 'IconCalendar',
type: 'DATE_TIME',
label: 'Creation date',
value: '01/23/2025 15:16',
isLeaf: true,
},
createdBy: {
icon: 'IconCreativeCommonsSa',
label: 'Created by',
value: {
name: {
type: 'TEXT',
label: ' Name',
value: 'My text',
isLeaf: true,
},
source: {
type: 'SELECT',
label: ' Source',
value: null,
isLeaf: true,
},
context: {
type: 'RAW_JSON',
label: ' Context',
value: null,
isLeaf: true,
},
workspaceMemberId: {
type: 'UUID',
label: ' Workspace Member Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
},
isLeaf: false,
},
deletedAt: {
icon: 'IconCalendarMinus',
type: 'DATE_TIME',
label: 'Deleted at',
value: '01/23/2025 15:16',
isLeaf: true,
},
employees: {
icon: 'IconUsers',
type: 'NUMBER',
label: 'Employees',
value: 20,
isLeaf: true,
},
updatedAt: {
icon: 'IconCalendarClock',
type: 'DATE_TIME',
label: 'Last update',
value: '01/23/2025 15:16',
isLeaf: true,
},
domainName: {
icon: 'IconLink',
label: 'Domain Name',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
linkedinLink: {
icon: 'IconBrandLinkedin',
label: 'Linkedin',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
idealCustomerProfile: {
icon: 'IconTarget',
type: 'BOOLEAN',
label: 'ICP',
value: true,
isLeaf: true,
},
annualRecurringRevenue: {
icon: 'IconMoneybag',
label: 'ARR',
value: {
amountMicros: {
type: 'NUMERIC',
label: ' Amount Micros',
value: null,
isLeaf: true,
},
currencyCode: {
type: 'TEXT',
label: ' Currency Code',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
},
isLeaf: false,
},
jobTitle: {
icon: 'IconBriefcase',
type: 'TEXT',
label: 'Job Title',
value: 'My text',
isLeaf: true,
},
createdAt: {
icon: 'IconCalendar',
type: 'DATE_TIME',
label: 'Creation date',
value: '01/23/2025 15:16',
isLeaf: true,
},
createdBy: {
icon: 'IconCreativeCommonsSa',
label: 'Created by',
value: {
name: {
type: 'TEXT',
label: ' Name',
value: 'My text',
isLeaf: true,
},
source: {
type: 'SELECT',
label: ' Source',
value: null,
isLeaf: true,
},
context: {
type: 'RAW_JSON',
label: ' Context',
value: null,
isLeaf: true,
},
workspaceMemberId: {
type: 'UUID',
label: ' Workspace Member Id',
value: '123e4567-e89b-12d3-a456-426614174000',
isLeaf: true,
},
},
isLeaf: false,
},
deletedAt: {
icon: 'IconCalendarMinus',
type: 'DATE_TIME',
label: 'Deleted at',
value: '01/23/2025 15:16',
isLeaf: true,
},
updatedAt: {
icon: 'IconCalendarClock',
type: 'DATE_TIME',
label: 'Last update',
value: '01/23/2025 15:16',
isLeaf: true,
},
linkedinLink: {
icon: 'IconBrandLinkedin',
label: 'Linkedin',
value: {
primaryLinkUrl: {
type: 'TEXT',
label: ' Primary Link Url',
value: 'My text',
isLeaf: true,
},
secondaryLinks: {
type: 'RAW_JSON',
label: ' Secondary Links',
value: null,
isLeaf: true,
},
primaryLinkLabel: {
type: 'TEXT',
label: ' Primary Link Label',
value: 'My text',
isLeaf: true,
},
},
isLeaf: false,
},
},
object: {
icon: 'IconUser',
label: 'Person',
value: 'A person',
isLeaf: true,
fieldIdName: 'id',
nameSingular: 'person',
},
_outputSchemaType: 'RECORD',
depth: 0,
}),
},
errorHandlingOptions: {
retryOnFailure: { value: false },
@@ -6,8 +6,8 @@ import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-worksp
import { prefillCompanies } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies';
import { prefillPeople } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-people';
import { prefillViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-views';
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites';
import { prefillWorkflows } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows';
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites';
export const standardObjectsPrefillData = async (
mainDataSource: DataSource,
@@ -19,7 +19,7 @@ export const standardObjectsPrefillData = async (
await prefillPeople(entityManager, schemaName);
await prefillWorkflows(entityManager, schemaName);
await prefillWorkflows(entityManager, schemaName, objectMetadataItems);
const viewDefinitionsWithId = await prefillViews(
entityManager,
@@ -31,7 +31,7 @@ export type BaseOutputSchema = Record<string, Leaf | Node>;
export type FieldOutputSchema =
| ((Leaf | Node) & {
fieldMetadataId?: string;
fieldMetadataId: string;
})
| RecordOutputSchema;
@@ -10,7 +10,7 @@ const companyMockObjectMetadataItem = mockObjectMetadataItemsWithFieldMaps.find(
describe('generateFakeFormResponse', () => {
it('should generate fake responses for a form schema', async () => {
const schema: FormFieldMetadata[] = [
const formFieldMetadataItems: FormFieldMetadata[] = [
{
id: '96939213-49ac-4dee-949d-56e6c7be98e6',
name: 'name',
@@ -51,8 +51,8 @@ describe('generateFakeFormResponse', () => {
},
};
const result = await generateFakeFormResponse({
formMetadata: schema,
const result = generateFakeFormResponse({
formFieldMetadataItems,
objectMetadataMaps: mockObjectMetadataMaps,
});
@@ -1,7 +1,7 @@
import { FieldMetadataType } from 'twenty-shared/types';
import * as generateFakeValueModule from 'src/engine/utils/generate-fake-value';
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
import { generateFakeRecordField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-record-field';
import * as camelToTitleCaseModule from 'src/utils/camel-to-title-case';
jest.mock('src/engine/utils/generate-fake-value');
@@ -52,7 +52,7 @@ describe('generateFakeField', () => {
it('should generate a leaf node for TEXT type', () => {
generateFakeValueSpy.mockReturnValueOnce('Fake Text');
const result = generateFakeField({
const result = generateFakeRecordField({
type: FieldMetadataType.TEXT,
label: 'Text Field',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
@@ -74,7 +74,7 @@ describe('generateFakeField', () => {
});
it('should handle custom value', () => {
const result = generateFakeField({
const result = generateFakeRecordField({
type: FieldMetadataType.TEXT,
label: 'Text Field',
value: 'Test value',
@@ -96,7 +96,7 @@ describe('generateFakeField', () => {
it('should generate a leaf node for NUMBER type with icon', () => {
generateFakeValueSpy.mockReturnValueOnce(42);
const result = generateFakeField({
const result = generateFakeRecordField({
type: FieldMetadataType.NUMBER,
label: 'Number Field',
icon: 'IconNumber',
@@ -118,7 +118,7 @@ describe('generateFakeField', () => {
generateFakeValueSpy.mockReturnValueOnce(fakeDate);
const result = generateFakeField({
const result = generateFakeRecordField({
type: FieldMetadataType.DATE,
label: 'Date Field',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
@@ -145,7 +145,7 @@ describe('generateFakeField', () => {
.mockReturnValueOnce('Label')
.mockReturnValueOnce('Url');
const result = generateFakeField({
const result = generateFakeRecordField({
type: FieldMetadataType.LINKS,
label: 'Links Field',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
@@ -189,7 +189,7 @@ describe('generateFakeField', () => {
.mockReturnValueOnce('Amount')
.mockReturnValueOnce('Currency Code');
const result = generateFakeField({
const result = generateFakeRecordField({
type: FieldMetadataType.CURRENCY,
label: 'Currency Field',
icon: 'IconCurrency',
@@ -230,7 +230,7 @@ describe('generateFakeField', () => {
generateFakeValueSpy.mockReturnValueOnce('Unknown Value');
const result = generateFakeField({
const result = generateFakeRecordField({
type: unknownType,
label: 'Unknown Field',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
@@ -249,7 +249,7 @@ describe('generateFakeField', () => {
it('should handle empty label', () => {
generateFakeValueSpy.mockReturnValueOnce('Fake Boolean');
const result = generateFakeField({
const result = generateFakeRecordField({
type: FieldMetadataType.BOOLEAN,
label: '',
fieldMetadataId: '123e4567-e89b-12d3-a456-426614174000',
@@ -1,12 +1,12 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
import { generateFakeRecordField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-record-field';
import { generateObjectRecordFields } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-object-record-fields';
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/__mocks__/mockObjectMetadataItemsWithFieldMaps';
jest.mock(
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field',
'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-record-field',
);
jest.mock(
'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value',
@@ -42,7 +42,7 @@ describe('generateObjectRecordFields', () => {
(field) => field.type !== FieldMetadataType.RELATION,
);
(generateFakeField as jest.Mock).mockImplementation(
(generateFakeRecordField as jest.Mock).mockImplementation(
({ type, label, icon }) => ({
type,
label,
@@ -69,7 +69,7 @@ describe('generateObjectRecordFields', () => {
});
expect(shouldGenerateFieldFakeValue).toHaveBeenCalledTimes(2);
expect(generateFakeField).toHaveBeenCalledTimes(2);
expect(generateFakeRecordField).toHaveBeenCalledTimes(2);
});
it('should return empty object when no valid fields', () => {
@@ -79,6 +79,6 @@ describe('generateObjectRecordFields', () => {
expect(result).toEqual({});
expect(shouldGenerateFieldFakeValue).toHaveBeenCalledTimes(2);
expect(generateFakeField).not.toHaveBeenCalled();
expect(generateFakeRecordField).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,26 @@
import { type FieldMetadataType } from 'twenty-shared/types';
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
import {
type Leaf,
type Node,
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
type GenerateFakeFormFieldArgs = {
type: FieldMetadataType;
label: string;
value?: string;
};
export const generateFakeFormField = ({
type,
label,
value,
}: GenerateFakeFormFieldArgs): Leaf | Node => {
return {
isLeaf: true,
type: type,
label: label,
value: value ?? generateFakeValue(type, 'FieldMetadataType'),
};
};
@@ -6,59 +6,59 @@ import {
type Leaf,
type Node,
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
import { generateFakeFormField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-form-field';
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
import { type FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
export const generateFakeFormResponse = async ({
formMetadata,
objectMetadataMaps,
}: {
formMetadata: FormFieldMetadata[];
type GenerateFakeFormResponseArgs = {
formFieldMetadataItems: FormFieldMetadata[];
objectMetadataMaps: ObjectMetadataMaps;
}): Promise<Record<string, Leaf | Node>> => {
const result = await Promise.all(
formMetadata.map(async (formFieldMetadata) => {
if (formFieldMetadata.type === 'RECORD') {
if (!formFieldMetadata?.settings?.objectName) {
return undefined;
}
};
const objectMetadataItemWithFieldsMaps =
getObjectMetadataMapItemByNameSingular(
objectMetadataMaps,
formFieldMetadata?.settings?.objectName,
);
if (!isDefined(objectMetadataItemWithFieldsMaps)) {
throw new Error(
`Object metadata not found for object name ${formFieldMetadata?.settings?.objectName}`,
);
}
return {
[formFieldMetadata.name]: {
isLeaf: false,
label: formFieldMetadata.label,
value: generateFakeObjectRecord({
objectMetadataInfo: {
objectMetadataItemWithFieldsMaps,
objectMetadataMaps,
},
}),
},
};
} else {
return {
[formFieldMetadata.name]: generateFakeField({
type: formFieldMetadata.type,
label: formFieldMetadata.label,
value: formFieldMetadata.placeholder,
}),
};
export const generateFakeFormResponse = ({
formFieldMetadataItems,
objectMetadataMaps,
}: GenerateFakeFormResponseArgs): Record<string, Leaf | Node> => {
const result = formFieldMetadataItems.map((formFieldMetadata) => {
if (formFieldMetadata.type === 'RECORD') {
if (!formFieldMetadata?.settings?.objectName) {
return undefined;
}
}),
);
const objectMetadataItemWithFieldsMaps =
getObjectMetadataMapItemByNameSingular(
objectMetadataMaps,
formFieldMetadata?.settings?.objectName,
);
if (!isDefined(objectMetadataItemWithFieldsMaps)) {
throw new Error(
`Object metadata not found for object name ${formFieldMetadata?.settings?.objectName}`,
);
}
return {
[formFieldMetadata.name]: {
isLeaf: false,
label: formFieldMetadata.label,
value: generateFakeObjectRecord({
objectMetadataInfo: {
objectMetadataItemWithFieldsMaps,
objectMetadataMaps,
},
}),
},
};
} else {
return {
[formFieldMetadata.name]: generateFakeFormField({
type: formFieldMetadata.type,
label: formFieldMetadata.label,
value: formFieldMetadata.placeholder,
}),
};
}
});
return result.filter(isDefined).reduce(
(acc, curr) => {
@@ -8,19 +8,23 @@ import {
} from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
import { camelToTitleCase } from 'src/utils/camel-to-title-case';
export const generateFakeField = ({
type GenerateFakeRecordFieldArgs = {
type: FieldMetadataType;
label: string;
fieldMetadataId: string;
icon?: string;
value?: string;
};
export const generateFakeRecordField = ({
type,
label,
icon,
value,
fieldMetadataId,
}: {
type: FieldMetadataType;
label: string;
fieldMetadataId?: string;
icon?: string;
value?: string;
}): (Leaf | Node) & { fieldMetadataId?: string } => {
}: GenerateFakeRecordFieldArgs): (Leaf | Node) & {
fieldMetadataId: string;
} => {
const compositeType = compositeTypeDefinitions.get(type);
if (compositeType) {
@@ -3,8 +3,8 @@ import { isDefined } from 'twenty-shared/utils';
import { type ObjectMetadataInfo } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { type FieldOutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
import { generateFakeField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-field';
import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record';
import { generateFakeRecordField } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-record-field';
import { shouldGenerateFieldFakeValue } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/should-generate-field-fake-value';
const MAXIMUM_DEPTH = 1;
@@ -25,7 +25,7 @@ export const generateObjectRecordFields = ({
}
if (field.type !== FieldMetadataType.RELATION) {
acc[field.name] = generateFakeField({
acc[field.name] = generateFakeRecordField({
type: field.type,
label: field.label,
icon: field.icon ?? undefined,
@@ -73,7 +73,7 @@ export class WorkflowSchemaWorkspaceService {
});
case WorkflowActionType.FORM:
return this.computeFormActionOutputSchema({
formMetadata: step.settings.input,
formFieldMetadataItems: step.settings.input,
workspaceId,
});
case WorkflowActionType.CODE: // StepOutput schema is computed on serverlessFunction draft execution
@@ -156,10 +156,10 @@ export class WorkflowSchemaWorkspaceService {
}
private async computeFormActionOutputSchema({
formMetadata,
formFieldMetadataItems,
workspaceId,
}: {
formMetadata: FormFieldMetadata[];
formFieldMetadataItems: FormFieldMetadata[];
workspaceId: string;
}): Promise<OutputSchema> {
const objectMetadataMaps =
@@ -168,7 +168,7 @@ export class WorkflowSchemaWorkspaceService {
);
return generateFakeFormResponse({
formMetadata,
formFieldMetadataItems,
objectMetadataMaps,
});
}