From fcd984aca6b5af7d316065ff604d6d8c08d9a847 Mon Sep 17 00:00:00 2001 From: Corey Huinker Date: Mon, 21 May 2018 15:18:59 -0400 Subject: [PATCH 1/8] Allow get_tables to see views and v10-style partitioned tables And it does so with just one query. --- redash/query_runner/pg.py | 48 +++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/redash/query_runner/pg.py b/redash/query_runner/pg.py index fe3375b896..1bf89182a1 100644 --- a/redash/query_runner/pg.py +++ b/redash/query_runner/pg.py @@ -106,33 +106,43 @@ def _get_definitions(self, schema, query): schema[table_name]['columns'].append(row['column_name']) def _get_tables(self, schema): - query = """ - SELECT table_schema, table_name, column_name - FROM information_schema.columns - WHERE table_schema NOT IN ('pg_catalog', 'information_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 + ''' - 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'; + query = """ + 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, materialized_views_query) + self._get_definitions(schema, query) return schema.values() def _get_connection(self): - connection = psycopg2.connect(user=self.configuration.get('user'), + + connection = psycopg2.connect(user=self.configuration.get('user'), password=self.configuration.get('password'), host=self.configuration.get('host'), port=self.configuration.get('port'), From 95593481a5b0df223a8670580d1040332363cb27 Mon Sep 17 00:00:00 2001 From: Corey Huinker Date: Mon, 21 May 2018 15:26:46 -0400 Subject: [PATCH 2/8] Update pg.py --- redash/query_runner/pg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/redash/query_runner/pg.py b/redash/query_runner/pg.py index 1bf89182a1..f343ce73a0 100644 --- a/redash/query_runner/pg.py +++ b/redash/query_runner/pg.py @@ -141,8 +141,7 @@ def _get_tables(self, schema): return schema.values() def _get_connection(self): - - connection = psycopg2.connect(user=self.configuration.get('user'), + connection = psycopg2.connect(user=self.configuration.get('user'), password=self.configuration.get('password'), host=self.configuration.get('host'), port=self.configuration.get('port'), From 76e86817c8d9fb67c8681b5bdef8dced77ecf90b Mon Sep 17 00:00:00 2001 From: Corey Huinker Date: Mon, 21 May 2018 15:37:00 -0400 Subject: [PATCH 3/8] remove blank line --- redash/query_runner/pg.py | 1 - 1 file changed, 1 deletion(-) diff --git a/redash/query_runner/pg.py b/redash/query_runner/pg.py index f343ce73a0..2c90e00570 100644 --- a/redash/query_runner/pg.py +++ b/redash/query_runner/pg.py @@ -120,7 +120,6 @@ def _get_tables(self, schema): c = composite type ''' - query = """ SELECT s.nspname as table_schema, c.relname as table_name, From ea8951711f1ed80472a1a24bdd9f2910601b2bba Mon Sep 17 00:00:00 2001 From: Alison Date: Tue, 5 Jun 2018 22:50:35 -0500 Subject: [PATCH 4/8] sort datasources alphabetically fixes https://github.com/getredash/redash/issues/2567 Same as https://github.com/mozilla/redash/issues/367 --- redash/handlers/data_sources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redash/handlers/data_sources.py b/redash/handlers/data_sources.py index 230673aea2..756505d3b1 100644 --- a/redash/handlers/data_sources.py +++ b/redash/handlers/data_sources.py @@ -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): From 2982d77ff26a6a5661d13e6c4209d628e0fea4e4 Mon Sep 17 00:00:00 2001 From: Alison Date: Tue, 5 Jun 2018 23:11:19 -0500 Subject: [PATCH 5/8] remove extra menu line if query is archived fixes #2571 Sames as https://github.com/mozilla/redash/issues/319 fixed in https://github.com/mozilla/redash/pull/414/ --- client/app/pages/queries/query.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/app/pages/queries/query.html b/client/app/pages/queries/query.html index b04111a828..205bddc5cf 100644 --- a/client/app/pages/queries/query.html +++ b/client/app/pages/queries/query.html @@ -63,7 +63,7 @@

  • Archive
  • Manage Permissions
  • Unpublish
  • -
  • +
  • Show API Key
  • From 1efdb6034fbf04cd41c4575b09b2e9da1a08eddc Mon Sep 17 00:00:00 2001 From: ariarijp Date: Sun, 10 Jun 2018 20:17:53 +0900 Subject: [PATCH 6/8] Fix connection error when you run "create_tables" --- redash/cli/database.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/redash/cli/database.py b/redash/cli/database.py index 8ff279302a..701103625f 100644 --- a/redash/cli/database.py +++ b/redash/cli/database.py @@ -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 @@ -18,4 +36,5 @@ def drop_tables(): """Drop the database tables.""" from redash.models import db + _wait_for_db_connection(db) db.drop_all() From 418ac1bb681443ff4b1de1fdb860ca5832859552 Mon Sep 17 00:00:00 2001 From: Levko Kravets Date: Tue, 12 Jun 2018 20:03:21 +0300 Subject: [PATCH 7/8] Show data only mode: allow to add and delete visualizations --- client/app/pages/queries/query.html | 2 +- client/app/pages/queries/source-view.js | 28 ------------------------ client/app/pages/queries/view.js | 29 +++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/client/app/pages/queries/query.html b/client/app/pages/queries/query.html index b04111a828..c92849ee3a 100644 --- a/client/app/pages/queries/query.html +++ b/client/app/pages/queries/query.html @@ -225,7 +225,7 @@

    × -
  • + New Visualization
  • +
  • + New Visualization
  • diff --git a/client/app/pages/queries/source-view.js b/client/app/pages/queries/source-view.js index 4afaea202a..a2172af05a 100644 --- a/client/app/pages/queries/source-view.js +++ b/client/app/pages/queries/source-view.js @@ -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); @@ -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() { @@ -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); }); diff --git a/client/app/pages/queries/view.js b/client/app/pages/queries/view.js index 9ac461897e..ab2aa63be6 100644 --- a/client/app/pages/queries/view.js +++ b/client/app/pages/queries/view.js @@ -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, @@ -20,6 +22,7 @@ function QueryViewCtrl( currentUser, Query, DataSource, + Visualization, ) { function getQueryResult(maxAge) { if (maxAge === undefined) { @@ -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; @@ -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); }); @@ -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; }, ); From 1678155490253ec09fcba49c2f4990749d41a602 Mon Sep 17 00:00:00 2001 From: Zsolt Kocsmarszky Date: Wed, 13 Jun 2018 16:57:20 +0200 Subject: [PATCH 8/8] Make + New Visualization a button and add nicer icons --- client/app/assets/less/redash/query.less | 11 +++++++++++ client/app/pages/queries/query.html | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/client/app/assets/less/redash/query.less b/client/app/assets/less/redash/query.less index 2eb8a5383e..671798a0f6 100644 --- a/client/app/assets/less/redash/query.less +++ b/client/app/assets/less/redash/query.less @@ -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; diff --git a/client/app/pages/queries/query.html b/client/app/pages/queries/query.html index c92849ee3a..b88e69f654 100644 --- a/client/app/pages/queries/query.html +++ b/client/app/pages/queries/query.html @@ -223,9 +223,9 @@