Skip to content

Commit 32fb307

Browse files
fix(ui): advise users on correct chron syntax for scheduling
1 parent 7e749ff commit 32fb307

File tree

3 files changed

+94
-1
lines changed

3 files changed

+94
-1
lines changed

datahub-web-react/src/app/ingest/source/builder/CreateScheduleStep.tsx

+35-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Cron } from 'react-js-cron';
55
import 'react-js-cron/dist/styles.css';
66
import styled from 'styled-components';
77
import cronstrue from 'cronstrue';
8-
import { CheckCircleOutlined, WarningOutlined } from '@ant-design/icons';
8+
import { CheckCircleOutlined, WarningOutlined, InfoCircleOutlined } from '@ant-design/icons';
99
import { SourceBuilderState, StepProps } from './types';
1010
import { TimezoneSelect } from './TimezoneSelect';
1111
import { ANTD_GRAY, REDESIGN_COLORS } from '../../../entity/shared/constants';
@@ -81,6 +81,26 @@ const StyledWarningOutlined = styled(WarningOutlined)`
8181
margin-top: 12px;
8282
`;
8383

84+
const HashmarkNotice = styled.div`
85+
margin-top: 8px;
86+
padding: 8px;
87+
background-color: #e6f7ff;
88+
border: 1px solid #91d5ff;
89+
border-radius: 4px;
90+
display: flex;
91+
align-items: flex-start;
92+
`;
93+
94+
const InfoIcon = styled(InfoCircleOutlined)`
95+
color: #1890ff;
96+
margin-right: 8px;
97+
margin-top: 3px;
98+
`;
99+
100+
const containsHashmark = (cronExpression: string): boolean => {
101+
return cronExpression.includes('#');
102+
};
103+
84104
const ItemDescriptionText = styled(Typography.Paragraph)``;
85105

86106
const DAILY_MIDNIGHT_CRON_INTERVAL = '0 0 * * *';
@@ -93,6 +113,7 @@ export const CreateScheduleStep = ({ state, updateState, goTo, prev }: StepProps
93113
const [advancedCronCheck, setAdvancedCronCheck] = useState(false);
94114
const [scheduleCronInterval, setScheduleCronInterval] = useState(interval);
95115
const [scheduleTimezone, setScheduleTimezone] = useState(timezone);
116+
const hasHashmark = useMemo(() => containsHashmark(scheduleCronInterval), [scheduleCronInterval]);
96117

97118
const cronAsText = useMemo(() => {
98119
if (scheduleCronInterval) {
@@ -183,6 +204,19 @@ export const CreateScheduleStep = ({ state, updateState, goTo, prev }: StepProps
183204
/>
184205
</AdvancedSchedule>
185206
</Schedule>
207+
{advancedCronCheck && hasHashmark && (
208+
<HashmarkNotice>
209+
<InfoIcon />
210+
<div>
211+
<Typography.Text strong>Special schedule syntax detected</Typography.Text>
212+
<Typography.Paragraph style={{ marginBottom: 0 }}>
213+
Your schedule uses special syntax (# character) to run on a specific occurrence of a day each month.
214+
Please combine the day of the month with the day of the week to achieve this schedule.
215+
For example use '0 0 8-14 0 1' instead of '0 0 0 0 1#2' for the 2nd Monday of the month
216+
</Typography.Paragraph>
217+
</div>
218+
</HashmarkNotice>
219+
)}
186220
<CronText>
187221
{cronAsText.error && <>Invalid cron schedule. Cron must be of UNIX form:</>}
188222
{!cronAsText.text && (

ingestion-scheduler/src/test/java/com/datahub/metadata/ingestion/IngestionSchedulerTest.java

+55
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,59 @@ public void testUnscheduleAll() throws Exception {
299299
// And that the future is cancelled
300300
Assert.assertTrue(future.isCancelled());
301301
}
302+
303+
@Test
304+
public void testDayRangeAndHashmarkSyntax() throws Exception {
305+
assertEquals(_ingestionScheduler.nextIngestionSourceExecutionCache.size(), 1);
306+
307+
// Create two ingestion sources with different cron syntax methods
308+
// need to affirm both '0 0 1-7 * 0' and the equivalent '0 0 0 * 0#2' work
309+
final Urn dayRangeUrn = Urn.createFromString("urn:li:dataHubIngestionSourceUrn:day-range");
310+
final DataHubIngestionSourceInfo dayRangeInfo = new DataHubIngestionSourceInfo();
311+
dayRangeInfo.setSchedule(
312+
new DataHubIngestionSourceSchedule().setInterval("0 0 1-7 * 0").setTimezone("UTC"));
313+
dayRangeInfo.setType("mysql");
314+
dayRangeInfo.setName("Day Range Syntax Test Source");
315+
dayRangeInfo.setConfig(
316+
new DataHubIngestionSourceConfig()
317+
.setExecutorId("default")
318+
.setRecipe("{ type: \"type\" }")
319+
.setVersion("0.8.18"));
320+
321+
final Urn hashmarkUrn = Urn.createFromString("urn:li:dataHubIngestionSourceUrn:hashmark");
322+
final DataHubIngestionSourceInfo hashmarkInfo = new DataHubIngestionSourceInfo();
323+
hashmarkInfo.setSchedule(
324+
new DataHubIngestionSourceSchedule().setInterval("0 0 * * 0#1").setTimezone("UTC"));
325+
hashmarkInfo.setType("mysql");
326+
hashmarkInfo.setName("Hashmark Syntax Test Source");
327+
hashmarkInfo.setConfig(
328+
new DataHubIngestionSourceConfig()
329+
.setExecutorId("default")
330+
.setRecipe("{ type: \"type\" }")
331+
.setVersion("0.8.18"));
332+
333+
// Schedule both sources
334+
_ingestionScheduler.scheduleNextIngestionSourceExecution(dayRangeUrn, dayRangeInfo);
335+
_ingestionScheduler.scheduleNextIngestionSourceExecution(hashmarkUrn, hashmarkInfo);
336+
337+
// Verify both were scheduled successfully
338+
assertEquals(_ingestionScheduler.nextIngestionSourceExecutionCache.size(), 3);
339+
assertTrue(_ingestionScheduler.nextIngestionSourceExecutionCache.containsKey(dayRangeUrn));
340+
assertTrue(_ingestionScheduler.nextIngestionSourceExecutionCache.containsKey(hashmarkUrn));
341+
342+
// Get both scheduled futures
343+
ScheduledFuture<?> dayRangeFuture =
344+
_ingestionScheduler.nextIngestionSourceExecutionCache.get(dayRangeUrn);
345+
ScheduledFuture<?> hashmarkFuture =
346+
_ingestionScheduler.nextIngestionSourceExecutionCache.get(hashmarkUrn);
347+
348+
// Both should be valid futures
349+
assertFalse(dayRangeFuture.isCancelled());
350+
assertFalse(hashmarkFuture.isCancelled());
351+
352+
// Clean up
353+
_ingestionScheduler.unscheduleNextIngestionSourceExecution(dayRangeUrn);
354+
_ingestionScheduler.unscheduleNextIngestionSourceExecution(hashmarkUrn);
355+
assertEquals(_ingestionScheduler.nextIngestionSourceExecutionCache.size(), 1);
356+
}
302357
}

metadata-ingestion/schedule_docs/cron.md

+4
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ We can use crontab to schedule ingestion to run five minutes after midnight, eve
2525
5 0 * * * datahub ingest -c /home/ubuntu/datahub_ingest/mysql_to_datahub.yml
2626
```
2727

28+
:::tip
29+
For scheduling on a specific occurrence of a weekday (e.g., 2nd Monday), use day ranges like 8-14 * 1 instead of hashmark syntax like * * 1#2. While both work, the the hashmark syntax is unsupported in the UI scheduler.
30+
:::
31+
2832
Read through [crontab docs](https://man7.org/linux/man-pages/man5/crontab.5.html) for more options related to scheduling.

0 commit comments

Comments
 (0)