Repository: getredash/redash

Issue:
Forked queries should reuse existing query-results



Discussion:

Resolving patch:
diff --git a/redash/data/worker.py b/redash/data/worker.py
--- a/redash/data/worker.py
+++ b/redash/data/worker.py
@@ -314,7 +314,7 @@ def _fork_and_process(self, job_id):
                              self.name, job_id)
                 job.done(None, "Interrupted/Cancelled while running.")
 
-            job.expire(24 * 3600)
+            job.expire(settings.JOB_EXPIRY_TIME)
 
             logging.info("[%s] Finished Processing %s (pid: %d status: %d)",
                          self.name, job_id, self.child_pid, status)
diff --git a/redash/models.py b/redash/models.py
--- a/redash/models.py
+++ b/redash/models.py
@@ -133,8 +133,13 @@ def to_dict(self):
     def get_latest(cls, data_source, query, ttl=0):
         query_hash = utils.gen_query_hash(query)
 
-        query = cls.select().where(cls.query_hash == query_hash, cls.data_source == data_source,
-                                   peewee.SQL("retrieved_at + interval '%s second' >= now() at time zone 'utc'", ttl)).order_by(cls.retrieved_at.desc())
+        if ttl == -1:
+            query = cls.select().where(cls.query_hash == query_hash,
+                                       cls.data_source == data_source).order_by(cls.retrieved_at.desc())
+        else:
+            query = cls.select().where(cls.query_hash == query_hash, cls.data_source == data_source,
+                                       peewee.SQL("retrieved_at + interval '%s second' >= now() at time zone 'utc'",
+                                                  ttl)).order_by(cls.retrieved_at.desc())
 
         return query.first()
 
@@ -261,7 +266,23 @@ def to_dict(self, with_widgets=False):
                 .switch(Query)\
                 .join(QueryResult, join_type=peewee.JOIN_LEFT_OUTER)
             widgets = {w.id: w.to_dict() for w in widgets}
-            widgets_layout = map(lambda row: map(lambda widget_id: widgets.get(widget_id, None), row), layout)
+
+            # The following is a workaround for cases when the widget object gets deleted without the dashboard layout
+            # updated. This happens for users with old databases that didn't have a foreign key relationship between
+            # visualizations and widgets.
+            # It's temporary until better solution is implemented (we probably should move the position information
+            # to the widget).
+            widgets_layout = []
+            for row in layout:
+                new_row = []
+                for widget_id in row:
+                    widget = widgets.get(widget_id, None)
+                    if widget:
+                        new_row.append(widget)
+
+                widgets_layout.append(new_row)
+
+            # widgets_layout = map(lambda row: map(lambda widget_id: widgets.get(widget_id, None), row), layout)
         else:
             widgets_layout = None
 
diff --git a/redash/settings.py b/redash/settings.py
--- a/redash/settings.py
+++ b/redash/settings.py
@@ -63,6 +63,7 @@ def parse_boolean(str):
 ALLOWED_EXTERNAL_USERS = array_from_string(os.environ.get("REDASH_ALLOWED_EXTERNAL_USERS", ''))
 STATIC_ASSETS_PATH = fix_assets_path(os.environ.get("REDASH_STATIC_ASSETS_PATH", "../rd_ui/app/"))
 WORKERS_COUNT = int(os.environ.get("REDASH_WORKERS_COUNT", "2"))
+JOB_EXPIRY_TIME = int(os.environ.get("REDASH_JOB_EXPIRY_TIME", 3600*24))
 COOKIE_SECRET = os.environ.get("REDASH_COOKIE_SECRET", "c292a0a3aa32397cdb050e233733900f")
 LOG_LEVEL = os.environ.get("REDASH_LOG_LEVEL", "INFO")
 EVENTS_LOG_PATH = os.environ.get("REDASH_EVENTS_LOG_PATH", "")


Test patch:
diff --git a/tests/test_models.py b/tests/test_models.py
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -80,4 +80,14 @@ def test_get_latest_returns_the_most_recent_result(self):
 
         found_query_result = models.QueryResult.get_latest(qr.data_source, qr.query, 60)
 
+        self.assertEqual(found_query_result.id, qr.id)
+
+    def test_get_latest_returns_the_last_cached_result_for_negative_ttl(self):
+        yesterday = datetime.datetime.now() + datetime.timedelta(days=-100)
+        very_old = query_result_factory.create(retrieved_at=yesterday)
+
+        yesterday = datetime.datetime.now() + datetime.timedelta(days=-1)
+        qr = query_result_factory.create(retrieved_at=yesterday)
+        found_query_result = models.QueryResult.get_latest(qr.data_source, qr.query, -1)
+
         self.assertEqual(found_query_result.id, qr.id)
\ No newline at end of file