Skip to content

Commit

Permalink
Merge pull request #1 from getredash/master
Browse files Browse the repository at this point in the history
pull upstream to my fork
  • Loading branch information
alison985 authored Jun 17, 2018
2 parents d116381 + c28702a commit f4b3224
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 52 deletions.
11 changes: 11 additions & 0 deletions client/app/assets/less/redash/query.less
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ body.fixed-layout {
}
}

.tab-nav .tab-new-vis {
margin: 0 5px;

> a {
color: @headings-color;
padding: 7px;
margin: 8px 0;
font-weight: 400;
}
}

.bottom-controller {
padding: 10px 15px;
background: #fff;
Expand Down
6 changes: 3 additions & 3 deletions client/app/pages/queries/query.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ <h3>
<li ng-if="!query.is_archived && query.id != undefined && (isQueryOwner || currentUser.hasPermission('admin'))"><a ng-click="archiveQuery()">Archive</a></li>
<li ng-if="!query.is_archived && query.id != undefined && (isQueryOwner || currentUser.hasPermission('admin')) && showPermissionsControl"><a ng-click="showManagePermissionsModal()">Manage Permissions</a></li>
<li ng-if="!query.is_draft && query.id != undefined && (isQueryOwner || currentUser.hasPermission('admin'))"><a ng-click="togglePublished()">Unpublish</a></li>
<li class="divider"></li>
<li class="divider" ng-if="!query.is_archived"></li>
<li ng-if="query.id != undefined"><a ng-click="showApiKey()">Show API Key</a></li>
</ul>
</div>
Expand Down Expand Up @@ -223,9 +223,9 @@ <h3>
<ul class="tab-nav">
<rd-tab ng-if="!query.visualizations.length" tab-id="table" name="Table" base-path="query.getUrl(sourceMode)"></rd-tab>
<rd-tab tab-id="{{vis.id}}" name="{{vis.name}}" base-path="query.getUrl(sourceMode)" ng-repeat="vis in query.visualizations | orderBy:'id'">
<span class="remove" ng-click="deleteVisualization($event, vis)" ng-if="canEdit && !($first && (vis.type === 'TABLE'))"> &times;</span>
<span class="remove" ng-click="deleteVisualization($event, vis)" ng-if="canEdit && !($first && (vis.type === 'TABLE'))"> <i class="zmdi zmdi-close"></i></span>
</rd-tab>
<li class="rd-tab"><a ng-click="openVisualizationEditor()" ng-if="sourceMode && canEdit">&plus; New Visualization</a></li>
<li class="rd-tab tab-new-vis"><a ng-click="openVisualizationEditor()" class="btn btn-default" ng-if="canEdit"><i class="zmdi zmdi-plus"></i> New Visualization</a></li>
</ul>
<div ng-if="!query.visualizations.length" class="query__vis m-t-15 p-b-15 scrollbox">
<filters filters="filters"></filters>
Expand Down
28 changes: 0 additions & 28 deletions client/app/pages/queries/source-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ function QuerySourceCtrl(
) {
// extends QueryViewCtrl
$controller('QueryViewCtrl', { $scope });
// TODO:
// This doesn't get inherited. Setting it on this didn't work either (which is weird).
// Obviously it shouldn't be repeated, but we got bigger fish to fry.
const DEFAULT_TAB = 'table';

Events.record('view_source', 'query', $scope.query.id);

Expand All @@ -21,8 +17,6 @@ function QuerySourceCtrl(
$scope.isDirty = false;
$scope.base_url = `${$location.protocol()}://${$location.host()}:${$location.port()}`;

$scope.newVisualization = undefined;

// @override
Object.defineProperty($scope, 'showDataset', {
get() {
Expand Down Expand Up @@ -71,28 +65,6 @@ function QuerySourceCtrl(
.catch(error => toastr.error(error));
};

$scope.deleteVisualization = ($e, vis) => {
$e.preventDefault();

const title = undefined;
const message = `Are you sure you want to delete ${vis.name} ?`;
const confirm = { class: 'btn-danger', title: 'Delete' };

AlertDialog.open(title, message, confirm).then(() => {
Events.record('delete', 'visualization', vis.id);

Visualization.delete({ id: vis.id }, () => {
if ($scope.selectedTab === String(vis.id)) {
$scope.selectedTab = DEFAULT_TAB;
$location.hash($scope.selectedTab);
}
$scope.query.visualizations = $scope.query.visualizations.filter(v => vis.id !== v.id);
}, () => {
toastr.error("Error deleting visualization. Maybe it's used in a dashboard?");
});
});
};

$scope.$watch('query.query', (newQueryText) => {
$scope.isDirty = (newQueryText !== queryText);
});
Expand Down
29 changes: 27 additions & 2 deletions client/app/pages/queries/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { pick, any, some, find, min, isObject } from 'underscore';
import { SCHEMA_NOT_SUPPORTED, SCHEMA_LOAD_ERROR } from '@/services/data-source';
import template from './query.html';

const DEFAULT_TAB = 'table';

function QueryViewCtrl(
$scope,
Events,
Expand All @@ -20,6 +22,7 @@ function QueryViewCtrl(
currentUser,
Query,
DataSource,
Visualization,
) {
function getQueryResult(maxAge) {
if (maxAge === undefined) {
Expand Down Expand Up @@ -117,7 +120,7 @@ function QueryViewCtrl(
Notifications.getPermissions();
};

$scope.selectedTab = 'table';
$scope.selectedTab = DEFAULT_TAB;
$scope.currentUser = currentUser;
$scope.dataSource = {};
$scope.query = $route.current.locals.query;
Expand Down Expand Up @@ -315,6 +318,28 @@ function QueryViewCtrl(
$location.hash(visualization.id);
};

$scope.deleteVisualization = ($e, vis) => {
$e.preventDefault();

const title = undefined;
const message = `Are you sure you want to delete ${vis.name} ?`;
const confirm = { class: 'btn-danger', title: 'Delete' };

AlertDialog.open(title, message, confirm).then(() => {
Events.record('delete', 'visualization', vis.id);

Visualization.delete({ id: vis.id }, () => {
if ($scope.selectedTab === String(vis.id)) {
$scope.selectedTab = DEFAULT_TAB;
$location.hash($scope.selectedTab);
}
$scope.query.visualizations = $scope.query.visualizations.filter(v => vis.id !== v.id);
}, () => {
toastr.error("Error deleting visualization. Maybe it's used in a dashboard?");
});
});
};

$scope.$watch('query.name', () => {
Title.set($scope.query.name);
});
Expand Down Expand Up @@ -422,7 +447,7 @@ function QueryViewCtrl(
if (!isObject(visualization)) {
visualization = {};
}
$scope.selectedTab = (exists ? hash : visualization.id) || 'table';
$scope.selectedTab = (exists ? hash : visualization.id) || DEFAULT_TAB;
},
);

Expand Down
19 changes: 19 additions & 0 deletions redash/cli/database.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import time

from flask.cli import AppGroup
from flask_migrate import stamp
from sqlalchemy.exc import DatabaseError

manager = AppGroup(help="Manage the database (create/drop tables).")


def _wait_for_db_connection(db):
retried = False
while not retried:
try:
db.engine.execute('SELECT 1;')
return
except DatabaseError:
time.sleep(30)

retried = True


@manager.command()
def create_tables():
"""Create the database tables."""
from redash.models import db

_wait_for_db_connection(db)
db.create_all()

# Need to mark current DB as up to date
Expand All @@ -18,4 +36,5 @@ def drop_tables():
"""Drop the database tables."""
from redash.models import db

_wait_for_db_connection(db)
db.drop_all()
2 changes: 1 addition & 1 deletion redash/handlers/data_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def get(self):
except AttributeError:
logging.exception("Error with DataSource#to_dict (data source id: %d)", ds.id)

return sorted(response.values(), key=lambda d: d['id'])
return sorted(response.values(), key=lambda d: d['name'])

@require_admin
def post(self):
Expand Down
44 changes: 26 additions & 18 deletions redash/query_runner/pg.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,29 +106,37 @@ def _get_definitions(self, schema, query):
schema[table_name]['columns'].append(row['column_name'])

def _get_tables(self, schema):
'''
relkind constants per https://www.postgresql.org/docs/10/static/catalog-pg-class.html
r = regular table
v = view
m = materialized view
f = foreign table
p = partitioned table (new in 10)
---
i = index
S = sequence
t = TOAST table
c = composite type
'''

query = """
SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE table_schema NOT IN ('pg_catalog', 'information_schema');
SELECT s.nspname as table_schema,
c.relname as table_name,
a.attname as column_name
FROM pg_class c
JOIN pg_namespace s
ON c.relnamespace = s.oid
AND s.nspname NOT IN ('pg_catalog', 'information_schema')
JOIN pg_attribute a
ON a.attrelid = c.oid
AND a.attnum > 0
AND NOT a.attisdropped
WHERE c.relkind IN ('r', 'v', 'm', 'f', 'p')
"""

self._get_definitions(schema, query)

materialized_views_query = """
SELECT ns.nspname as table_schema,
mv.relname as table_name,
atr.attname as column_name
FROM pg_class mv
JOIN pg_namespace ns ON mv.relnamespace = ns.oid
JOIN pg_attribute atr
ON atr.attrelid = mv.oid
AND atr.attnum > 0
AND NOT atr.attisdropped
WHERE mv.relkind = 'm';
"""

self._get_definitions(schema, materialized_views_query)

return schema.values()

def _get_connection(self):
Expand Down

0 comments on commit f4b3224

Please sign in to comment.