mirror of
https://github.com/getredash/redash.git
synced 2026-05-08 09:01:12 -04:00
Merge pull request #1256 from ninneko/fork_with_vis
Change: when forking a query, copy all visualizations
This commit is contained in:
@@ -14,7 +14,8 @@
|
||||
var isNewQuery = !$scope.query.id,
|
||||
queryText = $scope.query.query,
|
||||
// ref to QueryViewCtrl.saveQuery
|
||||
saveQuery = $scope.saveQuery;
|
||||
saveQuery = $scope.saveQuery,
|
||||
forkQuery = $scope.forkQuery;
|
||||
|
||||
$scope.sourceMode = true;
|
||||
$scope.canEdit = currentUser.canEdit($scope.query) || $scope.query.can_edit;// TODO: bring this back? || clientConfig.allowAllToEditQueries;
|
||||
@@ -77,12 +78,23 @@
|
||||
return savePromise;
|
||||
};
|
||||
|
||||
$scope.forkQuery = function(options, data) {
|
||||
var savePromise = forkQuery(options, data);
|
||||
|
||||
if (!savePromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
savePromise.then(function(savedQuery) {
|
||||
queryText = savedQuery.query;
|
||||
});
|
||||
|
||||
return savePromise;
|
||||
};
|
||||
|
||||
$scope.duplicateQuery = function() {
|
||||
Events.record(currentUser, 'fork', 'query', $scope.query.id);
|
||||
$scope.query.name = 'Copy of (#'+$scope.query.id+') '+$scope.query.name;
|
||||
$scope.query.id = null;
|
||||
$scope.query.schedule = null;
|
||||
$scope.saveQuery({
|
||||
$scope.forkQuery({
|
||||
successMessage: 'Query forked',
|
||||
errorMessage: 'Query could not be forked'
|
||||
}).then(function redirect(savedQuery) {
|
||||
|
||||
@@ -151,7 +151,15 @@
|
||||
growl.addErrorMessage(options.errorMessage);
|
||||
}
|
||||
}).$promise;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.forkQuery = function(options, data) {
|
||||
return Query.fork({id:$scope.query.id}, function() {
|
||||
growl.addSuccessMessage(options.successMessage);
|
||||
}, function(httpResponse) {
|
||||
growl.addErrorMessage(options.errorMessage);
|
||||
}).$promise;
|
||||
};
|
||||
|
||||
$scope.saveDescription = function() {
|
||||
Events.record(currentUser, 'edit_description', 'query', $scope.query.id);
|
||||
|
||||
@@ -454,6 +454,12 @@
|
||||
method: 'get',
|
||||
isArray: false,
|
||||
url: "api/queries/my"
|
||||
},
|
||||
fork: {
|
||||
method: 'post',
|
||||
isArray: false,
|
||||
url: "api/queries/:id/fork",
|
||||
params: {id: '@id'}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from redash.handlers.alerts import AlertResource, AlertListResource, AlertSubscr
|
||||
from redash.handlers.dashboards import DashboardListResource, RecentDashboardsResource, DashboardResource, DashboardShareResource
|
||||
from redash.handlers.data_sources import DataSourceTypeListResource, DataSourceListResource, DataSourceSchemaResource, DataSourceResource, DataSourcePauseResource, DataSourceTestResource
|
||||
from redash.handlers.events import EventResource
|
||||
from redash.handlers.queries import QueryRefreshResource, QueryListResource, QueryRecentResource, QuerySearchResource, QueryResource, MyQueriesResource
|
||||
from redash.handlers.queries import 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
|
||||
from redash.handlers.visualizations import VisualizationListResource
|
||||
@@ -71,6 +71,7 @@ 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/<query_id>/refresh', endpoint='query_refresh')
|
||||
api.add_org_resource(QueryResource, '/api/queries/<query_id>', endpoint='query')
|
||||
api.add_org_resource(QueryForkResource, '/api/queries/<query_id>/fork', endpoint='query_fork')
|
||||
|
||||
api.add_org_resource(ObjectPermissionsListResource, '/api/<object_type>/<object_id>/acl', endpoint='object_permissions')
|
||||
api.add_org_resource(CheckPermissionResource, '/api/<object_type>/<object_id>/acl/<access_type>', endpoint='check_permissions')
|
||||
|
||||
@@ -139,6 +139,14 @@ class QueryResource(BaseResource):
|
||||
query.archive(self.current_user)
|
||||
|
||||
|
||||
class QueryForkResource(BaseResource):
|
||||
@require_permission('edit_query')
|
||||
def post(self, query_id):
|
||||
query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
|
||||
forked_query = query.fork(self.current_user)
|
||||
return forked_query.to_dict(with_visualizations=True)
|
||||
|
||||
|
||||
class QueryRefreshResource(BaseResource):
|
||||
def post(self, query_id):
|
||||
query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org)
|
||||
|
||||
@@ -843,6 +843,31 @@ class Query(ChangeTrackingMixin, ModelTimestampsMixin, BaseVersionedModel, Belon
|
||||
|
||||
return query
|
||||
|
||||
def fork(self, user):
|
||||
query = self
|
||||
forked_query = Query()
|
||||
forked_query.name = 'Copy of (#{}) {}'.format(query.id, query.name)
|
||||
forked_query.user = user
|
||||
forked_list = ['org', 'data_source', 'latest_query_data', 'description', 'query', 'query_hash']
|
||||
for a in forked_list:
|
||||
setattr(forked_query, a, getattr(query, a))
|
||||
forked_query.save()
|
||||
|
||||
forked_visualizations = []
|
||||
for v in query.visualizations:
|
||||
if v.type == 'TABLE':
|
||||
continue
|
||||
forked_v = v.to_dict()
|
||||
forked_v['options'] = v.options
|
||||
forked_v['query'] = forked_query
|
||||
forked_v.pop('id')
|
||||
forked_visualizations.append(forked_v)
|
||||
|
||||
if len(forked_visualizations) > 0:
|
||||
with db.database.atomic():
|
||||
Visualization.insert_many(forked_visualizations).execute()
|
||||
return forked_query
|
||||
|
||||
def pre_save(self, created):
|
||||
super(Query, self).pre_save(created)
|
||||
self.query_hash = utils.gen_query_hash(self.query)
|
||||
|
||||
@@ -1,5 +1,73 @@
|
||||
from tests import BaseTestCase
|
||||
from redash.models import Query
|
||||
|
||||
|
||||
# Add tests for change tracking
|
||||
class TestApiKeyGetByObject(BaseTestCase):
|
||||
|
||||
def assert_visualizations(self, origin_q, origin_v, forked_q, forked_v):
|
||||
self.assertEqual(origin_v.options, forked_v.options)
|
||||
self.assertEqual(origin_v.type, forked_v.type)
|
||||
self.assertNotEqual(origin_v.id, forked_v.id)
|
||||
self.assertNotEqual(origin_v.query, forked_v.query)
|
||||
self.assertEqual(forked_q.id, forked_v.query.id)
|
||||
|
||||
|
||||
def test_fork_with_visualizations(self):
|
||||
# prepare original query and visualizations
|
||||
data_source = self.factory.create_data_source(group=self.factory.create_group())
|
||||
query = self.factory.create_query(data_source=data_source, description="this is description")
|
||||
visualization_chart = self.factory.create_visualization(query=query, description="chart vis", type="CHART", options="""{"yAxis": [{"type": "linear"}, {"type": "linear", "opposite": true}], "series": {"stacking": null}, "globalSeriesType": "line", "sortX": true, "seriesOptions": {"count": {"zIndex": 0, "index": 0, "type": "line", "yAxis": 0}}, "xAxis": {"labels": {"enabled": true}, "type": "datetime"}, "columnMapping": {"count": "y", "created_at": "x"}, "bottomMargin": 50, "legend": {"enabled": true}}""")
|
||||
visualization_box = self.factory.create_visualization(query=query, description="box vis", type="BOXPLOT", options="{}")
|
||||
fork_user = self.factory.create_user()
|
||||
|
||||
forked_query = query.fork(fork_user)
|
||||
|
||||
|
||||
forked_visualization_chart = None
|
||||
forked_visualization_box = None
|
||||
forked_table = None
|
||||
count_table = 0
|
||||
for v in forked_query.visualizations:
|
||||
if v.description == "chart vis":
|
||||
forked_visualization_chart = v
|
||||
if v.description == "box vis":
|
||||
forked_visualization_box = v
|
||||
if v.type == "TABLE":
|
||||
count_table += 1
|
||||
forked_table = v
|
||||
self.assert_visualizations(query, visualization_chart, forked_query, forked_visualization_chart)
|
||||
self.assert_visualizations(query, visualization_box, forked_query, forked_visualization_box)
|
||||
|
||||
self.assertEqual(forked_query.org, query.org)
|
||||
self.assertEqual(forked_query.data_source, query.data_source)
|
||||
self.assertEqual(forked_query.latest_query_data, query.latest_query_data)
|
||||
self.assertEqual(forked_query.description, query.description)
|
||||
self.assertEqual(forked_query.query, query.query)
|
||||
self.assertEqual(forked_query.query_hash, query.query_hash)
|
||||
self.assertEqual(forked_query.user, fork_user)
|
||||
self.assertEqual(forked_query.description, query.description)
|
||||
self.assertTrue(forked_query.name.startswith('Copy'))
|
||||
# num of TABLE must be 1. default table only
|
||||
self.assertEqual(count_table, 1)
|
||||
self.assertEqual(forked_table.name, "Table")
|
||||
self.assertEqual(forked_table.description, "")
|
||||
self.assertEqual(forked_table.options, "{}")
|
||||
|
||||
def test_fork_from_query_that_has_no_visualization(self):
|
||||
# prepare original query and visualizations
|
||||
data_source = self.factory.create_data_source(group=self.factory.create_group())
|
||||
query = self.factory.create_query(data_source=data_source, description="this is description")
|
||||
fork_user = self.factory.create_user()
|
||||
|
||||
forked_query = query.fork(fork_user)
|
||||
|
||||
count_table = 0
|
||||
count_vis = 0
|
||||
for v in forked_query.visualizations:
|
||||
count_vis += 1
|
||||
if v.type == "TABLE":
|
||||
count_table += 1
|
||||
|
||||
self.assertEqual(count_table, 1)
|
||||
self.assertEqual(count_vis, 1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user