Skip to content

Commit

Permalink
Merge pull request #399 from EverythingMe/feature/schema
Browse files Browse the repository at this point in the history
Feature: schema browser and simple autocomplete
  • Loading branch information
arikfr committed Apr 2, 2015
2 parents 267c32b + e3c5da5 commit 9fb33cf
Show file tree
Hide file tree
Showing 19 changed files with 292 additions and 36 deletions.
4 changes: 3 additions & 1 deletion rd_ui/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<link rel="stylesheet" href="/bower_components/angular-ui-select/dist/select.css">
<link rel="stylesheet" href="/bower_components/pace/themes/pace-theme-minimal.css">
<link rel="stylesheet" href="/bower_components/font-awesome/css/font-awesome.css">
<link rel="stylesheet" href="/bower_components/codemirror/addon/hint/show-hint.css">
<link rel="stylesheet" href="/styles/redash.css">
<!-- endbuild -->
</head>
Expand Down Expand Up @@ -105,9 +106,10 @@
<script src="/bower_components/codemirror/lib/codemirror.js"></script>
<script src="/bower_components/codemirror/addon/edit/matchbrackets.js"></script>
<script src="/bower_components/codemirror/addon/edit/closebrackets.js"></script>
<script src="/bower_components/codemirror/addon/hint/show-hint.js"></script>
<script src="/bower_components/codemirror/addon/hint/anyword-hint.js"></script>
<script src="/bower_components/codemirror/mode/sql/sql.js"></script>
<script src="/bower_components/codemirror/mode/javascript/javascript.js"></script>
<script src="/bower_components/angular-ui-codemirror/ui-codemirror.js"></script>
<script src="/bower_components/highcharts/highcharts.js"></script>
<script src="/bower_components/highcharts/modules/exporting.js"></script>
<script src="/bower_components/gridster/dist/jquery.gridster.js"></script>
Expand Down
1 change: 0 additions & 1 deletion rd_ui/app/scripts/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ angular.module('redash', [
'redash.services',
'redash.renderers',
'redash.visualization',
'ui.codemirror',
'highchart',
'ui.select2',
'angular-growl',
Expand Down
23 changes: 23 additions & 0 deletions rd_ui/app/scripts/controllers/query_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@
}

$scope.query = $route.current.locals.query;

var updateSchema = function() {
$scope.hasSchema = false;
$scope.editorSize = "col-md-12";
var dataSourceId = $scope.query.data_source_id || $scope.dataSources[0].id;
DataSource.getSchema({id: dataSourceId}, function(data) {
if (data && data.length > 0) {
$scope.schema = data;
_.each(data, function(table) {
table.collapsed = true;
});

$scope.editorSize = "col-md-9";
$scope.hasSchema = true;
} else {
$scope.hasSchema = false;
$scope.editorSize = "col-md-12";
}
});
}

Events.record(currentUser, 'view', 'query', $scope.query.id);
getQueryResult();
$scope.queryExecuting = false;
Expand All @@ -27,6 +48,7 @@
$scope.canViewSource = currentUser.hasPermission('view_source');

$scope.dataSources = DataSource.get(function(dataSources) {
updateSchema();
$scope.query.data_source_id = $scope.query.data_source_id || dataSources[0].id;
});

Expand Down Expand Up @@ -126,6 +148,7 @@
});
}

updateSchema();
$scope.executeQuery();
};

Expand Down
80 changes: 67 additions & 13 deletions rd_ui/app/scripts/directives/query_directives.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
restrict: 'E',
template: '<span ng-show="query.id && canViewSource">\
<a ng-show="!sourceMode"\
ng-href="{{query.id}}/source#{{selectedTab}}">Show Source\
ng-href="/queries/{{query.id}}/source#{{selectedTab}}">Show Source\
</a>\
<a ng-show="sourceMode"\
ng-href="/queries/{{query.id}}#{{selectedTab}}">Hide Source\
Expand Down Expand Up @@ -63,26 +63,80 @@
restrict: 'E',
scope: {
'query': '=',
'lock': '='
'lock': '=',
'schema': '='
},
template: '<textarea\
ui-codemirror="editorOptions"\
ng-model="query.query">',
link: function($scope) {
$scope.editorOptions = {
template: '<textarea></textarea>',
link: {
pre: function ($scope, element) {
var textarea = element.children()[0];
var editorOptions = {
mode: 'text/x-sql',
lineWrapping: true,
lineNumbers: true,
readOnly: false,
matchBrackets: true,
autoCloseBrackets: true
};
autoCloseBrackets: true,
extraKeys: {"Ctrl-Space": "autocomplete"}
};

$scope.$watch('lock', function(locked) {
$scope.editorOptions.readOnly = locked ? 'nocursor' : false;
});
var additionalHints = [];

CodeMirror.commands.autocomplete = function(cm) {
var hinter = function(editor, options) {
var hints = CodeMirror.hint.anyword(editor, options);
var cur = editor.getCursor(), token = editor.getTokenAt(cur).string;

hints.list = _.union(hints.list, _.filter(additionalHints, function (h) {
return h.search(token) === 0;
}));

return hints;
};

// CodeMirror.showHint(cm, CodeMirror.hint.anyword);
CodeMirror.showHint(cm, hinter);
};

var codemirror = CodeMirror.fromTextArea(textarea, editorOptions);

codemirror.on('change', function(instance) {
var newValue = instance.getValue();

if (newValue !== $scope.query.query) {
$scope.$evalAsync(function() {
$scope.query.query = newValue;
});
}
});

$scope.$watch('query.query', function () {
if ($scope.query.query !== codemirror.getValue()) {
codemirror.setValue($scope.query.query);
}
});

$scope.$watch('schema', function (schema) {
if (schema) {
var keywords = [];
_.each(schema, function (table) {
keywords.push(table.name);
_.each(table.columns, function (c) {
keywords.push(c);
});
});

additionalHints = _.unique(keywords);
}
});

$scope.$watch('lock', function (locked) {
var readOnly = locked ? 'nocursor' : false;
codemirror.setOption('readOnly', readOnly);
});
}
}
}
};
}

function queryFormatter($http) {
Expand Down
7 changes: 6 additions & 1 deletion rd_ui/app/scripts/services/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,12 @@


var DataSource = function ($resource) {
var DataSourceResource = $resource('/api/data_sources/:id', {id: '@id'}, {'get': {'method': 'GET', 'cache': true, 'isArray': true}});
var actions = {
'get': {'method': 'GET', 'cache': true, 'isArray': true},
'getSchema': {'method': 'GET', 'cache': true, 'isArray': true, 'url': '/api/data_sources/:id/schema'}
};

var DataSourceResource = $resource('/api/data_sources/:id', {id: '@id'}, actions);

return DataSourceResource;
}
Expand Down
14 changes: 13 additions & 1 deletion rd_ui/app/styles/redash.css
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ li.widget:hover {
/* CodeMirror */
.CodeMirror {
border: 1px solid #eee;
height: auto;
/*height: auto;*/
min-height: 300px;
margin-bottom: 10px;
}
Expand Down Expand Up @@ -308,6 +308,18 @@ counter-renderer counter-name {
height: 100%;
}

.schema-browser {
height: 300px;
overflow: scroll;
}

div.table-name {
overflow: scroll;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}

/*
bootstrap's hidden-xs class adds display:block when not hidden
use this class when you need to keep the original display value
Expand Down
34 changes: 23 additions & 11 deletions rd_ui/app/views/query.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ <h2>

<hr>

<div class="row">
<div class="col-lg-12">
<div ng-show="sourceMode">
<div class="row" ng-if="sourceMode">
<div ng-class="editorSize">
<div>
<p>
<button type="button" class="btn btn-primary btn-xs" ng-disabled="queryExecuting" ng-click="executeQuery()">
<span class="glyphicon glyphicon-play"></span> Execute
Expand All @@ -77,27 +77,39 @@ <h2>
</button>
</span>
</p>
</div>

<!-- code editor -->
<div ng-show="sourceMode">
<p>
<query-editor query="query" lock="queryFormatting"></query-editor>
<query-editor query="query" schema="schema" lock="queryFormatting"></query-editor>
</p>
<hr>
</div>
</div>
<div class="col-md-3" ng-show="hasSchema">
<div>
<input type="text" placeholder="Search schema..." class="form-control" ng-model="schemaFilter">
</div>
<div class="schema-browser">
<div ng-repeat="table in schema | filter:schemaFilter">
<div class="table-name" ng-click="table.collapsed = !table.collapsed">
<i class="fa fa-table"></i> <strong><span title="{{table.name}}">{{table.name}}</span></strong>
</div>
<div collapse="table.collapsed">
<div ng-repeat="column in table.columns | filter:schemaFilter" style="padding-left:16px;">{{column}}</div>
</div>
</div>
</div>
</div>

</div>

</div>
<hr ng-if="sourceMode">
<div class="row">
<div class="col-lg-3 rd-hidden-xs">
<p>
<span class="glyphicon glyphicon-user"></span>
<span class="text-muted">Created By </span>
<strong>{{query.user.name}}</strong>
</p>
<p ng-if="query.user.id != query.last_modified_by.id">
<p ng-if="query.last_modified_by && query.user.id != query.last_modified_by.id">
<span class="glyphicon glyphicon-user"></span>
<span class="text-muted">Last Modified By </span>
<strong>{{query.last_modified_by.name}}</strong>
Expand Down Expand Up @@ -190,7 +202,7 @@ <h4 class="modal-title">Query Archive</h4>
<rd-tab tab-id="{{vis.id}}" name="{{vis.name}}" ng-if="vis.type!='TABLE'" ng-repeat="vis in query.visualizations">
<span class="remove" ng-click="deleteVisualization($event, vis)" ng-show="canEdit"> &times;</span>
</rd-tab>
<rd-tab tab-id="add" name="&plus; New" removeable="true" ng-show="canEdit"></rd-tab>
<rd-tab tab-id="add" name="&plus; New Visualization" removeable="true" ng-show="canEdit"></rd-tab>
<li ng-if="!sourceMode" class="rd-tab-btn"><button class="btn btn-sm btn-default" ng-click="executeQuery()" ng-disabled="queryExecuting" title="Refresh Dataset"><span class="glyphicon glyphicon-refresh"></span></button></li>
</ul>
</div>
Expand Down
2 changes: 1 addition & 1 deletion rd_ui/bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"es5-shim": "2.0.8",
"angular-moment": "0.2.0",
"moment": "2.1.0",
"angular-ui-codemirror": "0.0.5",
"codemirror": "4.8.0",
"highcharts": "3.0.10",
"underscore": "1.5.1",
"pivottable": "~1.1.1",
Expand Down
3 changes: 0 additions & 3 deletions redash/cache.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from flask import make_response
from functools import update_wrapper

ONE_YEAR = 60 * 60 * 24 * 365.25

headers = {
Expand Down
10 changes: 9 additions & 1 deletion redash/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,18 @@ def post(self):

return datasource.to_dict()


api.add_resource(DataSourceListAPI, '/api/data_sources', endpoint='data_sources')


class DataSourceSchemaAPI(BaseResource):
def get(self, data_source_id):
data_source = models.DataSource.get_by_id(data_source_id)
schema = data_source.get_schema()

return schema

api.add_resource(DataSourceSchemaAPI, '/api/data_sources/<data_source_id>/schema')

class DashboardRecentAPI(BaseResource):
def get(self):
return [d.to_dict() for d in models.Dashboard.recent(current_user.id).limit(20)]
Expand Down
20 changes: 19 additions & 1 deletion redash/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from flask.ext.login import UserMixin, AnonymousUserMixin
import psycopg2

from redash import utils, settings
from redash import utils, settings, redis_connection
from redash.query_runner import get_query_runner


class Database(object):
Expand Down Expand Up @@ -241,6 +242,23 @@ def to_dict(self):
'type': self.type
}

def get_schema(self, refresh=False):
key = "data_source:schema:{}".format(self.id)

cache = None
if not refresh:
cache = redis_connection.get(key)

if cache is None:
query_runner = get_query_runner(self.type, self.options)
schema = sorted(query_runner.get_schema(), key=lambda t: t['name'])

redis_connection.set(key, json.dumps(schema))
else:
schema = json.loads(cache)

return schema

@classmethod
def all(cls):
return cls.select().order_by(cls.id.asc())
Expand Down
3 changes: 3 additions & 0 deletions redash/query_runner/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ def configuration_schema(cls):
def run_query(self, query):
raise NotImplementedError()

def get_schema(self):
return []

@classmethod
def to_dict(cls):
return {
Expand Down
Loading

0 comments on commit 9fb33cf

Please sign in to comment.