Repository: getredash/redash

Issue:
Queries search should be case insensitive



Discussion:

Resolving patch:
diff --git a/redash/models.py b/redash/models.py
--- a/redash/models.py
+++ b/redash/models.py
@@ -789,8 +789,8 @@ def outdated_queries(cls):
     @classmethod
     def search(cls, term, group_ids):
         # TODO: This is very naive implementation of search, to be replaced with PostgreSQL full-text-search solution.
-        where = (Query.name.like(u"%{}%".format(term)) |
-                 Query.description.like(u"%{}%".format(term)))
+        where = (Query.name.ilike(u"%{}%".format(term)) |
+                 Query.description.ilike(u"%{}%".format(term)))
 
         if term.isdigit():
             where |= Query.id == term


Test patch:
diff --git a/tests/models/test_queries.py b/tests/models/test_queries.py
--- a/tests/models/test_queries.py
+++ b/tests/models/test_queries.py
@@ -1,5 +1,163 @@
+# encoding: utf8
+
 from tests import BaseTestCase
-from redash.models import Query, db
+import datetime
+from redash.models import Query, Group, Event, db
+from redash.utils import utcnow
+
+
+class QueryTest(BaseTestCase):
+    def test_changing_query_text_changes_hash(self):
+        q = self.factory.create_query()
+        old_hash = q.query_hash
+
+        q.query_text = "SELECT 2;"
+        db.session.flush()
+        self.assertNotEquals(old_hash, q.query_hash)
+
+    def test_search_finds_in_name(self):
+        q1 = self.factory.create_query(name=u"Testing seåřċħ")
+        q2 = self.factory.create_query(name=u"Testing seåřċħing")
+        q3 = self.factory.create_query(name=u"Testing seå řċħ")
+        queries = list(Query.search(u"seåřċħ", [self.factory.default_group.id]))
+
+        self.assertIn(q1, queries)
+        self.assertIn(q2, queries)
+        self.assertNotIn(q3, queries)
+
+    def test_search_finds_in_description(self):
+        q1 = self.factory.create_query(description=u"Testing seåřċħ")
+        q2 = self.factory.create_query(description=u"Testing seåřċħing")
+        q3 = self.factory.create_query(description=u"Testing seå řċħ")
+
+        queries = Query.search(u"seåřċħ", [self.factory.default_group.id])
+
+        self.assertIn(q1, queries)
+        self.assertIn(q2, queries)
+        self.assertNotIn(q3, queries)
+
+    def test_search_by_id_returns_query(self):
+        q1 = self.factory.create_query(description="Testing search")
+        q2 = self.factory.create_query(description="Testing searching")
+        q3 = self.factory.create_query(description="Testing sea rch")
+        db.session.flush()
+        queries = Query.search(str(q3.id), [self.factory.default_group.id])
+
+        self.assertIn(q3, queries)
+        self.assertNotIn(q1, queries)
+        self.assertNotIn(q2, queries)
+
+    def test_search_respects_groups(self):
+        other_group = Group(org=self.factory.org, name="Other Group")
+        db.session.add(other_group)
+        ds = self.factory.create_data_source(group=other_group)
+
+        q1 = self.factory.create_query(description="Testing search", data_source=ds)
+        q2 = self.factory.create_query(description="Testing searching")
+        q3 = self.factory.create_query(description="Testing sea rch")
+
+        queries = list(Query.search("Testing", [self.factory.default_group.id]))
+
+        self.assertNotIn(q1, queries)
+        self.assertIn(q2, queries)
+        self.assertIn(q3, queries)
+
+        queries = list(Query.search("Testing", [other_group.id, self.factory.default_group.id]))
+        self.assertIn(q1, queries)
+        self.assertIn(q2, queries)
+        self.assertIn(q3, queries)
+
+        queries = list(Query.search("Testing", [other_group.id]))
+        self.assertIn(q1, queries)
+        self.assertNotIn(q2, queries)
+        self.assertNotIn(q3, queries)
+
+    def test_returns_each_query_only_once(self):
+        other_group = self.factory.create_group()
+        second_group = self.factory.create_group()
+        ds = self.factory.create_data_source(group=other_group)
+        ds.add_group(second_group, False)
+
+        q1 = self.factory.create_query(description="Testing search", data_source=ds)
+        db.session.flush()
+        queries = list(Query.search("Testing", [self.factory.default_group.id, other_group.id, second_group.id]))
+
+        self.assertEqual(1, len(queries))
+
+    def test_save_updates_updated_at_field(self):
+        # This should be a test of ModelTimestampsMixin, but it's easier to test in context of existing model... :-\
+        one_day_ago = utcnow().date() - datetime.timedelta(days=1)
+        q = self.factory.create_query(created_at=one_day_ago, updated_at=one_day_ago)
+        db.session.flush()
+        q.name = 'x'
+        db.session.flush()
+        self.assertNotEqual(q.updated_at, one_day_ago)
+
+    def test_search_is_case_insensitive(self):
+        q = self.factory.create_query(name="Testing search")
+
+        self.assertIn(q, Query.search('testing', [self.factory.default_group.id]))
+
+
+class QueryRecentTest(BaseTestCase):
+    def test_global_recent(self):
+        q1 = self.factory.create_query()
+        q2 = self.factory.create_query()
+        db.session.flush()
+        e = Event(org=self.factory.org, user=self.factory.user, action="edit",
+                  object_type="query", object_id=q1.id)
+        db.session.add(e)
+        recent = Query.recent([self.factory.default_group.id])
+        self.assertIn(q1, recent)
+        self.assertNotIn(q2, recent)
+
+    def test_recent_excludes_drafts(self):
+        q1 = self.factory.create_query()
+        q2 = self.factory.create_query(is_draft=True)
+
+        db.session.add_all([
+            Event(org=self.factory.org, user=self.factory.user,
+                  action="edit", object_type="query",
+                  object_id=q1.id),
+            Event(org=self.factory.org, user=self.factory.user,
+                  action="edit", object_type="query",
+                  object_id=q2.id)
+        ])
+        recent = Query.recent([self.factory.default_group.id])
+
+        self.assertIn(q1, recent)
+        self.assertNotIn(q2, recent)
+
+    def test_recent_for_user(self):
+        q1 = self.factory.create_query()
+        q2 = self.factory.create_query()
+        db.session.flush()
+        e = Event(org=self.factory.org, user=self.factory.user, action="edit",
+                  object_type="query", object_id=q1.id)
+        db.session.add(e)
+        recent = Query.recent([self.factory.default_group.id], user_id=self.factory.user.id)
+
+        self.assertIn(q1, recent)
+        self.assertNotIn(q2, recent)
+
+        recent = Query.recent([self.factory.default_group.id], user_id=self.factory.user.id + 1)
+        self.assertNotIn(q1, recent)
+        self.assertNotIn(q2, recent)
+
+    def test_respects_groups(self):
+        q1 = self.factory.create_query()
+        ds = self.factory.create_data_source(group=self.factory.create_group())
+        q2 = self.factory.create_query(data_source=ds)
+        db.session.flush()
+        Event(org=self.factory.org, user=self.factory.user, action="edit",
+              object_type="query", object_id=q1.id)
+        Event(org=self.factory.org, user=self.factory.user, action="edit",
+              object_type="query", object_id=q2.id)
+
+        recent = Query.recent([self.factory.default_group.id])
+
+        self.assertIn(q1, recent)
+        self.assertNotIn(q2, recent)
 
 
 class TestQueryByUser(BaseTestCase):
@@ -42,7 +200,6 @@ def assert_visualizations(self, origin_q, origin_v, forked_q, forked_v):
         self.assertNotEqual(origin_v.query_rel, forked_v.query_rel)
         self.assertEqual(forked_q.id, forked_v.query_rel.id)
 
-
     def test_fork_with_visualizations(self):
         # prepare original query and visualizations
         data_source = self.factory.create_data_source(
diff --git a/tests/test_models.py b/tests/test_models.py
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -26,155 +26,6 @@ def test_appends_suffix_to_slug_when_duplicate(self):
         self.assertNotEquals(d2.slug, d3.slug)
 
 
-class QueryTest(BaseTestCase):
-    def test_changing_query_text_changes_hash(self):
-        q = self.factory.create_query()
-        old_hash = q.query_hash
-
-        q.query_text = "SELECT 2;"
-        db.session.flush()
-        self.assertNotEquals(old_hash, q.query_hash)
-
-    def test_search_finds_in_name(self):
-        q1 = self.factory.create_query(name=u"Testing seåřċħ")
-        q2 = self.factory.create_query(name=u"Testing seåřċħing")
-        q3 = self.factory.create_query(name=u"Testing seå řċħ")
-        queries = list(models.Query.search(u"seåřċħ", [self.factory.default_group.id]))
-
-        self.assertIn(q1, queries)
-        self.assertIn(q2, queries)
-        self.assertNotIn(q3, queries)
-
-    def test_search_finds_in_description(self):
-        q1 = self.factory.create_query(description=u"Testing seåřċħ")
-        q2 = self.factory.create_query(description=u"Testing seåřċħing")
-        q3 = self.factory.create_query(description=u"Testing seå řċħ")
-
-        queries = models.Query.search(u"seåřċħ", [self.factory.default_group.id])
-
-        self.assertIn(q1, queries)
-        self.assertIn(q2, queries)
-        self.assertNotIn(q3, queries)
-
-    def test_search_by_id_returns_query(self):
-        q1 = self.factory.create_query(description="Testing search")
-        q2 = self.factory.create_query(description="Testing searching")
-        q3 = self.factory.create_query(description="Testing sea rch")
-        db.session.flush()
-        queries = models.Query.search(str(q3.id), [self.factory.default_group.id])
-
-        self.assertIn(q3, queries)
-        self.assertNotIn(q1, queries)
-        self.assertNotIn(q2, queries)
-
-    def test_search_respects_groups(self):
-        other_group = models.Group(org=self.factory.org, name="Other Group")
-        db.session.add(other_group)
-        ds = self.factory.create_data_source(group=other_group)
-
-        q1 = self.factory.create_query(description="Testing search", data_source=ds)
-        q2 = self.factory.create_query(description="Testing searching")
-        q3 = self.factory.create_query(description="Testing sea rch")
-
-        queries = list(models.Query.search("Testing", [self.factory.default_group.id]))
-
-        self.assertNotIn(q1, queries)
-        self.assertIn(q2, queries)
-        self.assertIn(q3, queries)
-
-        queries = list(models.Query.search("Testing", [other_group.id, self.factory.default_group.id]))
-        self.assertIn(q1, queries)
-        self.assertIn(q2, queries)
-        self.assertIn(q3, queries)
-
-        queries = list(models.Query.search("Testing", [other_group.id]))
-        self.assertIn(q1, queries)
-        self.assertNotIn(q2, queries)
-        self.assertNotIn(q3, queries)
-
-    def test_returns_each_query_only_once(self):
-        other_group = self.factory.create_group()
-        second_group = self.factory.create_group()
-        ds = self.factory.create_data_source(group=other_group)
-        ds.add_group(second_group, False)
-
-        q1 = self.factory.create_query(description="Testing search", data_source=ds)
-        db.session.flush()
-        queries = list(models.Query.search("Testing", [self.factory.default_group.id, other_group.id, second_group.id]))
-
-        self.assertEqual(1, len(queries))
-
-    def test_save_updates_updated_at_field(self):
-        # This should be a test of ModelTimestampsMixin, but it's easier to test in context of existing model... :-\
-        one_day_ago = utcnow().date() - datetime.timedelta(days=1)
-        q = self.factory.create_query(created_at=one_day_ago, updated_at=one_day_ago)
-        db.session.flush()
-        q.name = 'x'
-        db.session.flush()
-        self.assertNotEqual(q.updated_at, one_day_ago)
-
-
-class QueryRecentTest(BaseTestCase):
-    def test_global_recent(self):
-        q1 = self.factory.create_query()
-        q2 = self.factory.create_query()
-        db.session.flush()
-        e = models.Event(org=self.factory.org, user=self.factory.user, action="edit",
-                         object_type="query", object_id=q1.id)
-        db.session.add(e)
-        recent = models.Query.recent([self.factory.default_group.id])
-        self.assertIn(q1, recent)
-        self.assertNotIn(q2, recent)
-
-    def test_recent_excludes_drafts(self):
-        q1 = self.factory.create_query()
-        q2 = self.factory.create_query(is_draft=True)
-
-        models.db.session.add_all([
-            models.Event(org=self.factory.org, user=self.factory.user,
-                         action="edit", object_type="query",
-                         object_id=q1.id),
-            models.Event(org=self.factory.org, user=self.factory.user,
-                         action="edit", object_type="query",
-                         object_id=q2.id)
-        ])
-        recent = models.Query.recent([self.factory.default_group.id])
-
-        self.assertIn(q1, recent)
-        self.assertNotIn(q2, recent)
-
-    def test_recent_for_user(self):
-        q1 = self.factory.create_query()
-        q2 = self.factory.create_query()
-        db.session.flush()
-        e = models.Event(org=self.factory.org, user=self.factory.user, action="edit",
-                         object_type="query", object_id=q1.id)
-        db.session.add(e)
-        recent = models.Query.recent([self.factory.default_group.id], user_id=self.factory.user.id)
-
-        self.assertIn(q1, recent)
-        self.assertNotIn(q2, recent)
-
-        recent = models.Query.recent([self.factory.default_group.id], user_id=self.factory.user.id + 1)
-        self.assertNotIn(q1, recent)
-        self.assertNotIn(q2, recent)
-
-    def test_respects_groups(self):
-        q1 = self.factory.create_query()
-        ds = self.factory.create_data_source(group=self.factory.create_group())
-        q2 = self.factory.create_query(data_source=ds)
-        db.session.flush()
-        models.Event(org=self.factory.org, user=self.factory.user, action="edit",
-                     object_type="query", object_id=q1.id)
-        models.Event(org=self.factory.org, user=self.factory.user, action="edit",
-                     object_type="query", object_id=q2.id)
-
-        recent = models.Query.recent([self.factory.default_group.id])
-
-        self.assertIn(q1, recent)
-        self.assertNotIn(q2, recent)
-
-
 class ShouldScheduleNextTest(TestCase):
     def test_interval_schedule_that_needs_reschedule(self):
         now = utcnow()