Webhook follow up (#10124)

- fix webhook creation
- fix pointer
This commit is contained in:
martmull
2025-02-11 17:30:11 +01:00
committed by GitHub
parent 5252b22b64
commit 83bf2d1739
13 changed files with 184 additions and 113 deletions

View File

@ -26,7 +26,7 @@ type PartialObjectRecordWithId = Partial<ObjectRecord> & {
type useCreateManyRecordsProps = { type useCreateManyRecordsProps = {
objectNameSingular: string; objectNameSingular: string;
recordGqlFields?: RecordGqlOperationGqlRecordFields; recordGqlFields?: RecordGqlOperationGqlRecordFields;
skipPostOptmisticEffect?: boolean; skipPostOptimisticEffect?: boolean;
shouldMatchRootQueryFilter?: boolean; shouldMatchRootQueryFilter?: boolean;
}; };
@ -35,7 +35,7 @@ export const useCreateManyRecords = <
>({ >({
objectNameSingular, objectNameSingular,
recordGqlFields, recordGqlFields,
skipPostOptmisticEffect = false, skipPostOptimisticEffect = false,
shouldMatchRootQueryFilter, shouldMatchRootQueryFilter,
}: useCreateManyRecordsProps) => { }: useCreateManyRecordsProps) => {
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
@ -135,7 +135,7 @@ export const useCreateManyRecords = <
update: (cache, { data }) => { update: (cache, { data }) => {
const records = data?.[mutationResponseField]; const records = data?.[mutationResponseField];
if (!isDefined(records?.length) || skipPostOptmisticEffect) return; if (!isDefined(records?.length) || skipPostOptimisticEffect) return;
triggerCreateRecordsOptimisticEffect({ triggerCreateRecordsOptimisticEffect({
cache, cache,

View File

@ -23,7 +23,7 @@ import { isDefined } from 'twenty-shared';
type useCreateOneRecordProps = { type useCreateOneRecordProps = {
objectNameSingular: string; objectNameSingular: string;
recordGqlFields?: RecordGqlOperationGqlRecordFields; recordGqlFields?: RecordGqlOperationGqlRecordFields;
skipPostOptmisticEffect?: boolean; skipPostOptimisticEffect?: boolean;
shouldMatchRootQueryFilter?: boolean; shouldMatchRootQueryFilter?: boolean;
}; };
@ -32,7 +32,7 @@ export const useCreateOneRecord = <
>({ >({
objectNameSingular, objectNameSingular,
recordGqlFields, recordGqlFields,
skipPostOptmisticEffect = false, skipPostOptimisticEffect = false,
shouldMatchRootQueryFilter, shouldMatchRootQueryFilter,
}: useCreateOneRecordProps) => { }: useCreateOneRecordProps) => {
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
@ -95,7 +95,7 @@ export const useCreateOneRecord = <
computeReferences: false, computeReferences: false,
}); });
if (optimisticRecordNode !== null) { if (skipPostOptimisticEffect === false && optimisticRecordNode !== null) {
triggerCreateRecordsOptimisticEffect({ triggerCreateRecordsOptimisticEffect({
cache: apolloClient.cache, cache: apolloClient.cache,
objectMetadataItem, objectMetadataItem,
@ -117,7 +117,7 @@ export const useCreateOneRecord = <
}, },
update: (cache, { data }) => { update: (cache, { data }) => {
const record = data?.[mutationResponseField]; const record = data?.[mutationResponseField];
if (skipPostOptmisticEffect === false && isDefined(record)) { if (skipPostOptimisticEffect === false && isDefined(record)) {
triggerCreateRecordsOptimisticEffect({ triggerCreateRecordsOptimisticEffect({
cache, cache,
objectMetadataItem, objectMetadataItem,

View File

@ -139,7 +139,7 @@ export const FormNumberFieldInput = ({
</FormFieldInputRowContainer> </FormFieldInputRowContainer>
{hint ? <InputHint>{hint}</InputHint> : null} {hint ? <InputHint>{hint}</InputHint> : null}
{error && <InputErrorHelper>{error}</InputErrorHelper>} <InputErrorHelper>{error}</InputErrorHelper>
</FormFieldInputContainer> </FormFieldInputContainer>
); );
}; };

View File

@ -91,7 +91,7 @@ export const FormTextFieldInput = ({
) : null} ) : null}
</FormFieldInputRowContainer> </FormFieldInputRowContainer>
{hint && <InputHint>{hint}</InputHint>} {hint && <InputHint>{hint}</InputHint>}
{error && <InputErrorHelper>{error}</InputErrorHelper>} <InputErrorHelper>{error}</InputErrorHelper>
</FormFieldInputContainer> </FormFieldInputContainer>
); );
}; };

View File

@ -5,6 +5,7 @@ import { IconChevronRight } from 'twenty-ui';
import { Webhook } from '@/settings/developers/types/webhook/Webhook'; import { Webhook } from '@/settings/developers/types/webhook/Webhook';
import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow'; import { TableRow } from '@/ui/layout/table/components/TableRow';
import { getUrlHostname } from '~/utils/url/getUrlHostname';
export const StyledApisFieldTableRow = styled(TableRow)` export const StyledApisFieldTableRow = styled(TableRow)`
grid-template-columns: 1fr 28px; grid-template-columns: 1fr 28px;
@ -37,7 +38,9 @@ export const SettingsDevelopersWebhookTableRow = ({
return ( return (
<StyledApisFieldTableRow to={to}> <StyledApisFieldTableRow to={to}>
<StyledUrlTableCell>{fieldItem.targetUrl}</StyledUrlTableCell> <StyledUrlTableCell>
{getUrlHostname(fieldItem.targetUrl, { keepPath: true })}
</StyledUrlTableCell>
<StyledIconTableCell> <StyledIconTableCell>
<StyledIconChevronRight <StyledIconChevronRight
size={theme.icon.size.md} size={theme.icon.size.md}

View File

@ -2,6 +2,7 @@ import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useState } from 'react'; import { useState } from 'react';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { Webhook } from '@/settings/developers/types/webhook/Webhook'; import { Webhook } from '@/settings/developers/types/webhook/Webhook';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType'; import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType';
@ -11,6 +12,7 @@ import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { isValidUrl } from '~/utils/url/isValidUrl'; import { isValidUrl } from '~/utils/url/isValidUrl';
import { getUrlHostname } from '~/utils/url/getUrlHostname';
type WebhookFormData = { type WebhookFormData = {
targetUrl: string; targetUrl: string;
@ -19,10 +21,18 @@ type WebhookFormData = {
secret?: string; secret?: string;
}; };
export const useWebhookUpdateForm = ({ webhookId }: { webhookId: string }) => { export const useWebhookUpdateForm = ({
webhookId,
isCreationMode,
}: {
webhookId: string;
isCreationMode: boolean;
}) => {
const navigate = useNavigateSettings(); const navigate = useNavigateSettings();
const [loading, setLoading] = useState(true); const [isCreated, setIsCreated] = useState(!isCreationMode);
const [loading, setLoading] = useState(!isCreationMode);
const [title, setTitle] = useState(isCreationMode ? 'New Webhook' : '');
const [formData, setFormData] = useState<WebhookFormData>({ const [formData, setFormData] = useState<WebhookFormData>({
targetUrl: '', targetUrl: '',
@ -37,6 +47,10 @@ export const useWebhookUpdateForm = ({ webhookId }: { webhookId: string }) => {
objectNameSingular: CoreObjectNameSingular.Webhook, objectNameSingular: CoreObjectNameSingular.Webhook,
}); });
const { createOneRecord } = useCreateOneRecord<Webhook>({
objectNameSingular: CoreObjectNameSingular.Webhook,
});
const addEmptyOperationIfNecessary = ( const addEmptyOperationIfNecessary = (
newOperations: WebhookOperationType[], newOperations: WebhookOperationType[],
) => { ) => {
@ -49,34 +63,6 @@ export const useWebhookUpdateForm = ({ webhookId }: { webhookId: string }) => {
return newOperations; return newOperations;
}; };
useFindOneRecord({
objectNameSingular: CoreObjectNameSingular.Webhook,
objectRecordId: webhookId,
onCompleted: (data) => {
const baseOperations = data?.operations
? data.operations.map((op: string) => {
const [object, action] = op.split('.');
return { object, action };
})
: data?.operation
? [
{
object: data.operation.split('.')[0],
action: data.operation.split('.')[1],
},
]
: [];
const operations = addEmptyOperationIfNecessary(baseOperations);
setFormData({
targetUrl: data.targetUrl,
description: data.description,
operations,
secret: data.secret,
});
setLoading(false);
},
});
const cleanAndFormatOperations = (operations: WebhookOperationType[]) => { const cleanAndFormatOperations = (operations: WebhookOperationType[]) => {
return Array.from( return Array.from(
new Set( new Set(
@ -90,6 +76,19 @@ export const useWebhookUpdateForm = ({ webhookId }: { webhookId: string }) => {
const handleSave = useDebouncedCallback(async () => { const handleSave = useDebouncedCallback(async () => {
const cleanedOperations = cleanAndFormatOperations(formData.operations); const cleanedOperations = cleanAndFormatOperations(formData.operations);
const webhookData = {
...(isTargetUrlValid && { targetUrl: formData.targetUrl.trim() }),
operations: cleanedOperations,
description: formData.description,
secret: formData.secret,
};
if (!isCreated) {
await createOneRecord({ id: webhookId, ...webhookData });
setIsCreated(true);
return;
}
await updateOneRecord({ await updateOneRecord({
idToUpdate: webhookId, idToUpdate: webhookId,
updateOneRecordInput: { updateOneRecordInput: {
@ -104,7 +103,13 @@ export const useWebhookUpdateForm = ({ webhookId }: { webhookId: string }) => {
const validateData = (data: Partial<WebhookFormData>) => { const validateData = (data: Partial<WebhookFormData>) => {
if (isDefined(data?.targetUrl)) { if (isDefined(data?.targetUrl)) {
const trimmedUrl = data.targetUrl.trim(); const trimmedUrl = data.targetUrl.trim();
setIsTargetUrlValid(isValidUrl(trimmedUrl)); const isTargetUrlValid = isValidUrl(trimmedUrl);
setIsTargetUrlValid(isTargetUrlValid);
if (isTargetUrlValid) {
setTitle(
getUrlHostname(trimmedUrl, { keepPath: true }) || 'New Webhook',
);
}
} }
}; };
@ -147,8 +152,41 @@ export const useWebhookUpdateForm = ({ webhookId }: { webhookId: string }) => {
navigate(SettingsPath.Developers); navigate(SettingsPath.Developers);
}; };
useFindOneRecord({
skip: isCreationMode,
objectNameSingular: CoreObjectNameSingular.Webhook,
objectRecordId: webhookId,
onCompleted: (data) => {
const baseOperations = data?.operations
? data.operations.map((op: string) => {
const [object, action] = op.split('.');
return { object, action };
})
: data?.operation
? [
{
object: data.operation.split('.')[0],
action: data.operation.split('.')[1],
},
]
: [];
const operations = addEmptyOperationIfNecessary(baseOperations);
setFormData({
targetUrl: data.targetUrl,
description: data.description,
operations,
secret: data.secret,
});
setTitle(
getUrlHostname(data.targetUrl, { keepPath: true }) || 'New Webhook',
);
setLoading(false);
},
});
return { return {
formData, formData,
title,
isTargetUrlValid, isTargetUrlValid,
updateWebhook, updateWebhook,
updateOperation, updateOperation,

View File

@ -4,12 +4,25 @@ import styled from '@emotion/styled';
const StyledInputErrorHelper = styled.div` const StyledInputErrorHelper = styled.div`
color: ${({ theme }) => theme.color.red}; color: ${({ theme }) => theme.color.red};
font-size: ${({ theme }) => theme.font.size.xs}; font-size: ${({ theme }) => theme.font.size.xs};
position: absolute;
`;
const StyledErrorContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(1)};
`; `;
export const InputErrorHelper = ({ export const InputErrorHelper = ({
children, children,
isVisible = true,
}: { }: {
children: React.ReactNode; children?: React.ReactNode;
isVisible?: boolean;
}) => ( }) => (
<StyledInputErrorHelper aria-live="polite">{children}</StyledInputErrorHelper> <StyledErrorContainer>
{children && isVisible && (
<StyledInputErrorHelper aria-live="polite">
{children}
</StyledInputErrorHelper>
)}
</StyledErrorContainer>
); );

View File

@ -76,18 +76,14 @@ const StyledInput = styled.input<
} }
&:focus { &:focus {
${({ theme }) => { ${({ theme, error }) => {
return ` return `
border-color: ${theme.color.blue}; border-color: ${error ? theme.border.color.danger : theme.color.blue};
`; `;
}}; }};
} }
`; `;
const StyledErrorHelper = styled(InputErrorHelper)`
padding: ${({ theme }) => theme.spacing(1)};
`;
const StyledLeftIconContainer = styled.div<{ sizeVariant: TextInputV2Size }>` const StyledLeftIconContainer = styled.div<{ sizeVariant: TextInputV2Size }>`
align-items: center; align-items: center;
display: flex; display: flex;
@ -270,9 +266,7 @@ const TextInputV2Component = (
)} )}
</StyledTrailingIconContainer> </StyledTrailingIconContainer>
</StyledInputContainer> </StyledInputContainer>
{error && !noErrorHelper && ( <InputErrorHelper isVisible={!noErrorHelper}>{error}</InputErrorHelper>
<StyledErrorHelper>{error}</StyledErrorHelper>
)}
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -111,7 +111,7 @@ export const WorkflowEditTriggerCronForm = ({
placeholder="0 */1 * * *" placeholder="0 */1 * * *"
error={errorMessagesVisible ? errorMessages.CUSTOM : undefined} error={errorMessagesVisible ? errorMessages.CUSTOM : undefined}
onBlur={onBlur} onBlur={onBlur}
hint="Format: [Second] [Minute] [Hour] [Day of Month] [Month] [Day of Week]" hint="Format: [Minute] [Hour] [Day of Month] [Month] [Day of Week]"
readonly={triggerOptions.readonly} readonly={triggerOptions.readonly}
defaultValue={trigger.settings.pattern} defaultValue={trigger.settings.pattern}
onPersist={(newPattern: string) => { onPersist={(newPattern: string) => {

View File

@ -1,3 +1,4 @@
import { v4 } from 'uuid';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable'; import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable';
import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton'; import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton';
@ -9,10 +10,6 @@ import styled from '@emotion/styled';
import { Trans, useLingui } from '@lingui/react/macro'; import { Trans, useLingui } from '@lingui/react/macro';
import { Button, H2Title, IconPlus, MOBILE_VIEWPORT, Section } from 'twenty-ui'; import { Button, H2Title, IconPlus, MOBILE_VIEWPORT, Section } from 'twenty-ui';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { Webhook } from '@/settings/developers/types/webhook/Webhook';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
const StyledButtonContainer = styled.div` const StyledButtonContainer = styled.div`
display: flex; display: flex;
@ -32,24 +29,7 @@ const StyledContainer = styled.div<{ isMobile: boolean }>`
export const SettingsDevelopers = () => { export const SettingsDevelopers = () => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const navigate = useNavigateSettings();
const { t } = useLingui(); const { t } = useLingui();
const { createOneRecord: createOneWebhook } = useCreateOneRecord<Webhook>({
objectNameSingular: CoreObjectNameSingular.Webhook,
});
const createNewWebhook = async () => {
const newWebhook = await createOneWebhook({
targetUrl: '',
operations: ['*.*'],
});
if (!newWebhook) {
return;
}
navigate(SettingsPath.DevelopersNewWebhookDetail, {
webhookId: newWebhook.id,
});
};
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
@ -93,7 +73,15 @@ export const SettingsDevelopers = () => {
title={t`Create Webhook`} title={t`Create Webhook`}
size="small" size="small"
variant="secondary" variant="secondary"
onClick={createNewWebhook} to={getSettingsPath(
SettingsPath.DevelopersNewWebhookDetail,
{
webhookId: v4(),
},
{
creationMode: true,
},
)}
/> />
</StyledButtonContainer> </StyledButtonContainer>
</Section> </Section>

View File

@ -1,7 +1,7 @@
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams, useSearchParams } from 'react-router-dom';
import { import {
Button, Button,
H2Title, H2Title,
@ -33,6 +33,7 @@ import { useRecoilValue } from 'recoil';
import { FeatureFlagKey } from '~/generated/graphql'; import { FeatureFlagKey } from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { useWebhookUpdateForm } from '@/settings/developers/hooks/useWebhookUpdateForm'; import { useWebhookUpdateForm } from '@/settings/developers/hooks/useWebhookUpdateForm';
import { isDefined } from 'twenty-shared';
const OBJECT_DROPDOWN_WIDTH = 340; const OBJECT_DROPDOWN_WIDTH = 340;
const ACTION_DROPDOWN_WIDTH = 140; const ACTION_DROPDOWN_WIDTH = 140;
@ -68,8 +69,13 @@ export const SettingsDevelopersWebhooksDetail = () => {
const { webhookId = '' } = useParams(); const { webhookId = '' } = useParams();
const [searchParams] = useSearchParams();
const isCreationMode = isDefined(searchParams.get('creationMode'));
const { const {
formData, formData,
title,
loading, loading,
isTargetUrlValid, isTargetUrlValid,
updateWebhook, updateWebhook,
@ -78,6 +84,7 @@ export const SettingsDevelopersWebhooksDetail = () => {
deleteWebhook, deleteWebhook,
} = useWebhookUpdateForm({ } = useWebhookUpdateForm({
webhookId, webhookId,
isCreationMode,
}); });
const [isDeleteWebhookModalOpen, setIsDeleteWebhookModalOpen] = const [isDeleteWebhookModalOpen, setIsDeleteWebhookModalOpen] =
@ -114,7 +121,7 @@ export const SettingsDevelopersWebhooksDetail = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
title={formData.targetUrl} title={title}
reserveTitleSpace reserveTitleSpace
links={[ links={[
{ {
@ -220,7 +227,7 @@ export const SettingsDevelopersWebhooksDetail = () => {
fullWidth fullWidth
/> />
</Section> </Section>
{isAnalyticsEnabled && isAnalyticsV2Enabled && ( {!isCreationMode && isAnalyticsEnabled && isAnalyticsV2Enabled && (
<AnalyticsGraphDataInstanceContext.Provider <AnalyticsGraphDataInstanceContext.Provider
value={{ instanceId: `webhook-${webhookId}-analytics` }} value={{ instanceId: `webhook-${webhookId}-analytics` }}
> >

View File

@ -1,9 +1,12 @@
import { getAbsoluteUrl } from '~/utils/url/getAbsoluteUrl'; import { getAbsoluteUrl } from '~/utils/url/getAbsoluteUrl';
export const getUrlHostname = (url: string) => { export const getUrlHostname = (
url: string,
options?: { keepPath?: boolean },
) => {
try { try {
const absoluteUrl = getAbsoluteUrl(url); const parsedUrl = new URL(getAbsoluteUrl(url));
return new URL(absoluteUrl).hostname.replace(/^www\./i, ''); return `${parsedUrl.hostname.replace(/^www\./i, '')}${options?.keepPath && parsedUrl.pathname !== '/' ? parsedUrl.pathname : ''}`;
} catch { } catch {
return ''; return '';
} }

View File

@ -88,40 +88,65 @@ function assertCronTriggerSettingsAreValid(settings: any) {
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
); );
} }
if (settings.type === 'CUSTOM' && !settings.pattern) { switch (settings.type) {
throw new WorkflowTriggerException( case 'CUSTOM': {
'No pattern provided in CUSTOM cron trigger', if (!settings.pattern) {
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, throw new WorkflowTriggerException(
); 'No pattern provided in CUSTOM cron trigger',
} WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
if (!settings.schedule) { );
throw new WorkflowTriggerException( }
'No schedule provided in cron trigger',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
}
if (settings.type === 'HOURS' && settings.schedule.hour <= 0) {
throw new WorkflowTriggerException(
'Invalid hour value. Should be integer greater than 1',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
}
if ( return;
settings.type === 'HOURS' && }
(settings.schedule.minute < 0 || settings.schedule.minute > 59)
) {
throw new WorkflowTriggerException(
'Invalid minute value. Should be integer between 0 and 59',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
}
if (settings.type === 'MINUTES' && settings.schedule.minute <= 0) { case 'HOURS': {
throw new WorkflowTriggerException( if (!settings.schedule) {
'Invalid minute value. Should be integer greater than 1', throw new WorkflowTriggerException(
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, 'No schedule provided in cron trigger',
); WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
}
if (settings.schedule.hour <= 0) {
throw new WorkflowTriggerException(
'Invalid hour value. Should be integer greater than 1',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
}
if (settings.schedule.minute < 0 || settings.schedule.minute > 59) {
throw new WorkflowTriggerException(
'Invalid minute value. Should be integer between 0 and 59',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
}
return;
}
case 'MINUTES': {
if (!settings.schedule) {
throw new WorkflowTriggerException(
'No schedule provided in cron trigger',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
}
if (settings.schedule.minute <= 0) {
throw new WorkflowTriggerException(
'Invalid minute value. Should be integer greater than 1',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
}
return;
}
default:
throw new WorkflowTriggerException(
'Invalid setting type provided in cron trigger',
WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER,
);
} }
} }