Update next time execution time when starting function
This commit is contained in:
175
routes/timer.py
175
routes/timer.py
@@ -149,6 +149,75 @@ DEFAULT_ENVIRONMENT = """{
|
||||
|
||||
timer = Blueprint('timer', __name__)
|
||||
|
||||
def calculate_next_run(trigger_type, frequency_minutes=None, run_date=None, cron_expression=None, base_time=None):
|
||||
"""
|
||||
Calculate the next execution time based on trigger configuration.
|
||||
|
||||
Args:
|
||||
trigger_type: One of 'interval', 'date', or 'cron'
|
||||
frequency_minutes: Minutes between executions (for interval type)
|
||||
run_date: Specific datetime to run (for date type)
|
||||
cron_expression: Cron expression string (for cron type)
|
||||
base_time: Base datetime to calculate from (defaults to now)
|
||||
|
||||
Returns:
|
||||
tuple: (next_run datetime, error_response dict or None)
|
||||
If error_response is not None, it contains the error to return to client
|
||||
"""
|
||||
if base_time is None:
|
||||
base_time = datetime.now(timezone.utc)
|
||||
|
||||
next_run = None
|
||||
|
||||
if trigger_type == 'interval':
|
||||
if frequency_minutes is None:
|
||||
return None, {
|
||||
"status": "error",
|
||||
"message": "frequency_minutes is required for interval trigger type"
|
||||
}
|
||||
next_run = base_time + timedelta(minutes=int(frequency_minutes))
|
||||
|
||||
elif trigger_type == 'date':
|
||||
if run_date is None:
|
||||
return None, {
|
||||
"status": "error",
|
||||
"message": "run_date is required for date trigger type"
|
||||
}
|
||||
# run_date should already be a datetime object
|
||||
if isinstance(run_date, str):
|
||||
run_date = datetime.fromisoformat(run_date)
|
||||
next_run = run_date
|
||||
|
||||
elif trigger_type == 'cron':
|
||||
if cron_expression is None:
|
||||
return None, {
|
||||
"status": "error",
|
||||
"message": "cron_expression is required for cron trigger type"
|
||||
}
|
||||
from croniter import croniter
|
||||
|
||||
# Validate cron expression
|
||||
try:
|
||||
if not croniter.is_valid(cron_expression):
|
||||
return None, {
|
||||
"status": "error",
|
||||
"message": "Invalid cron expression format"
|
||||
}
|
||||
# Calculate next run time from base_time
|
||||
next_run = croniter(cron_expression, base_time).get_next(datetime)
|
||||
except Exception as e:
|
||||
return None, {
|
||||
"status": "error",
|
||||
"message": f"Invalid cron expression: {str(e)}"
|
||||
}
|
||||
else:
|
||||
return None, {
|
||||
"status": "error",
|
||||
"message": f"Invalid trigger type: {trigger_type}"
|
||||
}
|
||||
|
||||
return next_run, None
|
||||
|
||||
@timer.route('/overview')
|
||||
@login_required
|
||||
def overview():
|
||||
@@ -192,36 +261,28 @@ def new():
|
||||
"message": "Invalid trigger type"
|
||||
}), 400
|
||||
|
||||
# Calculate next_run based on trigger type
|
||||
next_run = None
|
||||
# Calculate next_run based on trigger type using centralized function
|
||||
next_run, error = calculate_next_run(
|
||||
trigger_type,
|
||||
frequency_minutes=data.get('frequency_minutes'),
|
||||
run_date=data.get('run_date'),
|
||||
cron_expression=data.get('cron_expression')
|
||||
)
|
||||
|
||||
if error:
|
||||
return jsonify(error), 400
|
||||
|
||||
# Extract individual variables for database storage
|
||||
frequency_minutes = None
|
||||
run_date = None
|
||||
cron_expression = None
|
||||
|
||||
if trigger_type == 'interval':
|
||||
frequency_minutes = int(data.get('frequency_minutes'))
|
||||
next_run = datetime.now(timezone.utc) + timedelta(minutes=frequency_minutes)
|
||||
elif trigger_type == 'date':
|
||||
run_date = datetime.fromisoformat(data.get('run_date'))
|
||||
next_run = run_date
|
||||
elif trigger_type == 'cron':
|
||||
from croniter import croniter
|
||||
cron_expression = data.get('cron_expression')
|
||||
|
||||
# Validate cron expression
|
||||
try:
|
||||
if not croniter.is_valid(cron_expression):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": "Invalid cron expression format"
|
||||
}), 400
|
||||
# Calculate first run time
|
||||
next_run = croniter(cron_expression, datetime.now(timezone.utc)).get_next(datetime)
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Invalid cron expression: {str(e)}"
|
||||
}), 400
|
||||
|
||||
# Insert new timer function
|
||||
db.execute("""
|
||||
@@ -301,36 +362,28 @@ def edit(function_id):
|
||||
"message": "Invalid trigger type"
|
||||
}), 400
|
||||
|
||||
# Calculate next_run based on trigger type
|
||||
next_run = None
|
||||
# Calculate next_run based on trigger type using centralized function
|
||||
next_run, error = calculate_next_run(
|
||||
trigger_type,
|
||||
frequency_minutes=data.get('frequency_minutes'),
|
||||
run_date=data.get('run_date'),
|
||||
cron_expression=data.get('cron_expression')
|
||||
)
|
||||
|
||||
if error:
|
||||
return jsonify(error), 400
|
||||
|
||||
# Extract individual variables for database storage
|
||||
frequency_minutes = None
|
||||
run_date = None
|
||||
cron_expression = None
|
||||
|
||||
if trigger_type == 'interval':
|
||||
frequency_minutes = int(data.get('frequency_minutes'))
|
||||
next_run = datetime.now(timezone.utc) + timedelta(minutes=frequency_minutes)
|
||||
elif trigger_type == 'date':
|
||||
run_date = datetime.fromisoformat(data.get('run_date'))
|
||||
next_run = run_date
|
||||
elif trigger_type == 'cron':
|
||||
from croniter import croniter
|
||||
cron_expression = data.get('cron_expression')
|
||||
|
||||
# Validate cron expression
|
||||
try:
|
||||
if not croniter.is_valid(cron_expression):
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": "Invalid cron expression format"
|
||||
}), 400
|
||||
# Calculate first run time
|
||||
next_run = croniter(cron_expression, datetime.now(timezone.utc)).get_next(datetime)
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": f"Invalid cron expression: {str(e)}"
|
||||
}), 400
|
||||
|
||||
# Update timer function
|
||||
db.execute("""
|
||||
@@ -406,20 +459,48 @@ def delete(function_id):
|
||||
@login_required
|
||||
def toggle(function_id):
|
||||
try:
|
||||
# Toggle the enabled status
|
||||
result = db.execute("""
|
||||
UPDATE timer_functions
|
||||
SET enabled = NOT enabled
|
||||
# Fetch timer function details first to get trigger configuration
|
||||
timer_function = db.execute("""
|
||||
SELECT enabled, trigger_type, frequency_minutes, run_date, cron_expression
|
||||
FROM timer_functions
|
||||
WHERE id = %s AND user_id = %s
|
||||
RETURNING enabled
|
||||
""", [function_id, current_user.id], commit=True, one=True)
|
||||
""", [function_id, current_user.id], one=True)
|
||||
|
||||
if not result:
|
||||
if not timer_function:
|
||||
return jsonify({
|
||||
"status": "error",
|
||||
"message": "Timer function not found or unauthorized"
|
||||
}), 404
|
||||
|
||||
# Determine new enabled state (toggling)
|
||||
new_enabled = not timer_function['enabled']
|
||||
|
||||
# If enabling the timer, recalculate next_run to prevent stale execution times
|
||||
if new_enabled:
|
||||
next_run, error = calculate_next_run(
|
||||
timer_function['trigger_type'],
|
||||
frequency_minutes=timer_function['frequency_minutes'],
|
||||
run_date=timer_function['run_date'],
|
||||
cron_expression=timer_function['cron_expression']
|
||||
)
|
||||
|
||||
if error:
|
||||
return jsonify(error), 400
|
||||
|
||||
# Update both enabled status and next_run
|
||||
db.execute("""
|
||||
UPDATE timer_functions
|
||||
SET enabled = %s, next_run = %s
|
||||
WHERE id = %s AND user_id = %s
|
||||
""", [new_enabled, next_run, function_id, current_user.id], commit=True)
|
||||
else:
|
||||
# Just toggle the enabled status
|
||||
db.execute("""
|
||||
UPDATE timer_functions
|
||||
SET enabled = %s
|
||||
WHERE id = %s AND user_id = %s
|
||||
""", [new_enabled, function_id, current_user.id], commit=True)
|
||||
|
||||
# Fetch updated timer functions for the overview template
|
||||
timer_functions = db.execute("""
|
||||
SELECT id, name, code, environment, trigger_type,
|
||||
|
||||
Reference in New Issue
Block a user