Add base form action without logic (#10811)
<img width="1298" alt="Capture d’écran 2025-03-12 à 15 32 27" src="https://github.com/user-attachments/assets/8a3140e5-e165-445e-a718-748aa76b525c" />
This commit is contained in:
@ -560,7 +560,6 @@ export enum FeatureFlagKey {
|
|||||||
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
||||||
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
||||||
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
||||||
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
|
|
||||||
IsCopilotEnabled = 'IsCopilotEnabled',
|
IsCopilotEnabled = 'IsCopilotEnabled',
|
||||||
IsCustomDomainEnabled = 'IsCustomDomainEnabled',
|
IsCustomDomainEnabled = 'IsCustomDomainEnabled',
|
||||||
IsEventObjectEnabled = 'IsEventObjectEnabled',
|
IsEventObjectEnabled = 'IsEventObjectEnabled',
|
||||||
@ -570,7 +569,8 @@ export enum FeatureFlagKey {
|
|||||||
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
||||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||||
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
||||||
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
IsWorkflowEnabled = 'IsWorkflowEnabled',
|
||||||
|
IsWorkflowFormActionEnabled = 'IsWorkflowFormActionEnabled'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Field = {
|
export type Field = {
|
||||||
|
|||||||
@ -491,7 +491,6 @@ export enum FeatureFlagKey {
|
|||||||
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled',
|
||||||
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
IsAnalyticsV2Enabled = 'IsAnalyticsV2Enabled',
|
||||||
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
IsApprovedAccessDomainsEnabled = 'IsApprovedAccessDomainsEnabled',
|
||||||
IsCommandMenuV2Enabled = 'IsCommandMenuV2Enabled',
|
|
||||||
IsCopilotEnabled = 'IsCopilotEnabled',
|
IsCopilotEnabled = 'IsCopilotEnabled',
|
||||||
IsCustomDomainEnabled = 'IsCustomDomainEnabled',
|
IsCustomDomainEnabled = 'IsCustomDomainEnabled',
|
||||||
IsEventObjectEnabled = 'IsEventObjectEnabled',
|
IsEventObjectEnabled = 'IsEventObjectEnabled',
|
||||||
@ -501,7 +500,8 @@ export enum FeatureFlagKey {
|
|||||||
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled',
|
||||||
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled',
|
||||||
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled',
|
||||||
IsWorkflowEnabled = 'IsWorkflowEnabled'
|
IsWorkflowEnabled = 'IsWorkflowEnabled',
|
||||||
|
IsWorkflowFormActionEnabled = 'IsWorkflowFormActionEnabled'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Field = {
|
export type Field = {
|
||||||
@ -1658,6 +1658,8 @@ export type ServerlessFunctionExecutionResult = {
|
|||||||
duration: Scalars['Float'];
|
duration: Scalars['Float'];
|
||||||
/** Execution error in JSON format */
|
/** Execution error in JSON format */
|
||||||
error?: Maybe<Scalars['JSON']>;
|
error?: Maybe<Scalars['JSON']>;
|
||||||
|
/** Execution Logs */
|
||||||
|
logs: Scalars['String'];
|
||||||
/** Execution status */
|
/** Execution status */
|
||||||
status: ServerlessFunctionExecutionStatus;
|
status: ServerlessFunctionExecutionStatus;
|
||||||
};
|
};
|
||||||
@ -2220,11 +2222,6 @@ export type GetTimelineThreadsFromPersonIdQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
|
export type GetTimelineThreadsFromPersonIdQuery = { __typename?: 'Query', getTimelineThreadsFromPersonId: { __typename?: 'TimelineThreadsWithTotal', totalNumberOfThreads: number, timelineThreads: Array<{ __typename?: 'TimelineThread', id: any, read: boolean, visibility: MessageChannelVisibility, lastMessageReceivedAt: string, lastMessageBody: string, subject: string, numberOfMessagesInThread: number, participantCount: number, firstParticipant: { __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }, lastTwoParticipants: Array<{ __typename?: 'TimelineThreadParticipant', personId?: any | null, workspaceMemberId?: any | null, firstName: string, lastName: string, displayName: string, avatarUrl: string, handle: string }> }> } };
|
||||||
|
|
||||||
export type EmptyQueryVariables = Exact<{ [key: string]: never; }>;
|
|
||||||
|
|
||||||
|
|
||||||
export type EmptyQuery = { __typename: 'Query' };
|
|
||||||
|
|
||||||
export type TrackMutationVariables = Exact<{
|
export type TrackMutationVariables = Exact<{
|
||||||
action: Scalars['String'];
|
action: Scalars['String'];
|
||||||
payload: Scalars['JSON'];
|
payload: Scalars['JSON'];
|
||||||
@ -3065,38 +3062,6 @@ export function useGetTimelineThreadsFromPersonIdLazyQuery(baseOptions?: Apollo.
|
|||||||
export type GetTimelineThreadsFromPersonIdQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdQuery>;
|
export type GetTimelineThreadsFromPersonIdQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdQuery>;
|
||||||
export type GetTimelineThreadsFromPersonIdLazyQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdLazyQuery>;
|
export type GetTimelineThreadsFromPersonIdLazyQueryHookResult = ReturnType<typeof useGetTimelineThreadsFromPersonIdLazyQuery>;
|
||||||
export type GetTimelineThreadsFromPersonIdQueryResult = Apollo.QueryResult<GetTimelineThreadsFromPersonIdQuery, GetTimelineThreadsFromPersonIdQueryVariables>;
|
export type GetTimelineThreadsFromPersonIdQueryResult = Apollo.QueryResult<GetTimelineThreadsFromPersonIdQuery, GetTimelineThreadsFromPersonIdQueryVariables>;
|
||||||
export const EmptyDocument = gql`
|
|
||||||
query Empty {
|
|
||||||
__typename
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useEmptyQuery__
|
|
||||||
*
|
|
||||||
* To run a query within a React component, call `useEmptyQuery` and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useEmptyQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
|
||||||
* you can use to render your UI.
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const { data, loading, error } = useEmptyQuery({
|
|
||||||
* variables: {
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useEmptyQuery(baseOptions?: Apollo.QueryHookOptions<EmptyQuery, EmptyQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useQuery<EmptyQuery, EmptyQueryVariables>(EmptyDocument, options);
|
|
||||||
}
|
|
||||||
export function useEmptyLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<EmptyQuery, EmptyQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useLazyQuery<EmptyQuery, EmptyQueryVariables>(EmptyDocument, options);
|
|
||||||
}
|
|
||||||
export type EmptyQueryHookResult = ReturnType<typeof useEmptyQuery>;
|
|
||||||
export type EmptyLazyQueryHookResult = ReturnType<typeof useEmptyLazyQuery>;
|
|
||||||
export type EmptyQueryResult = Apollo.QueryResult<EmptyQuery, EmptyQueryVariables>;
|
|
||||||
export const TrackDocument = gql`
|
export const TrackDocument = gql`
|
||||||
mutation Track($action: String!, $payload: JSON!) {
|
mutation Track($action: String!, $payload: JSON!) {
|
||||||
track(action: $action, payload: $payload) {
|
track(action: $action, payload: $payload) {
|
||||||
|
|||||||
@ -26,27 +26,12 @@ import { RecordFiltersComponentInstanceContext } from '@/object-record/record-fi
|
|||||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||||
import { HttpResponse, graphql } from 'msw';
|
import { HttpResponse, graphql } from 'msw';
|
||||||
import { IconDotsVertical } from 'twenty-ui';
|
import { IconDotsVertical } from 'twenty-ui';
|
||||||
import { FeatureFlagKey } from '~/generated/graphql';
|
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
|
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
|
||||||
import { CommandMenu } from '../CommandMenu';
|
import { CommandMenu } from '../CommandMenu';
|
||||||
|
|
||||||
const openTimeout = 50;
|
const openTimeout = 50;
|
||||||
|
|
||||||
// Mock workspace with feature flag enabled
|
|
||||||
const mockWorkspaceWithFeatureFlag = {
|
|
||||||
...mockCurrentWorkspace,
|
|
||||||
featureFlags: [
|
|
||||||
...(mockCurrentWorkspace.featureFlags || []),
|
|
||||||
{
|
|
||||||
id: 'mock-id',
|
|
||||||
key: FeatureFlagKey.IsCommandMenuV2Enabled,
|
|
||||||
value: true,
|
|
||||||
workspaceId: mockCurrentWorkspace.id,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContextStoreDecorator: Decorator = (Story) => {
|
const ContextStoreDecorator: Decorator = (Story) => {
|
||||||
return (
|
return (
|
||||||
<RecordFilterGroupsComponentInstanceContext.Provider
|
<RecordFilterGroupsComponentInstanceContext.Provider
|
||||||
@ -92,7 +77,7 @@ const meta: Meta<typeof CommandMenu> = {
|
|||||||
commandMenuNavigationStackState,
|
commandMenuNavigationStackState,
|
||||||
);
|
);
|
||||||
|
|
||||||
setCurrentWorkspace(mockWorkspaceWithFeatureFlag);
|
setCurrentWorkspace(mockCurrentWorkspace);
|
||||||
setCurrentWorkspaceMember(mockedWorkspaceMemberData);
|
setCurrentWorkspaceMember(mockedWorkspaceMemberData);
|
||||||
setIsCommandMenuOpened(true);
|
setIsCommandMenuOpened(true);
|
||||||
setCommandMenuNavigationStack([
|
setCommandMenuNavigationStack([
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/co
|
|||||||
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
|
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
|
||||||
import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions';
|
import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions';
|
||||||
import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions';
|
import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { MenuItemCommand, useIcons } from 'twenty-ui';
|
import { MenuItemCommand, useIcons } from 'twenty-ui';
|
||||||
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export const CommandMenuWorkflowSelectActionContent = ({
|
export const CommandMenuWorkflowSelectActionContent = ({
|
||||||
workflow,
|
workflow,
|
||||||
@ -15,6 +17,9 @@ export const CommandMenuWorkflowSelectActionContent = ({
|
|||||||
const { createStep } = useCreateStep({
|
const { createStep } = useCreateStep({
|
||||||
workflow,
|
workflow,
|
||||||
});
|
});
|
||||||
|
const isWorkflowFormActionEnabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsWorkflowFormActionEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightDrawerStepListContainer>
|
<RightDrawerStepListContainer>
|
||||||
@ -32,7 +37,9 @@ export const CommandMenuWorkflowSelectActionContent = ({
|
|||||||
<RightDrawerWorkflowSelectStepTitle>
|
<RightDrawerWorkflowSelectStepTitle>
|
||||||
Other
|
Other
|
||||||
</RightDrawerWorkflowSelectStepTitle>
|
</RightDrawerWorkflowSelectStepTitle>
|
||||||
{OTHER_ACTIONS.map((action) => (
|
{OTHER_ACTIONS.filter(
|
||||||
|
(action) => isWorkflowFormActionEnabled || action.type !== 'FORM',
|
||||||
|
).map((action) => (
|
||||||
<MenuItemCommand
|
<MenuItemCommand
|
||||||
key={action.type}
|
key={action.type}
|
||||||
LeftIcon={getIcon(action.icon)}
|
LeftIcon={getIcon(action.icon)}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import {
|
|||||||
workflowDeleteRecordActionSettingsSchema,
|
workflowDeleteRecordActionSettingsSchema,
|
||||||
workflowFindRecordsActionSchema,
|
workflowFindRecordsActionSchema,
|
||||||
workflowFindRecordsActionSettingsSchema,
|
workflowFindRecordsActionSettingsSchema,
|
||||||
|
workflowFormActionSchema,
|
||||||
|
workflowFormActionSettingsSchema,
|
||||||
workflowManualTriggerSchema,
|
workflowManualTriggerSchema,
|
||||||
workflowRunContextSchema,
|
workflowRunContextSchema,
|
||||||
workflowRunOutputSchema,
|
workflowRunOutputSchema,
|
||||||
@ -42,6 +44,9 @@ export type WorkflowDeleteRecordActionSettings = z.infer<
|
|||||||
export type WorkflowFindRecordsActionSettings = z.infer<
|
export type WorkflowFindRecordsActionSettings = z.infer<
|
||||||
typeof workflowFindRecordsActionSettingsSchema
|
typeof workflowFindRecordsActionSettingsSchema
|
||||||
>;
|
>;
|
||||||
|
export type WorkflowFormActionSettings = z.infer<
|
||||||
|
typeof workflowFormActionSettingsSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export type WorkflowCodeAction = z.infer<typeof workflowCodeActionSchema>;
|
export type WorkflowCodeAction = z.infer<typeof workflowCodeActionSchema>;
|
||||||
export type WorkflowSendEmailAction = z.infer<
|
export type WorkflowSendEmailAction = z.infer<
|
||||||
@ -59,6 +64,7 @@ export type WorkflowDeleteRecordAction = z.infer<
|
|||||||
export type WorkflowFindRecordsAction = z.infer<
|
export type WorkflowFindRecordsAction = z.infer<
|
||||||
typeof workflowFindRecordsActionSchema
|
typeof workflowFindRecordsActionSchema
|
||||||
>;
|
>;
|
||||||
|
export type WorkflowFormAction = z.infer<typeof workflowFormActionSchema>;
|
||||||
|
|
||||||
export type WorkflowAction = z.infer<typeof workflowActionSchema>;
|
export type WorkflowAction = z.infer<typeof workflowActionSchema>;
|
||||||
export type WorkflowActionType = WorkflowAction['type'];
|
export type WorkflowActionType = WorkflowAction['type'];
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
// Base schemas
|
// Base schemas
|
||||||
@ -81,6 +82,19 @@ export const workflowFindRecordsActionSettingsSchema =
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const workflowFormActionSettingsSchema =
|
||||||
|
baseWorkflowActionSettingsSchema.extend({
|
||||||
|
input: z.array(
|
||||||
|
z.object({
|
||||||
|
label: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
type: z.nativeEnum(FieldMetadataType),
|
||||||
|
placeholder: z.string().optional(),
|
||||||
|
settings: z.record(z.any()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
// Action schemas
|
// Action schemas
|
||||||
export const workflowCodeActionSchema = baseWorkflowActionSchema.extend({
|
export const workflowCodeActionSchema = baseWorkflowActionSchema.extend({
|
||||||
type: z.literal('CODE'),
|
type: z.literal('CODE'),
|
||||||
@ -118,6 +132,11 @@ export const workflowFindRecordsActionSchema = baseWorkflowActionSchema.extend({
|
|||||||
settings: workflowFindRecordsActionSettingsSchema,
|
settings: workflowFindRecordsActionSettingsSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const workflowFormActionSchema = baseWorkflowActionSchema.extend({
|
||||||
|
type: z.literal('FORM'),
|
||||||
|
settings: workflowFormActionSettingsSchema,
|
||||||
|
});
|
||||||
|
|
||||||
// Combined action schema
|
// Combined action schema
|
||||||
export const workflowActionSchema = z.discriminatedUnion('type', [
|
export const workflowActionSchema = z.discriminatedUnion('type', [
|
||||||
workflowCodeActionSchema,
|
workflowCodeActionSchema,
|
||||||
@ -126,6 +145,7 @@ export const workflowActionSchema = z.discriminatedUnion('type', [
|
|||||||
workflowUpdateRecordActionSchema,
|
workflowUpdateRecordActionSchema,
|
||||||
workflowDeleteRecordActionSchema,
|
workflowDeleteRecordActionSchema,
|
||||||
workflowFindRecordsActionSchema,
|
workflowFindRecordsActionSchema,
|
||||||
|
workflowFormActionSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Trigger schemas
|
// Trigger schemas
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { WorkflowAction, WorkflowTrigger } from '@/workflow/types/Workflow';
|
import { WorkflowAction, WorkflowTrigger } from '@/workflow/types/Workflow';
|
||||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||||
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
||||||
import { WorkflowEditActionFormCreateRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormCreateRecord';
|
import { WorkflowEditActionCreateRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord';
|
||||||
import { WorkflowEditActionFormDeleteRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormDeleteRecord';
|
import { WorkflowEditActionDeleteRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord';
|
||||||
import { WorkflowEditActionFormFindRecords } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormFindRecords';
|
import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFindRecords';
|
||||||
import { WorkflowEditActionFormSendEmail } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormSendEmail';
|
import { WorkflowEditActionForm } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionForm';
|
||||||
import { WorkflowEditActionFormUpdateRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormUpdateRecord';
|
import { WorkflowEditActionSendEmail } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionSendEmail';
|
||||||
|
import { WorkflowEditActionUpdateRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord';
|
||||||
import { WorkflowEditTriggerCronForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm';
|
import { WorkflowEditTriggerCronForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm';
|
||||||
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm';
|
import { WorkflowEditTriggerDatabaseEventForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerDatabaseEventForm';
|
||||||
import { WorkflowEditTriggerManualForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm';
|
import { WorkflowEditTriggerManualForm } from '@/workflow/workflow-trigger/components/WorkflowEditTriggerManualForm';
|
||||||
@ -13,19 +14,19 @@ 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';
|
||||||
|
|
||||||
const WorkflowEditActionFormServerlessFunction = lazy(() =>
|
const WorkflowEditActionServerlessFunction = lazy(() =>
|
||||||
import(
|
import(
|
||||||
'@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunction'
|
'@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunction'
|
||||||
).then((module) => ({
|
).then((module) => ({
|
||||||
default: module.WorkflowEditActionFormServerlessFunction,
|
default: module.WorkflowEditActionServerlessFunction,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const WorkflowReadonlyActionFormServerlessFunction = lazy(() =>
|
const WorkflowReadonlyActionServerlessFunction = lazy(() =>
|
||||||
import(
|
import(
|
||||||
'@/workflow/workflow-steps/workflow-actions/components/WorkflowReadonlyActionFormServerlessFunction'
|
'@/workflow/workflow-steps/workflow-actions/components/WorkflowReadonlyActionServerlessFunction'
|
||||||
).then((module) => ({
|
).then((module) => ({
|
||||||
default: module.WorkflowReadonlyActionFormServerlessFunction,
|
default: module.WorkflowReadonlyActionServerlessFunction,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -106,12 +107,12 @@ export const WorkflowStepDetail = ({
|
|||||||
return (
|
return (
|
||||||
<Suspense fallback={<RightDrawerSkeletonLoader />}>
|
<Suspense fallback={<RightDrawerSkeletonLoader />}>
|
||||||
{props.readonly ? (
|
{props.readonly ? (
|
||||||
<WorkflowReadonlyActionFormServerlessFunction
|
<WorkflowReadonlyActionServerlessFunction
|
||||||
key={stepId}
|
key={stepId}
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<WorkflowEditActionFormServerlessFunction
|
<WorkflowEditActionServerlessFunction
|
||||||
key={stepId}
|
key={stepId}
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
actionOptions={props}
|
actionOptions={props}
|
||||||
@ -122,7 +123,7 @@ export const WorkflowStepDetail = ({
|
|||||||
}
|
}
|
||||||
case 'SEND_EMAIL': {
|
case 'SEND_EMAIL': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditActionFormSendEmail
|
<WorkflowEditActionSendEmail
|
||||||
key={stepId}
|
key={stepId}
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
actionOptions={props}
|
actionOptions={props}
|
||||||
@ -131,7 +132,7 @@ export const WorkflowStepDetail = ({
|
|||||||
}
|
}
|
||||||
case 'CREATE_RECORD': {
|
case 'CREATE_RECORD': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditActionFormCreateRecord
|
<WorkflowEditActionCreateRecord
|
||||||
key={stepId}
|
key={stepId}
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
actionOptions={props}
|
actionOptions={props}
|
||||||
@ -141,7 +142,7 @@ export const WorkflowStepDetail = ({
|
|||||||
|
|
||||||
case 'UPDATE_RECORD': {
|
case 'UPDATE_RECORD': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditActionFormUpdateRecord
|
<WorkflowEditActionUpdateRecord
|
||||||
key={stepId}
|
key={stepId}
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
actionOptions={props}
|
actionOptions={props}
|
||||||
@ -151,7 +152,7 @@ export const WorkflowStepDetail = ({
|
|||||||
|
|
||||||
case 'DELETE_RECORD': {
|
case 'DELETE_RECORD': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditActionFormDeleteRecord
|
<WorkflowEditActionDeleteRecord
|
||||||
key={stepId}
|
key={stepId}
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
actionOptions={props}
|
actionOptions={props}
|
||||||
@ -161,7 +162,17 @@ export const WorkflowStepDetail = ({
|
|||||||
|
|
||||||
case 'FIND_RECORDS': {
|
case 'FIND_RECORDS': {
|
||||||
return (
|
return (
|
||||||
<WorkflowEditActionFormFindRecords
|
<WorkflowEditActionFindRecords
|
||||||
|
key={stepId}
|
||||||
|
action={stepDefinition.definition}
|
||||||
|
actionOptions={props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'FORM': {
|
||||||
|
return (
|
||||||
|
<WorkflowEditActionForm
|
||||||
key={stepId}
|
key={stepId}
|
||||||
action={stepDefinition.definition}
|
action={stepDefinition.definition}
|
||||||
actionOptions={props}
|
actionOptions={props}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { JsonValue } from 'type-fest';
|
|||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
type WorkflowEditActionFormCreateRecordProps = {
|
type WorkflowEditActionCreateRecordProps = {
|
||||||
action: WorkflowCreateRecordAction;
|
action: WorkflowCreateRecordAction;
|
||||||
actionOptions:
|
actionOptions:
|
||||||
| {
|
| {
|
||||||
@ -53,10 +53,10 @@ const sortByViewFieldPosition = (
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormCreateRecord = ({
|
export const WorkflowEditActionCreateRecord = ({
|
||||||
action,
|
action,
|
||||||
actionOptions,
|
actionOptions,
|
||||||
}: WorkflowEditActionFormCreateRecordProps) => {
|
}: WorkflowEditActionCreateRecordProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ import { HorizontalSeparator, useIcons } from 'twenty-ui';
|
|||||||
import { JsonValue } from 'type-fest';
|
import { JsonValue } from 'type-fest';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
type WorkflowEditActionFormDeleteRecordProps = {
|
type WorkflowEditActionDeleteRecordProps = {
|
||||||
action: WorkflowDeleteRecordAction;
|
action: WorkflowDeleteRecordAction;
|
||||||
actionOptions:
|
actionOptions:
|
||||||
| {
|
| {
|
||||||
@ -30,10 +30,10 @@ type DeleteRecordFormData = {
|
|||||||
objectRecordId: string;
|
objectRecordId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormDeleteRecord = ({
|
export const WorkflowEditActionDeleteRecord = ({
|
||||||
action,
|
action,
|
||||||
actionOptions,
|
actionOptions,
|
||||||
}: WorkflowEditActionFormDeleteRecordProps) => {
|
}: WorkflowEditActionDeleteRecordProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import { isDefined } from 'twenty-shared';
|
|||||||
import { HorizontalSeparator, useIcons } from 'twenty-ui';
|
import { HorizontalSeparator, useIcons } from 'twenty-ui';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
type WorkflowEditActionFormFindRecordsProps = {
|
type WorkflowEditActionFindRecordsProps = {
|
||||||
action: WorkflowFindRecordsAction;
|
action: WorkflowFindRecordsAction;
|
||||||
actionOptions:
|
actionOptions:
|
||||||
| {
|
| {
|
||||||
@ -29,10 +29,10 @@ type FindRecordsFormData = {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormFindRecords = ({
|
export const WorkflowEditActionFindRecords = ({
|
||||||
action,
|
action,
|
||||||
actionOptions,
|
actionOptions,
|
||||||
}: WorkflowEditActionFormFindRecordsProps) => {
|
}: WorkflowEditActionFindRecordsProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
|
||||||
|
import { FormFieldInputInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInputContainer';
|
||||||
|
import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer';
|
||||||
|
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||||
|
import { WorkflowFormAction } from '@/workflow/types/Workflow';
|
||||||
|
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||||
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
import { IconChevronDown, IconPlus, useIcons } from 'twenty-ui';
|
||||||
|
|
||||||
|
type WorkflowEditActionFormProps = {
|
||||||
|
action: WorkflowFormAction;
|
||||||
|
actionOptions:
|
||||||
|
| {
|
||||||
|
readonly: true;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
readonly?: false;
|
||||||
|
onActionUpdate: (action: WorkflowFormAction) => void;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
font-family: inherit;
|
||||||
|
padding-inline: ${({ theme }) => theme.spacing(2)};
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&[data-open='true'] {
|
||||||
|
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledPlaceholder = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledAddFieldContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: ${({ theme }) => theme.spacing(0.5)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const WorkflowEditActionForm = ({
|
||||||
|
action,
|
||||||
|
actionOptions,
|
||||||
|
}: WorkflowEditActionFormProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
const { t } = useLingui();
|
||||||
|
const headerTitle = isDefined(action.name) ? action.name : `Form`;
|
||||||
|
const headerIcon = getActionIcon(action.type);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WorkflowStepHeader
|
||||||
|
onTitleChange={(newName: string) => {
|
||||||
|
if (actionOptions.readonly === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionOptions.onActionUpdate({
|
||||||
|
...action,
|
||||||
|
name: newName,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
Icon={getIcon(headerIcon)}
|
||||||
|
iconColor={theme.font.color.tertiary}
|
||||||
|
initialTitle={headerTitle}
|
||||||
|
headerType="Action"
|
||||||
|
disabled={actionOptions.readonly}
|
||||||
|
/>
|
||||||
|
<WorkflowStepBody>
|
||||||
|
{action.settings.input.map((field) => (
|
||||||
|
<FormFieldInputContainer key={field.name}>
|
||||||
|
{field.label ? <InputLabel>{field.label}</InputLabel> : null}
|
||||||
|
|
||||||
|
<FormFieldInputRowContainer>
|
||||||
|
<FormFieldInputInputContainer hasRightElement={false}>
|
||||||
|
<StyledContainer onClick={() => {}}>
|
||||||
|
<StyledPlaceholder>{field.placeholder}</StyledPlaceholder>
|
||||||
|
<IconChevronDown
|
||||||
|
size={theme.icon.size.md}
|
||||||
|
color={theme.font.color.tertiary}
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
</FormFieldInputInputContainer>
|
||||||
|
</FormFieldInputRowContainer>
|
||||||
|
</FormFieldInputContainer>
|
||||||
|
))}
|
||||||
|
{!actionOptions.readonly && (
|
||||||
|
<FormFieldInputContainer>
|
||||||
|
<FormFieldInputRowContainer>
|
||||||
|
<FormFieldInputInputContainer hasRightElement={false}>
|
||||||
|
<StyledContainer onClick={() => {}}>
|
||||||
|
<StyledAddFieldContainer>
|
||||||
|
<IconPlus size={theme.icon.size.sm} />
|
||||||
|
{t`Add Field`}
|
||||||
|
</StyledAddFieldContainer>
|
||||||
|
</StyledContainer>
|
||||||
|
</FormFieldInputInputContainer>
|
||||||
|
</FormFieldInputRowContainer>
|
||||||
|
</FormFieldInputContainer>
|
||||||
|
)}
|
||||||
|
</WorkflowStepBody>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -27,7 +27,7 @@ import { JsonValue } from 'type-fest';
|
|||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
|
|
||||||
type WorkflowEditActionFormSendEmailProps = {
|
type WorkflowEditActionSendEmailProps = {
|
||||||
action: WorkflowSendEmailAction;
|
action: WorkflowSendEmailAction;
|
||||||
actionOptions:
|
actionOptions:
|
||||||
| {
|
| {
|
||||||
@ -46,10 +46,10 @@ type SendEmailFormData = {
|
|||||||
body: string;
|
body: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormSendEmail = ({
|
export const WorkflowEditActionSendEmail = ({
|
||||||
action,
|
action,
|
||||||
actionOptions,
|
actionOptions,
|
||||||
}: WorkflowEditActionFormSendEmailProps) => {
|
}: WorkflowEditActionSendEmailProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
@ -22,7 +22,7 @@ import { TabList } from '@/ui/layout/tab/components/TabList';
|
|||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||||
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
|
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
|
||||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||||
import { WorkflowEditActionFormServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunctionFields';
|
import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunctionFields';
|
||||||
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId';
|
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId';
|
||||||
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||||
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||||
@ -54,7 +54,7 @@ const StyledTabList = styled(TabList)`
|
|||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type WorkflowEditActionFormServerlessFunctionProps = {
|
type WorkflowEditActionServerlessFunctionProps = {
|
||||||
action: WorkflowCodeAction;
|
action: WorkflowCodeAction;
|
||||||
actionOptions:
|
actionOptions:
|
||||||
| {
|
| {
|
||||||
@ -70,10 +70,10 @@ type ServerlessFunctionInputFormData = {
|
|||||||
[field: string]: string | ServerlessFunctionInputFormData;
|
[field: string]: string | ServerlessFunctionInputFormData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormServerlessFunction = ({
|
export const WorkflowEditActionServerlessFunction = ({
|
||||||
action,
|
action,
|
||||||
actionOptions,
|
actionOptions,
|
||||||
}: WorkflowEditActionFormServerlessFunctionProps) => {
|
}: WorkflowEditActionServerlessFunctionProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
||||||
@ -303,7 +303,7 @@ export const WorkflowEditActionFormServerlessFunction = ({
|
|||||||
<WorkflowStepBody>
|
<WorkflowStepBody>
|
||||||
{activeTabId === 'code' && (
|
{activeTabId === 'code' && (
|
||||||
<>
|
<>
|
||||||
<WorkflowEditActionFormServerlessFunctionFields
|
<WorkflowEditActionServerlessFunctionFields
|
||||||
functionInput={functionInput}
|
functionInput={functionInput}
|
||||||
VariablePicker={WorkflowVariablePicker}
|
VariablePicker={WorkflowVariablePicker}
|
||||||
onInputChange={handleInputChange}
|
onInputChange={handleInputChange}
|
||||||
@ -327,7 +327,7 @@ export const WorkflowEditActionFormServerlessFunction = ({
|
|||||||
)}
|
)}
|
||||||
{activeTabId === 'test' && (
|
{activeTabId === 'test' && (
|
||||||
<>
|
<>
|
||||||
<WorkflowEditActionFormServerlessFunctionFields
|
<WorkflowEditActionServerlessFunctionFields
|
||||||
functionInput={serverlessFunctionTestData.input}
|
functionInput={serverlessFunctionTestData.input}
|
||||||
onInputChange={handleTestInputChange}
|
onInputChange={handleTestInputChange}
|
||||||
readonly={actionOptions.readonly}
|
readonly={actionOptions.readonly}
|
||||||
@ -11,7 +11,7 @@ const StyledContainer = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type WorkflowEditActionFormServerlessFunctionFieldsProps = {
|
type WorkflowEditActionServerlessFunctionFieldsProps = {
|
||||||
functionInput: FunctionInput;
|
functionInput: FunctionInput;
|
||||||
path?: string[];
|
path?: string[];
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
@ -19,13 +19,13 @@ type WorkflowEditActionFormServerlessFunctionFieldsProps = {
|
|||||||
VariablePicker?: VariablePickerComponent;
|
VariablePicker?: VariablePickerComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowEditActionFormServerlessFunctionFields = ({
|
export const WorkflowEditActionServerlessFunctionFields = ({
|
||||||
functionInput,
|
functionInput,
|
||||||
path = [],
|
path = [],
|
||||||
readonly,
|
readonly,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
VariablePicker,
|
VariablePicker,
|
||||||
}: WorkflowEditActionFormServerlessFunctionFieldsProps) => {
|
}: WorkflowEditActionServerlessFunctionFieldsProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Object.entries(functionInput).map(([inputKey, inputValue]) => {
|
{Object.entries(functionInput).map(([inputKey, inputValue]) => {
|
||||||
@ -37,7 +37,7 @@ export const WorkflowEditActionFormServerlessFunctionFields = ({
|
|||||||
<StyledContainer key={pathKey}>
|
<StyledContainer key={pathKey}>
|
||||||
<InputLabel>{inputKey}</InputLabel>
|
<InputLabel>{inputKey}</InputLabel>
|
||||||
<FormNestedFieldInputContainer>
|
<FormNestedFieldInputContainer>
|
||||||
<WorkflowEditActionFormServerlessFunctionFields
|
<WorkflowEditActionServerlessFunctionFields
|
||||||
functionInput={inputValue}
|
functionInput={inputValue}
|
||||||
path={currentPath}
|
path={currentPath}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
@ -18,7 +18,7 @@ import { JsonValue } from 'type-fest';
|
|||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
type WorkflowEditActionFormUpdateRecordProps = {
|
type WorkflowEditActionUpdateRecordProps = {
|
||||||
action: WorkflowUpdateRecordAction;
|
action: WorkflowUpdateRecordAction;
|
||||||
actionOptions:
|
actionOptions:
|
||||||
| {
|
| {
|
||||||
@ -55,10 +55,10 @@ const AVAILABLE_FIELD_METADATA_TYPES = [
|
|||||||
FieldMetadataType.UUID,
|
FieldMetadataType.UUID,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const WorkflowEditActionFormUpdateRecord = ({
|
export const WorkflowEditActionUpdateRecord = ({
|
||||||
action,
|
action,
|
||||||
actionOptions,
|
actionOptions,
|
||||||
}: WorkflowEditActionFormUpdateRecordProps) => {
|
}: WorkflowEditActionUpdateRecordProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
@ -5,7 +5,7 @@ import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/Workflo
|
|||||||
|
|
||||||
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
|
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
|
||||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||||
import { WorkflowEditActionFormServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormServerlessFunctionFields';
|
import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunctionFields';
|
||||||
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||||
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
@ -27,13 +27,13 @@ const StyledCodeEditorContainer = styled.div`
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type WorkflowReadonlyActionFormServerlessFunctionProps = {
|
type WorkflowReadonlyActionServerlessFunctionProps = {
|
||||||
action: WorkflowCodeAction;
|
action: WorkflowCodeAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkflowReadonlyActionFormServerlessFunction = ({
|
export const WorkflowReadonlyActionServerlessFunction = ({
|
||||||
action,
|
action,
|
||||||
}: WorkflowReadonlyActionFormServerlessFunctionProps) => {
|
}: WorkflowReadonlyActionServerlessFunctionProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
||||||
@ -81,7 +81,7 @@ export const WorkflowReadonlyActionFormServerlessFunction = ({
|
|||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<WorkflowStepBody>
|
<WorkflowStepBody>
|
||||||
<WorkflowEditActionFormServerlessFunctionFields
|
<WorkflowEditActionServerlessFunctionFields
|
||||||
functionInput={action.settings.input.serverlessFunctionInput}
|
functionInput={action.settings.input.serverlessFunctionInput}
|
||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
@ -10,11 +10,11 @@ import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorato
|
|||||||
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow';
|
import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow';
|
||||||
import { WorkflowEditActionFormCreateRecord } from '../WorkflowEditActionFormCreateRecord';
|
import { WorkflowEditActionCreateRecord } from '../WorkflowEditActionCreateRecord';
|
||||||
|
|
||||||
const meta: Meta<typeof WorkflowEditActionFormCreateRecord> = {
|
const meta: Meta<typeof WorkflowEditActionCreateRecord> = {
|
||||||
title: 'Modules/Workflow/WorkflowEditActionFormCreateRecord',
|
title: 'Modules/Workflow/WorkflowEditActionCreateRecord',
|
||||||
component: WorkflowEditActionFormCreateRecord,
|
component: WorkflowEditActionCreateRecord,
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
@ -54,7 +54,7 @@ const meta: Meta<typeof WorkflowEditActionFormCreateRecord> = {
|
|||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof WorkflowEditActionFormCreateRecord>;
|
type Story = StoryObj<typeof WorkflowEditActionCreateRecord>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
@ -11,7 +11,7 @@ import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
|||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { allMockPersonRecords } from '~/testing/mock-data/people';
|
import { allMockPersonRecords } from '~/testing/mock-data/people';
|
||||||
import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow';
|
import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow';
|
||||||
import { WorkflowEditActionFormDeleteRecord } from '../WorkflowEditActionFormDeleteRecord';
|
import { WorkflowEditActionDeleteRecord } from '../WorkflowEditActionDeleteRecord';
|
||||||
|
|
||||||
const DEFAULT_ACTION = {
|
const DEFAULT_ACTION = {
|
||||||
id: getWorkflowNodeIdMock(),
|
id: getWorkflowNodeIdMock(),
|
||||||
@ -35,9 +35,9 @@ const DEFAULT_ACTION = {
|
|||||||
},
|
},
|
||||||
} satisfies WorkflowDeleteRecordAction;
|
} satisfies WorkflowDeleteRecordAction;
|
||||||
|
|
||||||
const meta: Meta<typeof WorkflowEditActionFormDeleteRecord> = {
|
const meta: Meta<typeof WorkflowEditActionDeleteRecord> = {
|
||||||
title: 'Modules/Workflow/WorkflowEditActionFormDeleteRecord',
|
title: 'Modules/Workflow/WorkflowEditActionDeleteRecord',
|
||||||
component: WorkflowEditActionFormDeleteRecord,
|
component: WorkflowEditActionDeleteRecord,
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
@ -58,7 +58,7 @@ const meta: Meta<typeof WorkflowEditActionFormDeleteRecord> = {
|
|||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof WorkflowEditActionFormDeleteRecord>;
|
type Story = StoryObj<typeof WorkflowEditActionDeleteRecord>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { WorkflowFindRecordsAction } from '@/workflow/types/Workflow';
|
import { WorkflowFindRecordsAction } from '@/workflow/types/Workflow';
|
||||||
import { WorkflowEditActionFormFindRecords } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFormFindRecords';
|
import { WorkflowEditActionFindRecords } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionFindRecords';
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { expect, fn, userEvent, within } from '@storybook/test';
|
import { expect, fn, userEvent, within } from '@storybook/test';
|
||||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||||
@ -33,9 +33,9 @@ const DEFAULT_ACTION = {
|
|||||||
},
|
},
|
||||||
} satisfies WorkflowFindRecordsAction;
|
} satisfies WorkflowFindRecordsAction;
|
||||||
|
|
||||||
const meta: Meta<typeof WorkflowEditActionFormFindRecords> = {
|
const meta: Meta<typeof WorkflowEditActionFindRecords> = {
|
||||||
title: 'Modules/Workflow/WorkflowEditActionFormFindRecords',
|
title: 'Modules/Workflow/WorkflowEditActionFindRecords',
|
||||||
component: WorkflowEditActionFormFindRecords,
|
component: WorkflowEditActionFindRecords,
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
@ -55,7 +55,7 @@ const meta: Meta<typeof WorkflowEditActionFormFindRecords> = {
|
|||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof WorkflowEditActionFormFindRecords>;
|
type Story = StoryObj<typeof WorkflowEditActionFindRecords>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
import { WorkflowFormAction } from '@/workflow/types/Workflow';
|
||||||
|
import { WorkflowEditActionForm } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionForm';
|
||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { expect, fn, within } from '@storybook/test';
|
||||||
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
|
import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator';
|
||||||
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow';
|
||||||
|
|
||||||
|
const DEFAULT_ACTION = {
|
||||||
|
id: getWorkflowNodeIdMock(),
|
||||||
|
name: 'Form',
|
||||||
|
type: 'FORM',
|
||||||
|
valid: false,
|
||||||
|
settings: {
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
name: 'company',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
label: 'Company',
|
||||||
|
placeholder: 'Select a company',
|
||||||
|
settings: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'number',
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
label: 'Number',
|
||||||
|
placeholder: '1000',
|
||||||
|
settings: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputSchema: {},
|
||||||
|
errorHandlingOptions: {
|
||||||
|
retryOnFailure: {
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
continueOnFailure: {
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies WorkflowFormAction;
|
||||||
|
|
||||||
|
const meta: Meta<typeof WorkflowEditActionForm> = {
|
||||||
|
title: 'Modules/Workflow/WorkflowEditActionForm',
|
||||||
|
component: WorkflowEditActionForm,
|
||||||
|
parameters: {
|
||||||
|
msw: graphqlMocks,
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
action: DEFAULT_ACTION,
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
WorkflowStepActionDrawerDecorator,
|
||||||
|
ComponentDecorator,
|
||||||
|
RouterDecorator,
|
||||||
|
I18nFrontDecorator,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof WorkflowEditActionForm>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
actionOptions: {
|
||||||
|
onActionUpdate: fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
await canvas.findByText('Company');
|
||||||
|
await canvas.findByText('Add Field');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DisabledWithEmptyValues: Story = {
|
||||||
|
args: {
|
||||||
|
actionOptions: {
|
||||||
|
readonly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
const titleInput = await canvas.findByDisplayValue('Form');
|
||||||
|
|
||||||
|
expect(titleInput).toBeDisabled();
|
||||||
|
|
||||||
|
await canvas.findByText('Company');
|
||||||
|
|
||||||
|
const addFieldButton = canvas.queryByText('Add Field');
|
||||||
|
expect(addFieldButton).not.toBeInTheDocument();
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -11,7 +11,7 @@ import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
|||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { allMockPersonRecords } from '~/testing/mock-data/people';
|
import { allMockPersonRecords } from '~/testing/mock-data/people';
|
||||||
import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow';
|
import { getWorkflowNodeIdMock } from '~/testing/mock-data/workflow';
|
||||||
import { WorkflowEditActionFormUpdateRecord } from '../WorkflowEditActionFormUpdateRecord';
|
import { WorkflowEditActionUpdateRecord } from '../WorkflowEditActionUpdateRecord';
|
||||||
|
|
||||||
const DEFAULT_ACTION = {
|
const DEFAULT_ACTION = {
|
||||||
id: getWorkflowNodeIdMock(),
|
id: getWorkflowNodeIdMock(),
|
||||||
@ -48,9 +48,9 @@ const DEFAULT_ACTION = {
|
|||||||
valid: false,
|
valid: false,
|
||||||
} satisfies WorkflowUpdateRecordAction;
|
} satisfies WorkflowUpdateRecordAction;
|
||||||
|
|
||||||
const meta: Meta<typeof WorkflowEditActionFormUpdateRecord> = {
|
const meta: Meta<typeof WorkflowEditActionUpdateRecord> = {
|
||||||
title: 'Modules/Workflow/WorkflowEditActionFormUpdateRecord',
|
title: 'Modules/Workflow/WorkflowEditActionUpdateRecord',
|
||||||
component: WorkflowEditActionFormUpdateRecord,
|
component: WorkflowEditActionUpdateRecord,
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
@ -71,7 +71,7 @@ const meta: Meta<typeof WorkflowEditActionFormUpdateRecord> = {
|
|||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
type Story = StoryObj<typeof WorkflowEditActionFormUpdateRecord>;
|
type Story = StoryObj<typeof WorkflowEditActionUpdateRecord>;
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
@ -15,4 +15,9 @@ export const OTHER_ACTIONS: Array<{
|
|||||||
type: 'CODE',
|
type: 'CODE',
|
||||||
icon: 'IconCode',
|
icon: 'IconCode',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Form',
|
||||||
|
type: 'FORM',
|
||||||
|
icon: 'IconForms',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -20,9 +20,10 @@ export const RECORD_ACTIONS: Array<{
|
|||||||
type: 'DELETE_RECORD',
|
type: 'DELETE_RECORD',
|
||||||
icon: 'IconTrash',
|
icon: 'IconTrash',
|
||||||
},
|
},
|
||||||
{
|
// TODO: Add search records action
|
||||||
label: 'Search Records',
|
// {
|
||||||
type: 'FIND_RECORDS',
|
// label: 'Search Records',
|
||||||
icon: 'IconSearch',
|
// type: 'FIND_RECORDS',
|
||||||
},
|
// icon: 'IconSearch',
|
||||||
|
// },
|
||||||
];
|
];
|
||||||
|
|||||||
@ -70,6 +70,11 @@ export const seedFeatureFlags = async (
|
|||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
value: false,
|
value: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: FeatureFlagKey.IsWorkflowFormActionEnabled,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
.execute();
|
.execute();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,4 +13,5 @@ export enum FeatureFlagKey {
|
|||||||
IsApprovedAccessDomainsEnabled = 'IS_APPROVED_ACCESS_DOMAINS_ENABLED',
|
IsApprovedAccessDomainsEnabled = 'IS_APPROVED_ACCESS_DOMAINS_ENABLED',
|
||||||
IsNewRelationEnabled = 'IS_NEW_RELATION_ENABLED',
|
IsNewRelationEnabled = 'IS_NEW_RELATION_ENABLED',
|
||||||
IsPermissionsEnabled = 'IS_PERMISSIONS_ENABLED',
|
IsPermissionsEnabled = 'IS_PERMISSIONS_ENABLED',
|
||||||
|
IsWorkflowFormActionEnabled = 'IS_WORKFLOW_FORM_ACTION_ENABLED',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared';
|
import { FieldMetadataType, isDefined } from 'twenty-shared';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
@ -495,6 +495,31 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case WorkflowActionType.FORM: {
|
||||||
|
return {
|
||||||
|
id: newStepId,
|
||||||
|
name: 'Form',
|
||||||
|
type: WorkflowActionType.FORM,
|
||||||
|
valid: false,
|
||||||
|
settings: {
|
||||||
|
...BASE_STEP_DEFINITION,
|
||||||
|
input: [
|
||||||
|
{
|
||||||
|
label: 'Company',
|
||||||
|
name: 'company',
|
||||||
|
placeholder: 'Select a company',
|
||||||
|
type: FieldMetadataType.TEXT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Number',
|
||||||
|
name: 'number',
|
||||||
|
placeholder: '1000',
|
||||||
|
type: FieldMetadataType.NUMBER,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new WorkflowVersionStepException(
|
throw new WorkflowVersionStepException(
|
||||||
`WorkflowActionType '${type}' unknown`,
|
`WorkflowActionType '${type}' unknown`,
|
||||||
|
|||||||
Reference in New Issue
Block a user