Skip to content

Commit 1940e9e

Browse files
authored
Merge pull request #677 from UrekD/master
Extend repeat schedule functionality #676
2 parents 4225459 + 59a7bf1 commit 1940e9e

File tree

4 files changed

+130
-13
lines changed

4 files changed

+130
-13
lines changed

src/model/external_model.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ def parse_external_schedule(external_schedule):
129129
return {
130130
'repeatable': external_schedule.get('repeatable'),
131131
'start_datetime': external_schedule.get('startDatetime'),
132+
'end_option': external_schedule.get('endOption'),
133+
'end_arg': external_schedule.get('endArg'),
132134
'repeat_unit': external_schedule.get('repeatUnit'),
133135
'repeat_period': external_schedule.get('repeatPeriod'),
134136
'weekdays': external_schedule.get('weekDays')

src/scheduling/schedule_config.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77
ALLOWED_WEEKDAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
88

99

10-
def _read_start_datetime(incoming_schedule_config):
11-
start_datetime = model_helper.read_datetime_from_config('start_datetime', incoming_schedule_config)
12-
if start_datetime is None:
13-
raise InvalidScheduleException('start_datetime is required')
14-
return start_datetime
15-
10+
def _read_datetime(incoming_schedule_config, key):
11+
datetime_value = model_helper.read_datetime_from_config(key, incoming_schedule_config)
12+
if datetime_value is None:
13+
raise InvalidScheduleException(f'{key} is required')
14+
return datetime_value
1615

1716
def _read_repeat_unit(incoming_schedule_config):
1817
repeat_unit = incoming_schedule_config.get('repeat_unit')
@@ -32,6 +31,27 @@ def _read_repeat_period(incoming_schedule_config):
3231
return period
3332

3433

34+
def _read_end_arg_int(incoming_schedule_config):
35+
end_arg = model_helper.read_int_from_config('end_arg', incoming_schedule_config)
36+
if end_arg is None:
37+
raise InvalidScheduleException('end_arg is required for repeatable schedule')
38+
elif end_arg <= 0:
39+
raise InvalidScheduleException('end_arg should be > 0')
40+
return end_arg
41+
42+
43+
def _read_end_args(incoming_schedule_config):
44+
end_option = incoming_schedule_config.get('end_option')
45+
if end_option == 'end_datetime':
46+
end_arg = _read_datetime(incoming_schedule_config, 'end_arg')
47+
return end_option,end_arg
48+
elif end_option == 'max_executions':
49+
end_arg = _read_end_arg_int(incoming_schedule_config)
50+
return end_option,end_arg
51+
else:
52+
return end_option,None
53+
54+
3555
def read_repeatable_flag(incoming_schedule_config):
3656
repeatable = model_helper.read_bool_from_config('repeatable', incoming_schedule_config)
3757
if repeatable is None:
@@ -52,10 +72,15 @@ def read_weekdays(incoming_schedule_config):
5272

5373
def read_schedule_config(incoming_schedule_config):
5474
repeatable = read_repeatable_flag(incoming_schedule_config)
55-
start_datetime = _read_start_datetime(incoming_schedule_config)
75+
start_datetime = _read_datetime(incoming_schedule_config, 'start_datetime')
5676

5777
prepared_schedule_config = ScheduleConfig(repeatable, start_datetime)
5878
if repeatable:
79+
80+
prepared_schedule_config.executions_count = model_helper.read_int_from_config('executions_count', incoming_schedule_config, default=0)
81+
82+
prepared_schedule_config.end_option, prepared_schedule_config.end_arg = _read_end_args(incoming_schedule_config)
83+
5984
prepared_schedule_config.repeat_unit = _read_repeat_unit(incoming_schedule_config)
6085
prepared_schedule_config.repeat_period = _read_repeat_period(incoming_schedule_config)
6186

@@ -70,6 +95,9 @@ class ScheduleConfig:
7095
def __init__(self, repeatable, start_datetime) -> None:
7196
self.repeatable = repeatable
7297
self.start_datetime = start_datetime # type: datetime
98+
self.end_option = None
99+
self.end_arg = None
100+
self.executions_count = None
73101
self.repeat_unit = None
74102
self.repeat_period = None
75103
self.weekdays = None
@@ -80,6 +108,16 @@ def as_serializable_dict(self):
80108
'start_datetime': date_utils.to_iso_string(self.start_datetime)
81109
}
82110

111+
if self.end_option == 'end_datetime':
112+
result['end_option'] = self.end_option
113+
result['end_arg'] = date_utils.to_iso_string(self.end_arg)
114+
elif self.end_option == 'max_executions':
115+
result['end_option'] = self.end_option
116+
result['end_arg'] = self.end_arg
117+
118+
if self.repeatable:
119+
result['executions_count'] = self.executions_count
120+
83121
if self.repeat_unit is not None:
84122
result['repeat_unit'] = self.repeat_unit
85123

src/scheduling/schedule_service.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ def create_job(self, script_name, parameter_values, incoming_schedule_config, us
7676
if not schedule_config.repeatable and date_utils.is_past(schedule_config.start_datetime):
7777
raise InvalidScheduleException('Start date should be in the future')
7878

79+
if schedule_config.end_option == 'end_datetime':
80+
if schedule_config.start_datetime > schedule_config.end_arg:
81+
raise InvalidScheduleException('End date should be after start date')
82+
83+
if schedule_config.end_option == 'max_executions' and schedule_config.end_arg <= 0:
84+
raise InvalidScheduleException('Count should be greater than 0!')
85+
7986
id = self._id_generator.next_id()
8087

8188
normalized_values = {}
@@ -106,8 +113,16 @@ def schedule_job(self, job: SchedulingJob, job_path):
106113

107114
if not schedule.repeatable and date_utils.is_past(schedule.start_datetime):
108115
return
109-
116+
117+
if schedule.end_option == 'max_executions' and schedule.end_arg <= schedule.executions_count:
118+
return
119+
110120
next_datetime = schedule.get_next_time()
121+
122+
if schedule.end_option == 'end_datetime':
123+
if next_datetime > schedule.end_arg:
124+
return
125+
111126
LOGGER.info(
112127
'Scheduling ' + job.get_log_name() + ' at ' + next_datetime.astimezone(tz=None).strftime('%H:%M, %d %B %Y'))
113128

@@ -136,6 +151,12 @@ def cleanup():
136151
self._execution_service.cleanup_execution(execution_id, user)
137152

138153
self._execution_service.add_finish_listener(cleanup, execution_id)
154+
155+
if job.schedule.repeatable:
156+
job.schedule.executions_count += 1
157+
158+
self.save_job(job)
159+
139160
except:
140161
LOGGER.exception('Failed to execute ' + job.get_log_name())
141162

web-src/src/main-app/components/schedule/SchedulePanel.vue

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,39 @@
4141
label="Time" @error="checkErrors"/>
4242
</div>
4343

44+
<div>
45+
<span class="schedule-repeat_col-1">End:</span>
46+
<div class="schedule-type-panel">
47+
<p class="schedule-type-field">
48+
<label>
49+
<input :checked="endOption === 'never'" class="with-gap" name="end-type" type="radio" @click="endOption = 'never'" />
50+
<span>Never</span>
51+
</label>
52+
</p>
53+
<p class="schedule-type-field">
54+
<label>
55+
<input :checked="endOption === 'maxExecuteCount'" class="with-gap" name="end-type" type="radio" @click="endOption = 'maxExecuteCount'" />
56+
<span>Count</span>
57+
</label>
58+
</p>
59+
<p class="schedule-type-field">
60+
<label>
61+
<input :checked="endOption === 'endDatetime'" class="with-gap" name="end-type" type="radio" @click="endOption = 'endDatetime'" />
62+
<span>Date</span>
63+
</label>
64+
</p>
65+
</div>
66+
<br>
67+
<div v-if="endOption === 'endDatetime'">
68+
<span class="schedule-repeat_col-1">Ending</span>
69+
<DatePicker v-model="endDate" :show-header-in-modal="!mobileView" class="inline repeat-start-date schedule-repeat_col-2" label="Date" />
70+
<TimePicker v-model="endTime" class="inline repeat-start-time schedule-repeat_col-3" label="Time" @error="checkErrors" />
71+
</div>
72+
<div v-if="endOption === 'maxExecuteCount'">
73+
<span class="schedule-repeat_col-1">Count</span>
74+
<Textfield v-model="maxExecuteCount" :config="repeatPeriodField" class="inline repeat-period-field schedule-repeat_col-2" @error="checkErrors" />
75+
</div>
76+
4477
<div v-if="repeatTimeUnit === 'weeks'" class="repeat-weeks-panel">
4578
<div :class="{ error: weekdaysError }" class="repeat-weekday-panel">
4679
<ToggleDayButton v-for="day in weekDays"
@@ -62,6 +95,7 @@
6295
:preloaderStyle="{ width: '20px', height: '20px' }"
6396
title="Schedule"/>
6497
</div>
98+
</div>
6599
</div>
66100
</template>
67101

@@ -93,12 +127,19 @@ export default {
93127
const now = new Date();
94128
const currentDay = now.getDay();
95129
130+
const endDay = new Date(now);
131+
endDay.setDate(now.getDate() + 1);
132+
96133
return {
97134
oneTimeSchedule: true,
98135
startDate: now,
99136
startTime: now.toTimeString().substr(0, 5),
137+
endOption: 'never',
138+
endDate: endDay,
139+
endTime: endDay.toTimeString().substr(0, 5),
100140
id: null,
101141
repeatPeriod: 1,
142+
maxExecuteCount: 1,
102143
repeatTimeUnit: 'days',
103144
weekDays: [
104145
{'day': 'Monday', active: currentDay === 1},
@@ -135,15 +176,30 @@ export default {
135176
136177
buildScheduleSetup() {
137178
const startDatetime = new Date(this.startDate);
138-
const [hours, minutes] = this.startTime.split(':')
139-
startDatetime.setHours(parseInt(hours), parseInt(minutes), 0, 0)
179+
const [hours, minutes] = this.startTime.split(':');
180+
startDatetime.setHours(parseInt(hours), parseInt(minutes), 0, 0);
181+
182+
let endOption = this.endOption;
183+
let endArg = null;
184+
185+
if (this.endOption === 'maxExecuteCount') {
186+
endArg = this.maxExecuteCount;
187+
endOption = 'max_executions';
188+
} else if (this.endOption === 'endDatetime') {
189+
const endDatetime = new Date(this.endDate);
190+
const [hoursEnd, minutesEnd] = this.endTime.split(':');
191+
endDatetime.setHours(parseInt(hoursEnd), parseInt(minutesEnd), 0, 0);
192+
endArg = endDatetime;
193+
endOption = 'end_datetime';
194+
}
140195
141-
const weekDays = this.weekDays.filter(day => day.active)
142-
.map(day => day.day);
196+
const weekDays = this.weekDays.filter(day => day.active).map(day => day.day);
143197
144198
return {
145199
repeatable: !this.oneTimeSchedule,
146200
startDatetime: startDatetime,
201+
endOption: endOption,
202+
endArg: endArg,
147203
repeatUnit: this.repeatTimeUnit,
148204
repeatPeriod: this.repeatPeriod,
149205
weekDays: weekDays
@@ -206,7 +262,7 @@ export default {
206262
font-size: 16px;
207263
max-width: 320px;
208264
width: 100%;
209-
height: 380px;
265+
height: 480px;
210266
display: flex;
211267
flex-direction: column;
212268
justify-content: space-between;

0 commit comments

Comments
 (0)