400 workflows webhooks trigger (#11041)
https://github.com/user-attachments/assets/dc0ece22-4d87-417f-b9e1-a11c3fd52ce8
This commit is contained in:
@ -14,10 +14,11 @@ export const useTestWorkflowSingleRecordAction: ActionHookWithoutObjectMetadataI
|
|||||||
|
|
||||||
const shouldBeRegistered =
|
const shouldBeRegistered =
|
||||||
isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) &&
|
isDefined(workflowWithCurrentVersion?.currentVersion?.trigger) &&
|
||||||
workflowWithCurrentVersion.currentVersion.trigger.type === 'MANUAL' &&
|
((workflowWithCurrentVersion.currentVersion.trigger.type === 'MANUAL' &&
|
||||||
!isDefined(
|
!isDefined(
|
||||||
workflowWithCurrentVersion.currentVersion.trigger.settings.objectType,
|
workflowWithCurrentVersion.currentVersion.trigger.settings.objectType,
|
||||||
);
|
)) ||
|
||||||
|
workflowWithCurrentVersion.currentVersion.trigger.type === 'WEBHOOK');
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
if (!shouldBeRegistered) {
|
if (!shouldBeRegistered) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
IconRobot,
|
IconRobot,
|
||||||
IconSettingsAutomation,
|
IconSettingsAutomation,
|
||||||
IconUserCircle,
|
IconUserCircle,
|
||||||
|
IconWebhook,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
export const getActorSourceMultiSelectOptions = (
|
export const getActorSourceMultiSelectOptions = (
|
||||||
@ -53,6 +54,13 @@ export const getActorSourceMultiSelectOptions = (
|
|||||||
AvatarIcon: IconSettingsAutomation,
|
AvatarIcon: IconSettingsAutomation,
|
||||||
isIconInverted: true,
|
isIconInverted: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'WEBHOOK',
|
||||||
|
name: 'Webhook',
|
||||||
|
isSelected: selectedSourceNames.includes('WEBHOOK'),
|
||||||
|
AvatarIcon: IconWebhook,
|
||||||
|
isIconInverted: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'SYSTEM',
|
id: 'SYSTEM',
|
||||||
name: 'System',
|
name: 'System',
|
||||||
|
|||||||
@ -286,15 +286,18 @@ const FieldActorSourceSchema = z.union([
|
|||||||
z.literal('MANUAL'),
|
z.literal('MANUAL'),
|
||||||
z.literal('SYSTEM'),
|
z.literal('SYSTEM'),
|
||||||
z.literal('WORKFLOW'),
|
z.literal('WORKFLOW'),
|
||||||
|
z.literal('WEBHOOK'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const FieldActorValueSchema = z.object({
|
export const FieldActorValueSchema = z.object({
|
||||||
source: FieldActorSourceSchema,
|
source: FieldActorSourceSchema,
|
||||||
workspaceMemberId: z.string().nullable(),
|
workspaceMemberId: z.string().nullable(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
context: z.object({
|
context: z
|
||||||
provider: z.nativeEnum(ConnectedAccountProvider).optional(),
|
.object({
|
||||||
}),
|
provider: z.nativeEnum(ConnectedAccountProvider).optional(),
|
||||||
|
})
|
||||||
|
.nullable(),
|
||||||
});
|
});
|
||||||
export type FieldActorValue = z.infer<typeof FieldActorValueSchema>;
|
export type FieldActorValue = z.infer<typeof FieldActorValueSchema>;
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
IconMicrosoftOutlook,
|
IconMicrosoftOutlook,
|
||||||
IconRobot,
|
IconRobot,
|
||||||
IconSettingsAutomation,
|
IconSettingsAutomation,
|
||||||
|
IconWebhook,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
|
|
||||||
type ActorDisplayProps = Partial<FieldActorValue> & {
|
type ActorDisplayProps = Partial<FieldActorValue> & {
|
||||||
@ -54,6 +55,8 @@ export const ActorDisplay = ({
|
|||||||
return IconRobot;
|
return IconRobot;
|
||||||
case 'WORKFLOW':
|
case 'WORKFLOW':
|
||||||
return IconSettingsAutomation;
|
return IconSettingsAutomation;
|
||||||
|
case 'WEBHOOK':
|
||||||
|
return IconWebhook;
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,6 +65,7 @@ const StyledInput = styled.input<
|
|||||||
Pick<
|
Pick<
|
||||||
TextInputV2ComponentProps,
|
TextInputV2ComponentProps,
|
||||||
| 'LeftIcon'
|
| 'LeftIcon'
|
||||||
|
| 'RightIcon'
|
||||||
| 'error'
|
| 'error'
|
||||||
| 'sizeVariant'
|
| 'sizeVariant'
|
||||||
| 'width'
|
| 'width'
|
||||||
@ -112,9 +113,16 @@ const StyledInput = styled.input<
|
|||||||
: LeftIcon
|
: LeftIcon
|
||||||
? `calc(${theme.spacing(3)} + 16px)`
|
? `calc(${theme.spacing(3)} + 16px)`
|
||||||
: theme.spacing(2)};
|
: theme.spacing(2)};
|
||||||
|
padding-right: ${({ theme, RightIcon, autoGrow }) =>
|
||||||
|
autoGrow
|
||||||
|
? theme.spacing(1)
|
||||||
|
: RightIcon
|
||||||
|
? `calc(${theme.spacing(3)} + 16px)`
|
||||||
|
: theme.spacing(2)};
|
||||||
width: ${({ theme, width }) =>
|
width: ${({ theme, width }) =>
|
||||||
width ? `calc(${width}px + ${theme.spacing(0.5)})` : '100%'};
|
width ? `calc(${width}px + ${theme.spacing(0.5)})` : '100%'};
|
||||||
max-width: ${({ autoGrow }) => (autoGrow ? '100%' : 'none')};
|
max-width: ${({ autoGrow }) => (autoGrow ? '100%' : 'none')};
|
||||||
|
text-overflow: ellipsis;
|
||||||
&::placeholder,
|
&::placeholder,
|
||||||
&::-webkit-input-placeholder {
|
&::-webkit-input-placeholder {
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
@ -126,6 +134,10 @@ const StyledInput = styled.input<
|
|||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[readonly] {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
${({ theme, error }) => {
|
${({ theme, error }) => {
|
||||||
return `
|
return `
|
||||||
@ -165,7 +177,10 @@ const StyledTrailingIconContainer = styled.div<
|
|||||||
margin: auto 0;
|
margin: auto 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTrailingIcon = styled.div<{ isFocused?: boolean }>`
|
const StyledTrailingIcon = styled.div<{
|
||||||
|
isFocused?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: ${({ theme, isFocused }) =>
|
color: ${({ theme, isFocused }) =>
|
||||||
isFocused ? theme.font.color.secondary : theme.font.color.light};
|
isFocused ? theme.font.color.secondary : theme.font.color.light};
|
||||||
@ -189,6 +204,7 @@ export type TextInputV2ComponentProps = Omit<
|
|||||||
error?: string;
|
error?: string;
|
||||||
noErrorHelper?: boolean;
|
noErrorHelper?: boolean;
|
||||||
RightIcon?: IconComponent;
|
RightIcon?: IconComponent;
|
||||||
|
onRightIconClick?: () => void;
|
||||||
LeftIcon?: IconComponent;
|
LeftIcon?: IconComponent;
|
||||||
autoGrow?: boolean;
|
autoGrow?: boolean;
|
||||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||||
@ -224,8 +240,10 @@ const TextInputV2Component = forwardRef<
|
|||||||
autoFocus,
|
autoFocus,
|
||||||
placeholder,
|
placeholder,
|
||||||
disabled,
|
disabled,
|
||||||
|
readOnly,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
RightIcon,
|
RightIcon,
|
||||||
|
onRightIconClick,
|
||||||
LeftIcon,
|
LeftIcon,
|
||||||
autoComplete,
|
autoComplete,
|
||||||
maxLength,
|
maxLength,
|
||||||
@ -302,10 +320,12 @@ const TextInputV2Component = forwardRef<
|
|||||||
{...{
|
{...{
|
||||||
autoFocus,
|
autoFocus,
|
||||||
disabled,
|
disabled,
|
||||||
|
readOnly,
|
||||||
placeholder,
|
placeholder,
|
||||||
required,
|
required,
|
||||||
value,
|
value,
|
||||||
LeftIcon,
|
LeftIcon,
|
||||||
|
RightIcon,
|
||||||
maxLength,
|
maxLength,
|
||||||
error,
|
error,
|
||||||
sizeVariant,
|
sizeVariant,
|
||||||
@ -337,7 +357,9 @@ const TextInputV2Component = forwardRef<
|
|||||||
</StyledTrailingIcon>
|
</StyledTrailingIcon>
|
||||||
)}
|
)}
|
||||||
{!error && type !== INPUT_TYPE_PASSWORD && !!RightIcon && (
|
{!error && type !== INPUT_TYPE_PASSWORD && !!RightIcon && (
|
||||||
<StyledTrailingIcon>
|
<StyledTrailingIcon
|
||||||
|
onClick={onRightIconClick ? onRightIconClick : undefined}
|
||||||
|
>
|
||||||
<RightIcon size={theme.icon.size.md} />
|
<RightIcon size={theme.icon.size.md} />
|
||||||
</StyledTrailingIcon>
|
</StyledTrailingIcon>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {
|
|||||||
workflowTriggerSchema,
|
workflowTriggerSchema,
|
||||||
workflowUpdateRecordActionSchema,
|
workflowUpdateRecordActionSchema,
|
||||||
workflowUpdateRecordActionSettingsSchema,
|
workflowUpdateRecordActionSettingsSchema,
|
||||||
|
workflowWebhookTriggerSchema,
|
||||||
} from '@/workflow/validation-schemas/workflowSchema';
|
} from '@/workflow/validation-schemas/workflowSchema';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
@ -76,6 +77,9 @@ export type WorkflowDatabaseEventTrigger = z.infer<
|
|||||||
>;
|
>;
|
||||||
export type WorkflowManualTrigger = z.infer<typeof workflowManualTriggerSchema>;
|
export type WorkflowManualTrigger = z.infer<typeof workflowManualTriggerSchema>;
|
||||||
export type WorkflowCronTrigger = z.infer<typeof workflowCronTriggerSchema>;
|
export type WorkflowCronTrigger = z.infer<typeof workflowCronTriggerSchema>;
|
||||||
|
export type WorkflowWebhookTrigger = z.infer<
|
||||||
|
typeof workflowWebhookTriggerSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings'];
|
export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings'];
|
||||||
export type WorkflowManualTriggerAvailability =
|
export type WorkflowManualTriggerAvailability =
|
||||||
|
|||||||
@ -203,11 +203,19 @@ export const workflowCronTriggerSchema = baseTriggerSchema.extend({
|
|||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const workflowWebhookTriggerSchema = baseTriggerSchema.extend({
|
||||||
|
type: z.literal('WEBHOOK'),
|
||||||
|
settings: z.object({
|
||||||
|
outputSchema: z.object({}).passthrough(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
// Combined trigger schema
|
// Combined trigger schema
|
||||||
export const workflowTriggerSchema = z.discriminatedUnion('type', [
|
export const workflowTriggerSchema = z.discriminatedUnion('type', [
|
||||||
workflowDatabaseEventTriggerSchema,
|
workflowDatabaseEventTriggerSchema,
|
||||||
workflowManualTriggerSchema,
|
workflowManualTriggerSchema,
|
||||||
workflowCronTriggerSchema,
|
workflowCronTriggerSchema,
|
||||||
|
workflowWebhookTriggerSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Step output schemas
|
// Step output schemas
|
||||||
|
|||||||
@ -27,27 +27,10 @@ export const WorkflowDiagramStepNodeIcon = ({
|
|||||||
switch (data.nodeType) {
|
switch (data.nodeType) {
|
||||||
case 'trigger': {
|
case 'trigger': {
|
||||||
switch (data.triggerType) {
|
switch (data.triggerType) {
|
||||||
case 'DATABASE_EVENT': {
|
case 'DATABASE_EVENT':
|
||||||
return (
|
case 'MANUAL':
|
||||||
<StyledStepNodeLabelIconContainer>
|
case 'CRON':
|
||||||
<Icon
|
case 'WEBHOOK': {
|
||||||
size={theme.icon.size.md}
|
|
||||||
color={theme.font.color.tertiary}
|
|
||||||
/>
|
|
||||||
</StyledStepNodeLabelIconContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case 'MANUAL': {
|
|
||||||
return (
|
|
||||||
<StyledStepNodeLabelIconContainer>
|
|
||||||
<Icon
|
|
||||||
size={theme.icon.size.md}
|
|
||||||
color={theme.font.color.tertiary}
|
|
||||||
/>
|
|
||||||
</StyledStepNodeLabelIconContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case 'CRON': {
|
|
||||||
return (
|
return (
|
||||||
<StyledStepNodeLabelIconContainer>
|
<StyledStepNodeLabelIconContainer>
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@ -33,6 +33,14 @@ export const getWorkflowDiagramTriggerNode = ({
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'WEBHOOK': {
|
||||||
|
triggerDefaultLabel = 'Webhook';
|
||||||
|
triggerIcon = getTriggerIcon({
|
||||||
|
type: 'WEBHOOK',
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'DATABASE_EVENT': {
|
case 'DATABASE_EVENT': {
|
||||||
const triggerEvent = splitWorkflowTriggerEventName(
|
const triggerEvent = splitWorkflowTriggerEventName(
|
||||||
trigger.settings.eventName,
|
trigger.settings.eventName,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { WorkflowEditTriggerManualForm } from '@/workflow/workflow-trigger/compo
|
|||||||
import { Suspense, lazy } from 'react';
|
import { Suspense, lazy } from 'react';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
|
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
|
||||||
|
import { WorkflowEditTriggerWebhookForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerWebhookForm';
|
||||||
|
|
||||||
const WorkflowEditActionServerlessFunction = lazy(() =>
|
const WorkflowEditActionServerlessFunction = lazy(() =>
|
||||||
import(
|
import(
|
||||||
@ -85,6 +86,15 @@ export const WorkflowStepDetail = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case 'WEBHOOK': {
|
||||||
|
return (
|
||||||
|
<WorkflowEditTriggerWebhookForm
|
||||||
|
key={stepId}
|
||||||
|
trigger={stepDefinition.definition}
|
||||||
|
triggerOptions={props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
case 'CRON': {
|
case 'CRON': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditTriggerCronForm
|
<WorkflowEditTriggerCronForm
|
||||||
|
|||||||
@ -0,0 +1,99 @@
|
|||||||
|
import { WorkflowWebhookTrigger } from '@/workflow/types/Workflow';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { useIcons, IconCopy } from 'twenty-ui';
|
||||||
|
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
|
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||||
|
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||||
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||||
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
|
|
||||||
|
type WorkflowEditTriggerWebhookFormProps = {
|
||||||
|
trigger: WorkflowWebhookTrigger;
|
||||||
|
triggerOptions:
|
||||||
|
| {
|
||||||
|
readonly: true;
|
||||||
|
onTriggerUpdate?: undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
readonly?: false;
|
||||||
|
onTriggerUpdate: (trigger: WorkflowWebhookTrigger) => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const WorkflowEditTriggerWebhookForm = ({
|
||||||
|
trigger,
|
||||||
|
triggerOptions,
|
||||||
|
}: WorkflowEditTriggerWebhookFormProps) => {
|
||||||
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useLingui();
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
const workflowId = useRecoilValue(workflowIdState);
|
||||||
|
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||||
|
|
||||||
|
const headerTitle = isDefined(trigger.name) ? trigger.name : 'Webhook';
|
||||||
|
|
||||||
|
const headerIcon = getTriggerIcon({
|
||||||
|
type: 'WEBHOOK',
|
||||||
|
});
|
||||||
|
|
||||||
|
const webhookUrl = `${REACT_APP_SERVER_BASE_URL}/webhooks/workflows/${currentWorkspace?.id}/${workflowId}`;
|
||||||
|
const displayWebhookUrl = webhookUrl.replace(/^(https?:\/\/)?(www\.)?/, '');
|
||||||
|
|
||||||
|
const copyToClipboard = async () => {
|
||||||
|
await navigator.clipboard.writeText(webhookUrl);
|
||||||
|
enqueueSnackBar(t`Copied to clipboard!`, {
|
||||||
|
variant: SnackBarVariant.Success,
|
||||||
|
icon: <IconCopy size={theme.icon.size.md} />,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyToClipboardDebounced = useDebouncedCallback(copyToClipboard, 200);
|
||||||
|
|
||||||
|
if (!isDefined(currentWorkspace)) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WorkflowStepHeader
|
||||||
|
onTitleChange={(newName: string) => {
|
||||||
|
if (triggerOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerOptions.onTriggerUpdate({
|
||||||
|
...trigger,
|
||||||
|
name: newName,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
Icon={getIcon(headerIcon)}
|
||||||
|
iconColor={theme.font.color.tertiary}
|
||||||
|
initialTitle={headerTitle}
|
||||||
|
headerType="Trigger · Webhook"
|
||||||
|
disabled={triggerOptions.readonly}
|
||||||
|
/>
|
||||||
|
<WorkflowStepBody>
|
||||||
|
<TextInputV2
|
||||||
|
label="Live URL"
|
||||||
|
value={displayWebhookUrl}
|
||||||
|
RightIcon={() => (
|
||||||
|
<IconCopy
|
||||||
|
size={theme.icon.size.md}
|
||||||
|
color={theme.font.color.secondary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
onRightIconClick={copyToClipboardDebounced}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</WorkflowStepBody>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -15,4 +15,9 @@ export const OTHER_TRIGGER_TYPES: Array<{
|
|||||||
type: 'CRON',
|
type: 'CRON',
|
||||||
icon: 'IconClock',
|
icon: 'IconClock',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
defaultLabel: 'Webhook',
|
||||||
|
type: 'WEBHOOK',
|
||||||
|
icon: 'IconWebhook',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -58,6 +58,15 @@ export const getTriggerDefaultDefinition = ({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case 'WEBHOOK': {
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
name: defaultLabel,
|
||||||
|
settings: {
|
||||||
|
outputSchema: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return assertUnreachable(type, `Unknown type: ${type}`);
|
return assertUnreachable(type, `Unknown type: ${type}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,9 @@ export const getTriggerIcon = (
|
|||||||
| {
|
| {
|
||||||
type: 'CRON';
|
type: 'CRON';
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'WEBHOOK';
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'DATABASE_EVENT';
|
type: 'DATABASE_EVENT';
|
||||||
eventName: string;
|
eventName: string;
|
||||||
|
|||||||
@ -9,6 +9,9 @@ export const getTriggerDefaultLabel = (
|
|||||||
| {
|
| {
|
||||||
type: 'CRON';
|
type: 'CRON';
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'WEBHOOK';
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'DATABASE_EVENT';
|
type: 'DATABASE_EVENT';
|
||||||
eventName: string;
|
eventName: string;
|
||||||
|
|||||||
@ -12,6 +12,8 @@ export const getTriggerStepName = (trigger: WorkflowTrigger): string => {
|
|||||||
return getDatabaseEventTriggerStepName(trigger);
|
return getDatabaseEventTriggerStepName(trigger);
|
||||||
case 'CRON':
|
case 'CRON':
|
||||||
return 'On a Schedule';
|
return 'On a Schedule';
|
||||||
|
case 'WEBHOOK':
|
||||||
|
return 'Webhook';
|
||||||
case 'MANUAL':
|
case 'MANUAL':
|
||||||
if (!isDefined(trigger.settings.objectType)) {
|
if (!isDefined(trigger.settings.objectType)) {
|
||||||
return 'Manual trigger';
|
return 'Manual trigger';
|
||||||
|
|||||||
@ -190,6 +190,7 @@ describe('computeSchemaComponents', () => {
|
|||||||
'IMPORT',
|
'IMPORT',
|
||||||
'MANUAL',
|
'MANUAL',
|
||||||
'SYSTEM',
|
'SYSTEM',
|
||||||
|
'WEBHOOK',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -378,6 +379,7 @@ describe('computeSchemaComponents', () => {
|
|||||||
'IMPORT',
|
'IMPORT',
|
||||||
'MANUAL',
|
'MANUAL',
|
||||||
'SYSTEM',
|
'SYSTEM',
|
||||||
|
'WEBHOOK',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -565,6 +567,7 @@ describe('computeSchemaComponents', () => {
|
|||||||
'IMPORT',
|
'IMPORT',
|
||||||
'MANUAL',
|
'MANUAL',
|
||||||
'SYSTEM',
|
'SYSTEM',
|
||||||
|
'WEBHOOK',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
workspaceMemberId: {
|
workspaceMemberId: {
|
||||||
|
|||||||
@ -211,6 +211,7 @@ const getSchemaComponentsProperties = ({
|
|||||||
'IMPORT',
|
'IMPORT',
|
||||||
'MANUAL',
|
'MANUAL',
|
||||||
'SYSTEM',
|
'SYSTEM',
|
||||||
|
'WEBHOOK',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
...(forResponse
|
...(forResponse
|
||||||
|
|||||||
@ -0,0 +1,96 @@
|
|||||||
|
import { Controller, Get, Param, UseFilters } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
|
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
||||||
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
|
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
import { WorkflowTriggerRestApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-rest-api-exception.filter';
|
||||||
|
import {
|
||||||
|
WorkflowTriggerException,
|
||||||
|
WorkflowTriggerExceptionCode,
|
||||||
|
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||||
|
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
|
||||||
|
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||||
|
|
||||||
|
@Controller('webhooks')
|
||||||
|
@UseFilters(WorkflowTriggerRestApiExceptionFilter)
|
||||||
|
export class WorkflowTriggerController {
|
||||||
|
constructor(
|
||||||
|
private readonly twentyORMManager: TwentyORMManager,
|
||||||
|
private readonly workflowTriggerWorkspaceService: WorkflowTriggerWorkspaceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('workflows/:workspaceId/:workflowId')
|
||||||
|
async runWorkflow(
|
||||||
|
@Param('workspaceId') workspaceId: string,
|
||||||
|
@Param('workflowId') workflowId: string,
|
||||||
|
) {
|
||||||
|
const workflowRepository =
|
||||||
|
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
|
||||||
|
'workflow',
|
||||||
|
);
|
||||||
|
|
||||||
|
const workflow = await workflowRepository.findOne({
|
||||||
|
where: { id: workflowId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDefined(workflow)) {
|
||||||
|
throw new WorkflowTriggerException(
|
||||||
|
'Workflow not found',
|
||||||
|
WorkflowTriggerExceptionCode.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isDefined(workflow.lastPublishedVersionId) ||
|
||||||
|
workflow.lastPublishedVersionId === ''
|
||||||
|
) {
|
||||||
|
throw new WorkflowTriggerException(
|
||||||
|
'Workflow not activated',
|
||||||
|
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowVersionRepository =
|
||||||
|
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
||||||
|
'workflowVersion',
|
||||||
|
);
|
||||||
|
const workflowVersion = await workflowVersionRepository.findOne({
|
||||||
|
where: { id: workflow.lastPublishedVersionId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDefined(workflowVersion)) {
|
||||||
|
throw new WorkflowTriggerException(
|
||||||
|
'Workflow version not found',
|
||||||
|
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workflowVersion.trigger?.type !== WorkflowTriggerType.WEBHOOK) {
|
||||||
|
throw new WorkflowTriggerException(
|
||||||
|
'Workflow does not have a Webhook trigger',
|
||||||
|
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { workflowRunId } =
|
||||||
|
await this.workflowTriggerWorkspaceService.runWorkflowVersion({
|
||||||
|
workflowVersionId: workflow.lastPublishedVersionId,
|
||||||
|
payload: {},
|
||||||
|
createdBy: {
|
||||||
|
source: FieldActorSource.WEBHOOK,
|
||||||
|
workspaceMemberId: null,
|
||||||
|
name: 'Webhook',
|
||||||
|
context: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
workflowName: workflow.name,
|
||||||
|
success: true,
|
||||||
|
workflowRunId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ import { Catch, ExceptionFilter } from '@nestjs/common';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
|
NotFoundError,
|
||||||
UserInputError,
|
UserInputError,
|
||||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||||
import {
|
import {
|
||||||
@ -19,8 +20,11 @@ export class WorkflowTriggerGraphqlApiExceptionFilter
|
|||||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION:
|
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION:
|
||||||
case WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE:
|
case WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE:
|
||||||
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
|
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
|
||||||
|
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS:
|
||||||
case WorkflowTriggerExceptionCode.FORBIDDEN:
|
case WorkflowTriggerExceptionCode.FORBIDDEN:
|
||||||
throw new UserInputError(exception.message);
|
throw new UserInputError(exception.message);
|
||||||
|
case WorkflowTriggerExceptionCode.NOT_FOUND:
|
||||||
|
throw new NotFoundError(exception.message);
|
||||||
default:
|
default:
|
||||||
throw new InternalServerError(exception.message);
|
throw new InternalServerError(exception.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
|
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||||
|
import { CustomException } from 'src/utils/custom-exception';
|
||||||
|
import {
|
||||||
|
WorkflowTriggerException,
|
||||||
|
WorkflowTriggerExceptionCode,
|
||||||
|
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||||
|
|
||||||
|
@Catch()
|
||||||
|
export class WorkflowTriggerRestApiExceptionFilter implements ExceptionFilter {
|
||||||
|
constructor(
|
||||||
|
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
catch(exception: WorkflowTriggerException, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const response = ctx.getResponse<Response>();
|
||||||
|
|
||||||
|
switch (exception.code) {
|
||||||
|
case WorkflowTriggerExceptionCode.INVALID_INPUT:
|
||||||
|
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER:
|
||||||
|
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION:
|
||||||
|
case WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE:
|
||||||
|
case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS:
|
||||||
|
return this.httpExceptionHandlerService.handleError(
|
||||||
|
exception as CustomException,
|
||||||
|
response,
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
case WorkflowTriggerExceptionCode.FORBIDDEN:
|
||||||
|
return this.httpExceptionHandlerService.handleError(
|
||||||
|
exception as CustomException,
|
||||||
|
response,
|
||||||
|
403,
|
||||||
|
);
|
||||||
|
case WorkflowTriggerExceptionCode.NOT_FOUND:
|
||||||
|
return this.httpExceptionHandlerService.handleError(
|
||||||
|
exception as CustomException,
|
||||||
|
response,
|
||||||
|
404,
|
||||||
|
);
|
||||||
|
case WorkflowTriggerExceptionCode.INTERNAL_ERROR:
|
||||||
|
default:
|
||||||
|
return this.httpExceptionHandlerService.handleError(
|
||||||
|
exception as CustomException,
|
||||||
|
response,
|
||||||
|
500,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace
|
|||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
||||||
|
import { buildCreatedByFromFullNameMetadata } from 'src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
||||||
@ -43,11 +44,16 @@ export class WorkflowTriggerResolver {
|
|||||||
@AuthUser() user: User,
|
@AuthUser() user: User,
|
||||||
@Args('input') { workflowVersionId, payload }: RunWorkflowVersionInput,
|
@Args('input') { workflowVersionId, payload }: RunWorkflowVersionInput,
|
||||||
) {
|
) {
|
||||||
return await this.workflowTriggerWorkspaceService.runWorkflowVersion(
|
return await this.workflowTriggerWorkspaceService.runWorkflowVersion({
|
||||||
workflowVersionId,
|
workflowVersionId,
|
||||||
payload ?? {},
|
payload: payload ?? {},
|
||||||
workspaceMemberId,
|
createdBy: buildCreatedByFromFullNameMetadata({
|
||||||
user,
|
fullNameMetadata: {
|
||||||
);
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
},
|
||||||
|
workspaceMemberId: workspaceMemberId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
|||||||
import { WorkflowBuilderModule } from 'src/modules/workflow/workflow-builder/workflow-builder.module';
|
import { WorkflowBuilderModule } from 'src/modules/workflow/workflow-builder/workflow-builder.module';
|
||||||
import { WorkflowVersionModule } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.module';
|
import { WorkflowVersionModule } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.module';
|
||||||
import { WorkflowTriggerModule } from 'src/modules/workflow/workflow-trigger/workflow-trigger.module';
|
import { WorkflowTriggerModule } from 'src/modules/workflow/workflow-trigger/workflow-trigger.module';
|
||||||
|
import { WorkflowTriggerController } from 'src/engine/core-modules/workflow/controllers/workflow-trigger.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -16,6 +17,7 @@ import { WorkflowTriggerModule } from 'src/modules/workflow/workflow-trigger/wor
|
|||||||
WorkflowCommonModule,
|
WorkflowCommonModule,
|
||||||
WorkflowVersionModule,
|
WorkflowVersionModule,
|
||||||
],
|
],
|
||||||
|
controllers: [WorkflowTriggerController],
|
||||||
providers: [
|
providers: [
|
||||||
WorkflowTriggerResolver,
|
WorkflowTriggerResolver,
|
||||||
WorkflowBuilderResolver,
|
WorkflowBuilderResolver,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export enum FieldActorSource {
|
|||||||
IMPORT = 'IMPORT',
|
IMPORT = 'IMPORT',
|
||||||
MANUAL = 'MANUAL',
|
MANUAL = 'MANUAL',
|
||||||
SYSTEM = 'SYSTEM',
|
SYSTEM = 'SYSTEM',
|
||||||
|
WEBHOOK = 'WEBHOOK',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actorCompositeType: CompositeType = {
|
export const actorCompositeType: CompositeType = {
|
||||||
|
|||||||
@ -14,7 +14,8 @@ export class ScopedWorkspaceContextFactory {
|
|||||||
workspaceMetadataVersion: number | null;
|
workspaceMetadataVersion: number | null;
|
||||||
} {
|
} {
|
||||||
const workspaceId: string | undefined =
|
const workspaceId: string | undefined =
|
||||||
this.request?.['req']?.['workspaceId'];
|
this.request?.['req']?.['workspaceId'] ||
|
||||||
|
this.request?.['params']?.['workspaceId'];
|
||||||
const workspaceMetadataVersion: number | undefined =
|
const workspaceMetadataVersion: number | undefined =
|
||||||
this.request?.['req']?.['workspaceMetadataVersion'];
|
this.request?.['req']?.['workspaceMetadataVersion'];
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,7 @@ export class WorkflowSchemaWorkspaceService {
|
|||||||
objectMetadataRepository: this.objectMetadataRepository,
|
objectMetadataRepository: this.objectMetadataRepository,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
case WorkflowTriggerType.WEBHOOK:
|
||||||
case WorkflowTriggerType.CRON: {
|
case WorkflowTriggerType.CRON: {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,9 @@ export enum WorkflowTriggerExceptionCode {
|
|||||||
INVALID_INPUT = 'INVALID_INPUT',
|
INVALID_INPUT = 'INVALID_INPUT',
|
||||||
INVALID_WORKFLOW_TRIGGER = 'INVALID_WORKFLOW_TRIGGER',
|
INVALID_WORKFLOW_TRIGGER = 'INVALID_WORKFLOW_TRIGGER',
|
||||||
INVALID_WORKFLOW_VERSION = 'INVALID_WORKFLOW_VERSION',
|
INVALID_WORKFLOW_VERSION = 'INVALID_WORKFLOW_VERSION',
|
||||||
|
INVALID_WORKFLOW_STATUS = 'INVALID_WORKFLOW_STATUS',
|
||||||
INVALID_ACTION_TYPE = 'INVALID_ACTION_TYPE',
|
INVALID_ACTION_TYPE = 'INVALID_ACTION_TYPE',
|
||||||
|
NOT_FOUND = 'NOT_FOUND',
|
||||||
FORBIDDEN = 'FORBIDDEN',
|
FORBIDDEN = 'FORBIDDEN',
|
||||||
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export enum WorkflowTriggerType {
|
|||||||
DATABASE_EVENT = 'DATABASE_EVENT',
|
DATABASE_EVENT = 'DATABASE_EVENT',
|
||||||
MANUAL = 'MANUAL',
|
MANUAL = 'MANUAL',
|
||||||
CRON = 'CRON',
|
CRON = 'CRON',
|
||||||
|
WEBHOOK = 'WEBHOOK',
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseWorkflowTriggerSettings = {
|
type BaseWorkflowTriggerSettings = {
|
||||||
@ -58,9 +59,14 @@ export type WorkflowCronTrigger = BaseTrigger & {
|
|||||||
) & { outputSchema: object };
|
) & { outputSchema: object };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WorkflowWebhookTrigger = BaseTrigger & {
|
||||||
|
type: WorkflowTriggerType.WEBHOOK;
|
||||||
|
};
|
||||||
|
|
||||||
export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings'];
|
export type WorkflowManualTriggerSettings = WorkflowManualTrigger['settings'];
|
||||||
|
|
||||||
export type WorkflowTrigger =
|
export type WorkflowTrigger =
|
||||||
| WorkflowDatabaseEventTrigger
|
| WorkflowDatabaseEventTrigger
|
||||||
| WorkflowManualTrigger
|
| WorkflowManualTrigger
|
||||||
| WorkflowCronTrigger;
|
| WorkflowCronTrigger
|
||||||
|
| WorkflowWebhookTrigger;
|
||||||
|
|||||||
@ -69,6 +69,7 @@ function assertTriggerSettingsAreValid(
|
|||||||
assertDatabaseEventTriggerSettingsAreValid(settings);
|
assertDatabaseEventTriggerSettingsAreValid(settings);
|
||||||
break;
|
break;
|
||||||
case WorkflowTriggerType.MANUAL:
|
case WorkflowTriggerType.MANUAL:
|
||||||
|
case WorkflowTriggerType.WEBHOOK:
|
||||||
break;
|
break;
|
||||||
case WorkflowTriggerType.CRON:
|
case WorkflowTriggerType.CRON:
|
||||||
assertCronTriggerSettingsAreValid(settings);
|
assertCronTriggerSettingsAreValid(settings);
|
||||||
|
|||||||
@ -4,11 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { EntityManager, Repository } from 'typeorm';
|
import { EntityManager, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { buildCreatedByFromFullNameMetadata } from 'src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util';
|
|
||||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
@ -37,6 +35,7 @@ import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types
|
|||||||
import { assertVersionCanBeActivated } from 'src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util';
|
import { assertVersionCanBeActivated } from 'src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util';
|
||||||
import { computeCronPatternFromSchedule } from 'src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule';
|
import { computeCronPatternFromSchedule } from 'src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule';
|
||||||
import { assertNever } from 'src/utils/assert';
|
import { assertNever } from 'src/utils/assert';
|
||||||
|
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowTriggerWorkspaceService {
|
export class WorkflowTriggerWorkspaceService {
|
||||||
@ -66,12 +65,15 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
return workspaceId;
|
return workspaceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async runWorkflowVersion(
|
async runWorkflowVersion({
|
||||||
workflowVersionId: string,
|
workflowVersionId,
|
||||||
payload: object,
|
payload,
|
||||||
workspaceMemberId: string,
|
createdBy,
|
||||||
{ firstName, lastName }: User,
|
}: {
|
||||||
) {
|
workflowVersionId: string;
|
||||||
|
payload: object;
|
||||||
|
createdBy: ActorMetadata;
|
||||||
|
}) {
|
||||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
|
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
|
||||||
workflowVersionId,
|
workflowVersionId,
|
||||||
);
|
);
|
||||||
@ -80,10 +82,7 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
this.getWorkspaceId(),
|
this.getWorkspaceId(),
|
||||||
workflowVersionId,
|
workflowVersionId,
|
||||||
payload,
|
payload,
|
||||||
buildCreatedByFromFullNameMetadata({
|
createdBy,
|
||||||
fullNameMetadata: { firstName, lastName },
|
|
||||||
workspaceMemberId,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,6 +341,7 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
case WorkflowTriggerType.MANUAL:
|
case WorkflowTriggerType.MANUAL:
|
||||||
|
case WorkflowTriggerType.WEBHOOK:
|
||||||
return;
|
return;
|
||||||
case WorkflowTriggerType.CRON: {
|
case WorkflowTriggerType.CRON: {
|
||||||
const pattern = computeCronPatternFromSchedule(workflowVersion.trigger);
|
const pattern = computeCronPatternFromSchedule(workflowVersion.trigger);
|
||||||
@ -384,6 +384,7 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
case WorkflowTriggerType.MANUAL:
|
case WorkflowTriggerType.MANUAL:
|
||||||
|
case WorkflowTriggerType.WEBHOOK:
|
||||||
return;
|
return;
|
||||||
case WorkflowTriggerType.CRON:
|
case WorkflowTriggerType.CRON:
|
||||||
await this.messageQueueService.removeCron({
|
await this.messageQueueService.removeCron({
|
||||||
|
|||||||
Reference in New Issue
Block a user