Add UTC timezone label to CRON trigger form (#14674)
- Added 'Cron will be triggered at UTC time' notice below trigger interval dropdown - Positioned correctly between dropdown and expression field to match design - Only shows when Custom CRON option is selected --------- Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { formatTime } from '../formatTime';
|
||||
|
||||
describe('formatTime', () => {
|
||||
it('should format 24-hour time', () => {
|
||||
expect(formatTime('9', '30', true)).toBe('09:30');
|
||||
expect(formatTime('14', '0', true)).toBe('14:00');
|
||||
expect(formatTime('0', '0', true)).toBe('00:00');
|
||||
expect(formatTime('23', '59', true)).toBe('23:59');
|
||||
});
|
||||
|
||||
it('should format 12-hour time', () => {
|
||||
expect(formatTime('9', '30', false)).toBe('9:30 AM');
|
||||
expect(formatTime('14', '0', false)).toBe('2:00 PM');
|
||||
expect(formatTime('0', '0', false)).toBe('12:00 AM');
|
||||
expect(formatTime('12', '0', false)).toBe('12:00 PM');
|
||||
expect(formatTime('23', '59', false)).toBe('11:59 PM');
|
||||
});
|
||||
|
||||
it('should handle invalid inputs', () => {
|
||||
expect(formatTime('invalid', '30', true)).toBe('');
|
||||
expect(formatTime('9', 'invalid', true)).toBe('');
|
||||
expect(formatTime('', '', true)).toBe('');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { getOrdinalNumber } from '../getOrdinalNumber';
|
||||
|
||||
describe('getOrdinalNumber', () => {
|
||||
it('should return correct ordinal numbers', () => {
|
||||
expect(getOrdinalNumber(1)).toBe('1st');
|
||||
expect(getOrdinalNumber(2)).toBe('2nd');
|
||||
expect(getOrdinalNumber(3)).toBe('3rd');
|
||||
expect(getOrdinalNumber(4)).toBe('4th');
|
||||
expect(getOrdinalNumber(11)).toBe('11th');
|
||||
expect(getOrdinalNumber(21)).toBe('21st');
|
||||
expect(getOrdinalNumber(22)).toBe('22nd');
|
||||
expect(getOrdinalNumber(23)).toBe('23rd');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const formatTime = (
|
||||
hour: string,
|
||||
minute: string,
|
||||
use24HourFormat: boolean,
|
||||
): string => {
|
||||
if (!isDefined(hour) || !isDefined(minute)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const hourNum = parseInt(hour, 10);
|
||||
const minuteNum = parseInt(minute, 10);
|
||||
|
||||
if (isNaN(hourNum) || isNaN(minuteNum)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (use24HourFormat) {
|
||||
return `${hourNum.toString().padStart(2, '0')}:${minuteNum.toString().padStart(2, '0')}`;
|
||||
} else {
|
||||
const period = hourNum >= 12 ? 'PM' : 'AM';
|
||||
const displayHour =
|
||||
hourNum === 0 ? 12 : hourNum > 12 ? hourNum - 12 : hourNum;
|
||||
return `${displayHour}:${minuteNum.toString().padStart(2, '0')} ${period}`;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { selectOrdinal } from '@lingui/core/macro';
|
||||
|
||||
export const getOrdinalNumber = (num: number): string => {
|
||||
try {
|
||||
return selectOrdinal(num, {
|
||||
one: `${num}st`,
|
||||
two: `${num}nd`,
|
||||
few: `${num}rd`,
|
||||
other: `${num}th`,
|
||||
});
|
||||
} catch {
|
||||
// Fallback for test environment - basic English ordinals
|
||||
const lastDigit = num % 10;
|
||||
const lastTwoDigits = num % 100;
|
||||
|
||||
if (lastTwoDigits >= 11 && lastTwoDigits <= 13) {
|
||||
return `${num}th`;
|
||||
}
|
||||
|
||||
switch (lastDigit) {
|
||||
case 1:
|
||||
return `${num}st`;
|
||||
case 2:
|
||||
return `${num}nd`;
|
||||
case 3:
|
||||
return `${num}rd`;
|
||||
default:
|
||||
return `${num}th`;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { isListValue } from '../isListValue';
|
||||
|
||||
describe('isListValue', () => {
|
||||
it('should detect list values', () => {
|
||||
expect(isListValue('1,2,3')).toBe(true);
|
||||
expect(isListValue('0,15,30,45')).toBe(true);
|
||||
expect(isListValue('1,5')).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject non-list values', () => {
|
||||
expect(isListValue('*')).toBe(false);
|
||||
expect(isListValue('*/5')).toBe(false);
|
||||
expect(isListValue('1-5')).toBe(false);
|
||||
expect(isListValue('15')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { isNumericRange } from '../isNumericRange';
|
||||
|
||||
describe('isNumericRange', () => {
|
||||
it('should detect numeric ranges', () => {
|
||||
expect(isNumericRange('1-5')).toBe(true);
|
||||
expect(isNumericRange('10-20')).toBe(true);
|
||||
expect(isNumericRange('0-59')).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect single numbers', () => {
|
||||
expect(isNumericRange('15')).toBe(true);
|
||||
expect(isNumericRange('0')).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject non-numeric ranges', () => {
|
||||
expect(isNumericRange('*')).toBe(false);
|
||||
expect(isNumericRange('*/5')).toBe(false);
|
||||
expect(isNumericRange('1,2,3')).toBe(false);
|
||||
expect(isNumericRange('invalid')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { isStepValue } from '../isStepValue';
|
||||
|
||||
describe('isStepValue', () => {
|
||||
it('should detect step values', () => {
|
||||
expect(isStepValue('*/5')).toBe(true);
|
||||
expect(isStepValue('1-10/2')).toBe(true);
|
||||
expect(isStepValue('0-59/15')).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject non-step values', () => {
|
||||
expect(isStepValue('*')).toBe(false);
|
||||
expect(isStepValue('1-5')).toBe(false);
|
||||
expect(isStepValue('1,2,3')).toBe(false);
|
||||
expect(isStepValue('15')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const isListValue = (value: string): boolean => {
|
||||
return isDefined(value) && value.includes(',');
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const isNumericRange = (value: string): boolean => {
|
||||
return isDefined(value) && /^\d+(-\d+)?$/.test(value);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const isStepValue = (value: string): boolean => {
|
||||
return isDefined(value) && value.includes('/');
|
||||
};
|
||||
Reference in New Issue
Block a user