Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add: query snippets feature #1250

Merged
merged 1 commit into from
Aug 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions migrations/0025_add_notification_destination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from redash.models import db, QuerySnippet

if __name__ == '__main__':
with db.database.transaction():
if not QuerySnippet.table_exists():
QuerySnippet.create_table()

db.close_db(None)
1 change: 1 addition & 0 deletions rd_ui/app/app_layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ <h1><span class="zmdi zmdi-lock"></span></h1>
<script src="/scripts/controllers/query_view.js"></script>
<script src="/scripts/controllers/query_source.js"></script>
<script src="/scripts/controllers/users.js"></script>
<script src="/scripts/controllers/snippets.js"></script>
<script src="/scripts/visualizations/base.js"></script>
<script src="/scripts/visualizations/chart.js"></script>
<script src="/scripts/visualizations/cohort.js"></script>
Expand Down
10 changes: 9 additions & 1 deletion rd_ui/app/scripts/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,15 @@ angular.module('redash', [
$routeProvider.when('/groups', {
templateUrl: '/views/groups/list.html',
controller: 'GroupsCtrl'
})
});
$routeProvider.when('/query_snippets/:snippetId', {
templateUrl: '/views/query_snippets/show.html',
controller: 'SnippetCtrl'
});
$routeProvider.when('/query_snippets', {
templateUrl: '/views/query_snippets/list.html',
controller: 'SnippetsCtrl'
});
$routeProvider.when('/', {
templateUrl: '/views/index.html',
controller: 'IndexCtrl'
Expand Down
93 changes: 93 additions & 0 deletions rd_ui/app/scripts/controllers/snippets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
(function() {
var SnippetsCtrl = function ($scope, $location, growl, Events, QuerySnippet) {
Events.record(currentUser, "view", "page", "query_snippets");
$scope.$parent.pageTitle = "Query Snippets";

$scope.gridConfig = {
isPaginationEnabled: true,
itemsByPage: 20,
maxSize: 8,
};

$scope.gridColumns = [
{
"label": "Trigger",
"cellTemplate": '<a href="query_snippets/{{dataRow.id}}">{{dataRow.trigger}}</a>'
},
{
"label": "Description",
"map": "description"
},
{
"label": "Snippet",
"map": "snippet"
},
{
'label': 'Created By',
'map': 'user.name'
},
{
'label': 'Updated At',
'cellTemplate': '<span am-time-ago="dataRow.created_at"></span>'
}
];

$scope.snippets = [];
QuerySnippet.query(function(snippets) {
$scope.snippets = snippets;
});
};

var SnippetCtrl = function ($scope, $routeParams, $http, $location, growl, Events, QuerySnippet) {
$scope.$parent.pageTitle = "Query Snippets";
$scope.snippetId = $routeParams.snippetId;
Events.record(currentUser, "view", "query_snippet", $scope.snippetId);

$scope.editorOptions = {
mode: 'snippets',
advanced: {
behavioursEnabled: true,
enableSnippets: false,
autoScrollEditorIntoView: true,
},
onLoad: function(editor) {
editor.$blockScrolling = Infinity;
editor.getSession().setUseWrapMode(true);
editor.setShowPrintMargin(false);
}
};

$scope.saveChanges = function() {
$scope.snippet.$save(function(snippet) {
growl.addSuccessMessage("Saved.");
if ($scope.snippetId === "new") {
$location.path('/query_snippets/' + snippet.id).replace();
}
}, function() {
growl.addErrorMessage("Failed saving snippet.");
});
}

$scope.delete = function() {
$scope.snippet.$delete(function() {
$location.path('/query_snippets');
growl.addSuccessMessage("Query snippet deleted.");
}, function() {
growl.addErrorMessage("Failed deleting query snippet.");
});
}

if ($scope.snippetId == 'new') {
$scope.snippet = new QuerySnippet({description: ""});
$scope.canEdit = true;
} else {
$scope.snippet = QuerySnippet.get({id: $scope.snippetId}, function(snippet) {
$scope.canEdit = currentUser.canEdit(snippet);
});
}
};

angular.module('redash.controllers')
.controller('SnippetsCtrl', ['$scope', '$location', 'growl', 'Events', 'QuerySnippet', SnippetsCtrl])
.controller('SnippetCtrl', ['$scope', '$routeParams', '$http', '$location', 'growl', 'Events', 'QuerySnippet', SnippetCtrl])
})();
1 change: 1 addition & 0 deletions rd_ui/app/scripts/directives/directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@
scope.groupsPage = _.string.startsWith($location.path(), '/groups');
scope.dsPage = _.string.startsWith($location.path(), '/data_sources');
scope.destinationsPage = _.string.startsWith($location.path(), '/destinations');
scope.snippetsPage = _.string.startsWith($location.path(), '/query_snippets');

scope.showGroupsLink = currentUser.hasPermission('list_users');
scope.showUsersLink = currentUser.hasPermission('list_users');
Expand Down
18 changes: 15 additions & 3 deletions rd_ui/app/scripts/directives/query_directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
defineDummySnippets("sql");
defineDummySnippets("json");

function queryEditor() {
function queryEditor(QuerySnippet) {
return {
restrict: 'E',
scope: {
Expand All @@ -100,7 +100,19 @@
autoScrollEditorIntoView: true,
},
onLoad: function(editor) {
// Test for snippet manager
QuerySnippet.query(function(snippets) {
var snippetManager = ace.require("ace/snippets").snippetManager;
var m = {
snippetText: ''
};
m.snippets = snippetManager.parseSnippetFile(m.snippetText);
_.each(snippets, function(snippet) {
m.snippets.push(snippet.getSnippet());
});

snippetManager.register(m.snippets || [], m.scope);
});

editor.$blockScrolling = Infinity;
editor.getSession().setUseWrapMode(true);
editor.setShowPrintMargin(false);
Expand Down Expand Up @@ -314,7 +326,7 @@
.directive('queryLink', queryLink)
.directive('querySourceLink', ['$location', querySourceLink])
.directive('queryResultLink', queryResultLink)
.directive('queryEditor', queryEditor)
.directive('queryEditor', ['QuerySnippet', queryEditor])
.directive('queryRefreshSelect', queryRefreshSelect)
.directive('queryTimePicker', queryTimePicker)
.directive('queryFormatter', ['$http', 'growl', queryFormatter]);
Expand Down
21 changes: 20 additions & 1 deletion rd_ui/app/scripts/services/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,24 @@
return resource;
};

var QuerySnippet = function ($resource) {
var resource = $resource('api/query_snippets/:id', {id: '@id'});
resource.prototype.getSnippet = function() {
var name = this.trigger;
if (this.description !== "") {
name = this.trigger + ": " + this.description;
}

return {
"name": name,
"content": this.snippet,
"tabTrigger": this.trigger
};
}

return resource;
};

var Widget = function ($resource, Query) {
var WidgetResource = $resource('api/widgets/:id', {id: '@id'});

Expand Down Expand Up @@ -785,5 +803,6 @@
.factory('AlertSubscription', ['$resource', AlertSubscription])
.factory('Widget', ['$resource', 'Query', Widget])
.factory('User', ['$resource', '$http', User])
.factory('Group', ['$resource', Group]);
.factory('Group', ['$resource', Group])
.factory('QuerySnippet', ['$resource', QuerySnippet]);
})();
1 change: 1 addition & 0 deletions rd_ui/app/vendor_scripts.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<script src="/bower_components/ace-builds/src-min-noconflict/mode-sql.js"></script>
<script src="/bower_components/ace-builds/src-min-noconflict/mode-json.js"></script>
<script src="/bower_components/ace-builds/src-min-noconflict/mode-python.js"></script>
<script src="/bower_components/ace-builds/src-min-noconflict/mode-snippets.js"></script>
<script src="/bower_components/ace-builds/src-min-noconflict/ext-language_tools.js"></script>
<script src="/bower_components/angular/angular.js"></script>
<script src="/bower_components/angular-sanitize/angular-sanitize.js"></script>
Expand Down
2 changes: 1 addition & 1 deletion rd_ui/app/views/app_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<a href="data_sources" title="Data Sources"><i class="fa fa-database"></i></a>
</li>
<li ng-show="currentUser.hasPermission('list_users')">
<a href="users" title="Users"><i class="fa fa-users"></i></a>
<a href="users" title="Settings"><i class="fa fa-cog"></i></a>
</li>
<li class="dropdown" dropdown>
<a href="#" class="dropdown-toggle" dropdown-toggle><span ng-bind="currentUser.name"></span> <span
Expand Down
1 change: 1 addition & 0 deletions rd_ui/app/views/directives/settings_screen.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<li ng-class="{'active': usersPage }" ng-if="showUsersLink"><a href="users">Users</a></li>
<li ng-class="{'active': groupsPage }" ng-if="showGroupsLink"><a href="groups">Groups</a></li>
<li ng-class="{'active': destinationsPage }" ng-if="showDestinationsLink"><a href="destinations">Alert Destinations</a></li>
<li ng-class="{'active': snippetsPage }"><a href="query_snippets">Query Snippets</a></li>
</ul>

<div ng-transclude>
Expand Down
13 changes: 13 additions & 0 deletions rd_ui/app/views/query_snippets/list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<settings-screen>
<div class="row voffset1">
<div class="col-md-12">
<p>
<a href="query_snippets/new" class="btn btn-default"><i class="fa fa-plus"></i> New Snippet</a>
</p>

<smart-table rows="snippets" columns="gridColumns"
config="gridConfig"
class="table table-condensed table-hover"></smart-table>
</div>
</div>
</settings-screen>
36 changes: 36 additions & 0 deletions rd_ui/app/views/query_snippets/show.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<settings-screen>
<!--<h2 class="p-l-5">{{snippet.trigger}}</h2>-->

<div class="">
<!--<pre>-->
<!--{{snippet | json}}-->
<!--</pre>-->

<form name="snippetForm" class="form">
<div class="form-group">
<label>Trigger</label>
<input type="string" class="form-control" ng-model="snippet.trigger" ng-disabled="!canEdit" required>
</div>

<div class="form-group">
<label>Description</label>
<input type="string" class="form-control" ng-model="snippet.description" ng-disabled="!canEdit">
</div>

<div class="form-group">
<label>Snippet</label>
<pre ng-if="!canEdit">{{snippet.snippet}}</pre>
<div ui-ace="editorOptions" ng-model="snippet.snippet" style="height:300px" ng-if="canEdit"></div>
</div>

<div class="form-group" ng-if="canEdit">
<button class="btn btn-primary" ng-disabled="!snippetForm.$valid" ng-click="saveChanges()">Save</button>
<button class="btn btn-danger" ng-if="snippet.id" ng-click="delete()">Delete</button>
</div>
<small ng-if="snippet.user">
Created by: {{snippet.user.name}}
</small>
</form>

</div>
</settings-screen>
4 changes: 4 additions & 0 deletions redash/handlers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from redash.handlers.groups import GroupListResource, GroupResource, GroupMemberListResource, GroupMemberResource, \
GroupDataSourceListResource, GroupDataSourceResource
from redash.handlers.destinations import DestinationTypeListResource, DestinationResource, DestinationListResource
from redash.handlers.query_snippets import QuerySnippetListResource, QuerySnippetResource


class ApiExt(Api):
Expand Down Expand Up @@ -90,3 +91,6 @@ def json_representation(data, code, headers=None):
api.add_org_resource(DestinationTypeListResource, '/api/destinations/types', endpoint='destination_types')
api.add_org_resource(DestinationResource, '/api/destinations/<destination_id>', endpoint='destination')
api.add_org_resource(DestinationListResource, '/api/destinations', endpoint='destinations')

api.add_org_resource(QuerySnippetResource, '/api/query_snippets/<snippet_id>', endpoint='query_snippet')
api.add_org_resource(QuerySnippetListResource, '/api/query_snippets', endpoint='query_snippets')
64 changes: 64 additions & 0 deletions redash/handlers/query_snippets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from flask import request
from funcy import project

from redash import models
from redash.permissions import require_admin_or_owner
from redash.handlers.base import BaseResource, require_fields, get_object_or_404


class QuerySnippetResource(BaseResource):
def get(self, snippet_id):
snippet = get_object_or_404(models.QuerySnippet.get_by_id_and_org, snippet_id, self.current_org)
return snippet.to_dict()

def post(self, snippet_id):
req = request.get_json(True)
params = project(req, ('trigger', 'description', 'snippet'))
snippet = get_object_or_404(models.QuerySnippet.get_by_id_and_org, snippet_id, self.current_org)
require_admin_or_owner(snippet.user.id)

snippet.update_instance(**params)

self.record_event({
'action': 'edit',
'object_id': snippet.id,
'object_type': 'query_snippet'
})

return snippet.to_dict()

def delete(self, snippet_id):
snippet = get_object_or_404(models.QuerySnippet.get_by_id_and_org, snippet_id, self.current_org)
require_admin_or_owner(snippet.user.id)
snippet.delete_instance()

self.record_event({
'action': 'delete',
'object_id': snippet.id,
'object_type': 'query_snippet'
})


class QuerySnippetListResource(BaseResource):
def post(self):
req = request.get_json(True)
require_fields(req, ('trigger', 'description', 'snippet'))

snippet = models.QuerySnippet.create(
trigger=req['trigger'],
description=req['description'],
snippet=req['snippet'],
user=self.current_user,
org=self.current_org
)

self.record_event({
'action': 'create',
'object_id': snippet.id,
'object_type': 'query_snippet'
})

return snippet.to_dict()

def get(self):
return [snippet.to_dict() for snippet in models.QuerySnippet.all(org=self.current_org)]
2 changes: 2 additions & 0 deletions redash/handlers/static.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def register_static_routes(rules):
'/users/<pk>',
'/destinations',
'/destinations/<pk>',
'/query_snippets',
'/query_snippets/<pk>',
'/groups',
'/groups/<pk>',
'/groups/<pk>/data_sources',
Expand Down
Loading