diff --git a/client/app/services/query.js b/client/app/services/query.js
index 57ece987dd..71d0dfacc5 100644
--- a/client/app/services/query.js
+++ b/client/app/services/query.js
@@ -334,6 +334,11 @@ function QueryResource(
isArray: true,
url: 'api/queries/recent',
},
+ archive: {
+ method: 'get',
+ isArray: false,
+ url: 'api/queries/archive',
+ },
query: {
isArray: false,
},
diff --git a/redash/handlers/api.py b/redash/handlers/api.py
index 140c37ce83..1fef2f5409 100644
--- a/redash/handlers/api.py
+++ b/redash/handlers/api.py
@@ -9,7 +9,7 @@
from redash.handlers.dashboards import DashboardListResource, DashboardResource, DashboardShareResource, PublicDashboardResource
from redash.handlers.data_sources import DataSourceTypeListResource, DataSourceListResource, DataSourceSchemaResource, DataSourceResource, DataSourcePauseResource, DataSourceTestResource
from redash.handlers.events import EventsResource
-from redash.handlers.queries import QueryForkResource, QueryRefreshResource, QueryListResource, QueryRecentResource, QuerySearchResource, QueryResource, MyQueriesResource
+from redash.handlers.queries import QueryArchiveResource, QueryForkResource, QueryRefreshResource, QueryListResource, QueryRecentResource, QuerySearchResource, QueryResource, MyQueriesResource
from redash.handlers.query_results import QueryResultListResource, QueryResultResource, JobResource
from redash.handlers.users import UserResource, UserListResource, UserInviteResource, UserResetPasswordResource, UserDisableResource, UserRegenerateApiKeyResource
from redash.handlers.visualizations import VisualizationListResource
@@ -79,6 +79,7 @@ def json_representation(data, code, headers=None):
api.add_org_resource(QuerySearchResource, '/api/queries/search', endpoint='queries_search')
api.add_org_resource(QueryRecentResource, '/api/queries/recent', endpoint='recent_queries')
+api.add_org_resource(QueryArchiveResource, '/api/queries/archive', endpoint='queries_archive')
api.add_org_resource(QueryListResource, '/api/queries', endpoint='queries')
api.add_org_resource(MyQueriesResource, '/api/queries/my', endpoint='my_queries')
api.add_org_resource(QueryRefreshResource, '/api/queries//refresh', endpoint='query_refresh')
diff --git a/redash/handlers/organization.py b/redash/handlers/organization.py
index 384f4018db..464943a065 100644
--- a/redash/handlers/organization.py
+++ b/redash/handlers/organization.py
@@ -13,7 +13,7 @@ def organization_status(org_slug=None):
'users': models.User.all(current_org).count(),
'alerts': models.Alert.all(group_ids=current_user.group_ids).count(),
'data_sources': models.DataSource.all(current_org, group_ids=current_user.group_ids).count(),
- 'queries': models.Query.all_queries(current_user.group_ids, current_user.id, drafts=True).count(),
+ 'queries': models.Query.all_queries(current_user.group_ids, current_user.id, include_drafts=True).count(),
'dashboards': models.Dashboard.query.filter(models.Dashboard.org==current_org, models.Dashboard.is_archived==False).count(),
}
diff --git a/redash/handlers/queries.py b/redash/handlers/queries.py
index 7f33a52843..bd43c69576 100644
--- a/redash/handlers/queries.py
+++ b/redash/handlers/queries.py
@@ -102,7 +102,76 @@ def get(self):
return QuerySerializer(results, with_last_modified_by=False, with_user=False).serialize()
-class QueryListResource(BaseResource):
+class BaseQueryListResource(BaseResource):
+
+ def get_queries(self, search_term):
+ if search_term:
+ results = models.Query.search(
+ search_term,
+ self.current_user.group_ids,
+ self.current_user.id,
+ include_drafts=True,
+ )
+ else:
+ results = models.Query.all_queries(
+ self.current_user.group_ids,
+ self.current_user.id,
+ include_drafts=True,
+ )
+ return filter_by_tags(results, models.Query.tags)
+
+ @require_permission('view_query')
+ def get(self):
+ """
+ Retrieve a list of queries.
+
+ :qparam number page_size: Number of queries to return per page
+ :qparam number page: Page number to retrieve
+ :qparam number order: Name of column to order by
+ :qparam number q: Full text search term
+
+ Responds with an array of :ref:`query ` objects.
+ """
+ # See if we want to do full-text search or just regular queries
+ search_term = request.args.get('q', '')
+
+ queries = self.get_queries(search_term)
+
+ results = filter_by_tags(queries, models.Query.tags)
+
+ # order results according to passed order parameter,
+ # special-casing search queries where the database
+ # provides an order by search rank
+ ordered_results = order_results(results, fallback=bool(search_term))
+
+ page = request.args.get('page', 1, type=int)
+ page_size = request.args.get('page_size', 25, type=int)
+
+ response = paginate(
+ ordered_results,
+ page=page,
+ page_size=page_size,
+ serializer=QuerySerializer,
+ with_stats=True,
+ with_last_modified_by=False
+ )
+
+ if search_term:
+ self.record_event({
+ 'action': 'search',
+ 'object_type': 'query',
+ 'term': search_term,
+ })
+ else:
+ self.record_event({
+ 'action': 'list',
+ 'object_type': 'query',
+ })
+
+ return response
+
+
+class QueryListResource(BaseQueryListResource):
@require_permission('create_query')
def post(self):
"""
@@ -161,68 +230,26 @@ def post(self):
return QuerySerializer(query).serialize()
- @require_permission('view_query')
- def get(self):
- """
- Retrieve a list of queries.
-
- :qparam number page_size: Number of queries to return per page
- :qparam number page: Page number to retrieve
- :qparam number order: Name of column to order by
- :qparam number q: Full text search term
- Responds with an array of :ref:`query ` objects.
- """
- # See if we want to do full-text search or just regular queries
- search_term = request.args.get('q', '')
+class QueryArchiveResource(BaseQueryListResource):
+ def get_queries(self, search_term):
if search_term:
- results = models.Query.search(
+ return models.Query.search(
search_term,
self.current_user.group_ids,
self.current_user.id,
- include_drafts=True,
+ include_drafts=False,
+ include_archived=True,
)
else:
- results = models.Query.all_queries(
+ return models.Query.all_queries(
self.current_user.group_ids,
self.current_user.id,
- drafts=True,
+ include_drafts=False,
+ include_archived=True,
)
- results = filter_by_tags(results, models.Query.tags)
-
- # order results according to passed order parameter,
- # special-casing search queries where the database
- # provides an order by search rank
- ordered_results = order_results(results, fallback=bool(search_term))
-
- page = request.args.get('page', 1, type=int)
- page_size = request.args.get('page_size', 25, type=int)
-
- response = paginate(
- ordered_results,
- page=page,
- page_size=page_size,
- serializer=QuerySerializer,
- with_stats=True,
- with_last_modified_by=False
- )
-
- if search_term:
- self.record_event({
- 'action': 'search',
- 'object_type': 'query',
- 'term': search_term,
- })
- else:
- self.record_event({
- 'action': 'list',
- 'object_type': 'query',
- })
-
- return response
-
class MyQueriesResource(BaseResource):
@require_permission('view_query')
diff --git a/redash/models/__init__.py b/redash/models/__init__.py
index 73ff622bd3..8f9f189181 100644
--- a/redash/models/__init__.py
+++ b/redash/models/__init__.py
@@ -466,7 +466,7 @@ def create(cls, **kwargs):
return query
@classmethod
- def all_queries(cls, group_ids, user_id=None, drafts=False):
+ def all_queries(cls, group_ids, user_id=None, include_drafts=False, include_archived=False):
query_ids = (
db.session
.query(distinct(cls.id))
@@ -474,10 +474,10 @@ def all_queries(cls, group_ids, user_id=None, drafts=False):
DataSourceGroup,
Query.data_source_id == DataSourceGroup.data_source_id
)
- .filter(Query.is_archived == False)
+ .filter(Query.is_archived.is_(include_archived))
.filter(DataSourceGroup.group_id.in_(group_ids))
)
- q = (
+ queries = (
cls
.query
.options(
@@ -503,19 +503,19 @@ def all_queries(cls, group_ids, user_id=None, drafts=False):
.order_by(Query.created_at.desc())
)
- if not drafts:
- q = q.filter(
+ if not include_drafts:
+ queries = queries.filter(
or_(
- Query.is_draft == False,
+ Query.is_draft.is_(False),
Query.user_id == user_id
)
)
- return q
+ return queries
@classmethod
def favorites(cls, user, base_query=None):
if base_query is None:
- base_query = cls.all_queries(user.group_ids, user.id, drafts=True)
+ base_query = cls.all_queries(user.group_ids, user.id, include_drafts=True)
return base_query.join((
Favorite,
and_(
@@ -529,7 +529,7 @@ def all_tags(cls, user, include_drafts=False):
queries = cls.all_queries(
group_ids=user.group_ids,
user_id=user.id,
- drafts=include_drafts,
+ include_drafts=include_drafts,
)
tag_column = func.unnest(cls.tags).label('tag')
@@ -550,11 +550,13 @@ def by_user(cls, user):
@classmethod
def outdated_queries(cls):
- queries = (Query.query
- .options(joinedload(Query.latest_query_data).load_only('retrieved_at'))
- .filter(Query.schedule.isnot(None))
- .order_by(Query.id))
-
+ queries = (
+ Query.query
+ .options(joinedload(Query.latest_query_data).load_only('retrieved_at'))
+ .filter(Query.schedule.isnot(None))
+ .order_by(Query.id)
+ )
+
now = utils.utcnow()
outdated_queries = {}
scheduled_queries_executions.refresh()
@@ -582,8 +584,14 @@ def outdated_queries(cls):
return outdated_queries.values()
@classmethod
- def search(cls, term, group_ids, user_id=None, include_drafts=False, limit=None):
- all_queries = cls.all_queries(group_ids, user_id=user_id, drafts=include_drafts)
+ def search(cls, term, group_ids, user_id=None, include_drafts=False,
+ limit=None, include_archived=False):
+ all_queries = cls.all_queries(
+ group_ids,
+ user_id=user_id,
+ include_drafts=include_drafts,
+ include_archived=include_archived,
+ )
# sort the result using the weight as defined in the search vector column
return all_queries.search(term, sort=True).limit(limit)
diff --git a/tests/handlers/test_queries.py b/tests/handlers/test_queries.py
index d4219365e2..de587d6ed4 100644
--- a/tests/handlers/test_queries.py
+++ b/tests/handlers/test_queries.py
@@ -134,6 +134,7 @@ def test_works_for_non_owner_with_permission(self):
self.assertEqual(rv.json['name'], 'Testing')
self.assertEqual(rv.json['last_modified_by']['id'], user.id)
+
class TestQueryListResourceGet(BaseTestCase):
def test_returns_queries(self):
q1 = self.factory.create_query()
@@ -147,8 +148,8 @@ def test_returns_queries(self):
def test_filters_with_tags(self):
q1 = self.factory.create_query(tags=[u'test'])
- q2 = self.factory.create_query()
- q3 = self.factory.create_query()
+ self.factory.create_query()
+ self.factory.create_query()
rv = self.make_request('get', '/api/queries?tags=test')
assert len(rv.json['results']) == 1
@@ -157,12 +158,13 @@ def test_filters_with_tags(self):
def test_search_term(self):
q1 = self.factory.create_query(name="Sales")
q2 = self.factory.create_query(name="Q1 sales")
- q3 = self.factory.create_query(name="Ops")
+ self.factory.create_query(name="Ops")
rv = self.make_request('get', '/api/queries?q=sales')
assert len(rv.json['results']) == 2
assert set(map(lambda d: d['id'], rv.json['results'])) == set([q1.id, q2.id])
+
class TestQueryListResourcePost(BaseTestCase):
def test_create_query(self):
query_data = {
@@ -185,6 +187,27 @@ def test_create_query(self):
self.assertTrue(query.is_draft)
+class TestQueryArchiveResourceGet(BaseTestCase):
+ def test_returns_queries(self):
+ q1 = self.factory.create_query(is_archived=True)
+ q2 = self.factory.create_query(is_archived=True)
+ self.factory.create_query()
+
+ rv = self.make_request('get', '/api/queries/archive')
+
+ assert len(rv.json['results']) == 2
+ assert set(map(lambda d: d['id'], rv.json['results'])) == set([q1.id, q2.id])
+
+ def test_search_term(self):
+ q1 = self.factory.create_query(name="Sales", is_archived=True)
+ q2 = self.factory.create_query(name="Q1 sales", is_archived=True)
+ self.factory.create_query(name="Q2 sales")
+
+ rv = self.make_request('get', '/api/queries/archive?q=sales')
+ assert len(rv.json['results']) == 2
+ assert set(map(lambda d: d['id'], rv.json['results'])) == set([q1.id, q2.id])
+
+
class QueryRefreshTest(BaseTestCase):
def setUp(self):
super(QueryRefreshTest, self).setUp()