diff --git a/redash/models/__init__.py b/redash/models/__init__.py index dcfc4b5bfd..93d2515003 100644 --- a/redash/models/__init__.py +++ b/redash/models/__init__.py @@ -517,6 +517,22 @@ def by_user(cls, user): def by_api_key(cls, api_key): return cls.query.filter(cls.api_key == api_key).one() + @classmethod + def past_scheduled_queries(cls): + now = utils.utcnow() + queries = ( + Query.query + .filter(Query.schedule.isnot(None)) + .order_by(Query.id) + ) + return filter( + lambda x: + x.schedule["until"] is not None and pytz.utc.localize( + datetime.datetime.strptime(x.schedule['until'], '%Y-%m-%d') + ) <= now, + queries + ) + @classmethod def outdated_queries(cls): queries = ( diff --git a/redash/tasks/__init__.py b/redash/tasks/__init__.py index e5de680381..52a1511057 100644 --- a/redash/tasks/__init__.py +++ b/redash/tasks/__init__.py @@ -1,3 +1,3 @@ from .general import record_event, version_check, send_mail, sync_user_details -from .queries import QueryTask, refresh_queries, refresh_schemas, cleanup_query_results, execute_query +from .queries import QueryTask, refresh_queries, refresh_schemas, cleanup_query_results, execute_query, empty_schedules from .alerts import check_alerts_for_query diff --git a/redash/tasks/queries.py b/redash/tasks/queries.py index 6002ccd275..f322b51052 100644 --- a/redash/tasks/queries.py +++ b/redash/tasks/queries.py @@ -159,6 +159,18 @@ def enqueue_query(query, data_source, user_id, is_api_key=False, scheduled_query return job +@celery.task(name="redash.tasks.empty_schedules") +def empty_schedules(): + logger.info("Deleting schedules of past scheduled queries...") + + queries = models.Query.past_scheduled_queries() + for query in queries: + query.schedule = None + models.db.session.commit() + + logger.info("Deleted %d schedules.", len(queries)) + + @celery.task(name="redash.tasks.refresh_queries") def refresh_queries(): logger.info("Refreshing queries...") diff --git a/redash/worker.py b/redash/worker.py index f55ebd550a..ca0a65f002 100644 --- a/redash/worker.py +++ b/redash/worker.py @@ -26,6 +26,10 @@ 'task': 'redash.tasks.refresh_queries', 'schedule': timedelta(seconds=30) }, + 'empty_schedules': { + 'task': 'redash.tasks.empty_schedules', + 'schedule': timedelta(minutes=60) + }, 'refresh_schemas': { 'task': 'redash.tasks.refresh_schemas', 'schedule': timedelta(minutes=settings.SCHEMAS_REFRESH_SCHEDULE) diff --git a/tests/models/test_queries.py b/tests/models/test_queries.py index 94d25c5d05..d6595e8ecb 100644 --- a/tests/models/test_queries.py +++ b/tests/models/test_queries.py @@ -4,6 +4,7 @@ import datetime from redash.models import Query, Group, Event, db from redash.utils import utcnow +import mock class QueryTest(BaseTestCase): @@ -170,6 +171,18 @@ def test_search_query_parser_emails(self): self.assertNotIn(q1, queries) self.assertIn(q2, queries) + def test_past_scheduled_queries(self): + query = self.factory.create_query() + one_day_ago = (utcnow() - datetime.timedelta(days=1)).strftime("%Y-%m-%d") + one_day_later = (utcnow() + datetime.timedelta(days=1)).strftime("%Y-%m-%d") + query1 = self.factory.create_query(schedule={'interval':'3600','until':one_day_ago}) + query2 = self.factory.create_query(schedule={'interval':'3600','until':one_day_later}) + oq = staticmethod(lambda: [query1, query2]) + with mock.patch.object(query.query.filter(), 'order_by', oq): + res = query.past_scheduled_queries() + self.assertTrue(query1 in res) + self.assertFalse(query2 in res) + class QueryRecentTest(BaseTestCase): def test_global_recent(self): diff --git a/tests/tasks/test_empty_schedule.py b/tests/tasks/test_empty_schedule.py new file mode 100644 index 0000000000..6dc59a0fd5 --- /dev/null +++ b/tests/tasks/test_empty_schedule.py @@ -0,0 +1,16 @@ +import datetime +from mock import patch +from tests import BaseTestCase +from redash.tasks import empty_schedules +from redash.models import Query +from redash.utils import utcnow + + +class TestEmptyScheduleQuery(BaseTestCase): + def test_empty_schedules(self): + one_day_ago = (utcnow() - datetime.timedelta(days=1)).strftime("%Y-%m-%d") + query = self.factory.create_query(schedule={'interval':'3600','until':one_day_ago}) + oq = staticmethod(lambda: [query]) + with patch.object(Query, 'past_scheduled_queries', oq): + empty_schedules() + self.assertEqual(query.schedule, None)