diff --git a/client/app/pages/data-sources/list.html b/client/app/pages/data-sources/list.html
index 6e0bf7f2f0..35803214a1 100644
--- a/client/app/pages/data-sources/list.html
+++ b/client/app/pages/data-sources/list.html
@@ -7,7 +7,7 @@
diff --git a/redash/models.py b/redash/models.py
index f2b34ff30d..3879a9493f 100644
--- a/redash/models.py
+++ b/redash/models.py
@@ -564,11 +564,12 @@ def to_dict(self, all=False, with_permissions_for=None):
'type': self.type,
'syntax': self.query_runner.syntax,
'paused': self.paused,
- 'pause_reason': self.pause_reason
+ 'pause_reason': self.pause_reason,
+ 'type_name': self.query_runner.name(),
}
+ schema = get_configuration_schema_for_query_runner_type(self.type)
if all:
- schema = get_configuration_schema_for_query_runner_type(self.type)
self.options.set_schema(schema)
d['options'] = self.options.to_dict(mask_secrets=True)
d['queue_name'] = self.queue_name
@@ -580,6 +581,11 @@ def to_dict(self, all=False, with_permissions_for=None):
DataSourceGroup.group == with_permissions_for,
DataSourceGroup.data_source == self).one()[0]
+ doc_url = self.options.get('doc_url', schema['properties'].get(
+ 'doc_url', {}).get('default'))
+ if doc_url:
+ d['options'] = {'doc_url': doc_url}
+
return d
def __unicode__(self):
@@ -654,6 +660,8 @@ def add_group(self, group, view_only=False):
db.session.add(dsg)
return dsg
+ setattr(self, 'data_source_groups', dsg)
+
def remove_group(self, group):
db.session.query(DataSourceGroup).filter(
DataSourceGroup.group == group,
diff --git a/redash/query_runner/__init__.py b/redash/query_runner/__init__.py
index fff1408949..61ddb257c5 100644
--- a/redash/query_runner/__init__.py
+++ b/redash/query_runner/__init__.py
@@ -51,6 +51,7 @@ class NotSupported(Exception):
class BaseQueryRunner(object):
noop_query = None
+ default_doc_url = None
def __init__(self, configuration):
self.syntax = 'sql'
diff --git a/redash/query_runner/big_query.py b/redash/query_runner/big_query.py
index f765978585..d153f9280a 100644
--- a/redash/query_runner/big_query.py
+++ b/redash/query_runner/big_query.py
@@ -80,6 +80,7 @@ def _get_query_results(jobs, project_id, job_id, start_index):
class BigQuery(BaseQueryRunner):
noop_query = "SELECT 1"
+ default_doc_url = "https://cloud.google.com/bigquery/docs/reference/legacy-sql"
@classmethod
def enabled(cls):
@@ -117,6 +118,11 @@ def configuration_schema(cls):
'maximumBillingTier': {
"type": "number",
"title": "Maximum Billing Tier"
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['jsonKeyFile', 'projectId'],
diff --git a/redash/query_runner/cass.py b/redash/query_runner/cass.py
index ca8e4537fe..ac216ca3c2 100644
--- a/redash/query_runner/cass.py
+++ b/redash/query_runner/cass.py
@@ -27,6 +27,7 @@ def default(self, o):
class Cassandra(BaseQueryRunner):
noop_query = "SELECT dateof(now()) FROM system.local"
+ default_doc_url = "http://cassandra.apache.org/doc/latest/cql/index.html"
@classmethod
def enabled(cls):
@@ -65,6 +66,11 @@ def configuration_schema(cls):
'type': 'number',
'title': 'Timeout',
'default': 10
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['keyspace', 'host']
diff --git a/redash/query_runner/dynamodb_sql.py b/redash/query_runner/dynamodb_sql.py
index dfd48a5840..941cc1a013 100644
--- a/redash/query_runner/dynamodb_sql.py
+++ b/redash/query_runner/dynamodb_sql.py
@@ -33,6 +33,9 @@
class DynamoDBSQL(BaseSQLQueryRunner):
+ noop_query = "SELECT 1"
+ default_doc_url = "https://dql.readthedocs.io/en/latest/"
+
@classmethod
def configuration_schema(cls):
return {
@@ -47,6 +50,11 @@ def configuration_schema(cls):
},
"secret_key": {
"type": "string",
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"required": ["access_key", "secret_key"],
diff --git a/redash/query_runner/elasticsearch.py b/redash/query_runner/elasticsearch.py
index a32f788321..5c694a8b27 100644
--- a/redash/query_runner/elasticsearch.py
+++ b/redash/query_runner/elasticsearch.py
@@ -44,7 +44,8 @@
class BaseElasticSearch(BaseQueryRunner):
- DEBUG_ENABLED = False
+ DEBUG_ENABLED = True
+ default_doc_url = "https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html"
@classmethod
def configuration_schema(cls):
@@ -62,6 +63,11 @@ def configuration_schema(cls):
'basic_auth_password': {
'type': 'string',
'title': 'Basic Auth Password'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"secret": ["basic_auth_password"],
diff --git a/redash/query_runner/google_spreadsheets.py b/redash/query_runner/google_spreadsheets.py
index 33be529249..6c13044e0f 100644
--- a/redash/query_runner/google_spreadsheets.py
+++ b/redash/query_runner/google_spreadsheets.py
@@ -108,6 +108,7 @@ def parse_worksheet(worksheet):
columns.append({
'name': column_name,
'friendly_name': column_name,
+
'type': TYPE_STRING
})
@@ -139,6 +140,9 @@ def request(self, *args, **kwargs):
class GoogleSpreadsheet(BaseQueryRunner):
+ default_doc_url = ("http://redash.readthedocs.io/en/latest/"
+ "datasources.html#google-spreadsheets")
+
@classmethod
def annotate_query(cls):
return False
@@ -159,6 +163,11 @@ def configuration_schema(cls):
'jsonKeyFile': {
"type": "string",
'title': 'JSON Key File'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['jsonKeyFile'],
diff --git a/redash/query_runner/graphite.py b/redash/query_runner/graphite.py
index 023ec04940..e2aaff6643 100644
--- a/redash/query_runner/graphite.py
+++ b/redash/query_runner/graphite.py
@@ -42,6 +42,11 @@ def configuration_schema(cls):
'verify': {
'type': 'boolean',
'title': 'Verify SSL certificate'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['url'],
diff --git a/redash/query_runner/hive_ds.py b/redash/query_runner/hive_ds.py
index 73be16021b..5c0acb649b 100644
--- a/redash/query_runner/hive_ds.py
+++ b/redash/query_runner/hive_ds.py
@@ -36,6 +36,8 @@
class Hive(BaseSQLQueryRunner):
noop_query = "SELECT 1"
+ default_doc_url = ("https://cwiki.apache.org/confluence/display/Hive/"
+ "LanguageManual")
@classmethod
def configuration_schema(cls):
@@ -53,6 +55,11 @@ def configuration_schema(cls):
},
"username": {
"type": "string"
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"required": ["host"]
diff --git a/redash/query_runner/impala_ds.py b/redash/query_runner/impala_ds.py
index d3c42112ce..c1f817eb47 100644
--- a/redash/query_runner/impala_ds.py
+++ b/redash/query_runner/impala_ds.py
@@ -36,6 +36,8 @@
class Impala(BaseSQLQueryRunner):
noop_query = "show schemas"
+ default_doc_url = ("http://www.cloudera.com/documentation/enterprise/"
+ "latest/topics/impala_langref.html")
@classmethod
def configuration_schema(cls):
@@ -66,6 +68,11 @@ def configuration_schema(cls):
},
"timeout": {
"type": "number"
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"required": ["host"],
diff --git a/redash/query_runner/influx_db.py b/redash/query_runner/influx_db.py
index 8360a3af72..ea3d4932aa 100644
--- a/redash/query_runner/influx_db.py
+++ b/redash/query_runner/influx_db.py
@@ -50,6 +50,8 @@ def _transform_result(results):
class InfluxDB(BaseQueryRunner):
noop_query = "show measurements limit 1"
+ default_doc_url = ("https://docs.influxdata.com/influxdb/v1.0/"
+ "query_language/spec/")
@classmethod
def configuration_schema(cls):
@@ -58,6 +60,11 @@ def configuration_schema(cls):
'properties': {
'url': {
'type': 'string'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['url']
diff --git a/redash/query_runner/jql.py b/redash/query_runner/jql.py
index 37b1f345c6..e3f9743547 100644
--- a/redash/query_runner/jql.py
+++ b/redash/query_runner/jql.py
@@ -139,6 +139,8 @@ def get_dict_output_field_name(cls,field_name, member_name):
class JiraJQL(BaseQueryRunner):
noop_query = '{"queryType": "count"}'
+ default_doc_url = ("https://confluence.atlassian.com/jirasoftwarecloud/"
+ "advanced-searching-764478330.html")
@classmethod
def configuration_schema(cls):
@@ -154,6 +156,11 @@ def configuration_schema(cls):
},
'password': {
'type': 'string'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['url', 'username', 'password'],
diff --git a/redash/query_runner/mongodb.py b/redash/query_runner/mongodb.py
index e572167ed2..81ae8d9a63 100644
--- a/redash/query_runner/mongodb.py
+++ b/redash/query_runner/mongodb.py
@@ -118,6 +118,9 @@ def parse_results(results):
class MongoDB(BaseQueryRunner):
+ default_doc_url = ("https://docs.mongodb.com/manual/reference/operator/"
+ "query/")
+
@classmethod
def configuration_schema(cls):
return {
@@ -135,6 +138,11 @@ def configuration_schema(cls):
'type': 'string',
'title': 'Replica Set Name'
},
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
+ }
},
'required': ['connectionString', 'dbName']
}
diff --git a/redash/query_runner/mssql.py b/redash/query_runner/mssql.py
index bcc810fe4d..dd746d4334 100644
--- a/redash/query_runner/mssql.py
+++ b/redash/query_runner/mssql.py
@@ -35,6 +35,7 @@ def default(self, o):
class SqlServer(BaseSQLQueryRunner):
noop_query = "SELECT 1"
+ default_doc_url = "https://msdn.microsoft.com/en-us/library/bb510741.aspx"
@classmethod
def configuration_schema(cls):
@@ -68,6 +69,11 @@ def configuration_schema(cls):
"db": {
"type": "string",
"title": "Database Name"
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"required": ["db"],
diff --git a/redash/query_runner/mysql.py b/redash/query_runner/mysql.py
index 3679b694db..8b3fed0300 100644
--- a/redash/query_runner/mysql.py
+++ b/redash/query_runner/mysql.py
@@ -29,6 +29,7 @@
class Mysql(BaseSQLQueryRunner):
noop_query = "SELECT 1"
+ default_doc_url = 'https://dev.mysql.com/doc/refman/5.7/en/'
@classmethod
def configuration_schema(cls):
@@ -79,6 +80,11 @@ def configuration_schema(cls):
'ssl_key': {
'type': 'string',
'title': 'Path to private key file (SSL)'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
})
diff --git a/redash/query_runner/oracle.py b/redash/query_runner/oracle.py
index 5bb8f70f2f..12448029d5 100644
--- a/redash/query_runner/oracle.py
+++ b/redash/query_runner/oracle.py
@@ -31,8 +31,10 @@
logger = logging.getLogger(__name__)
+
class Oracle(BaseSQLQueryRunner):
noop_query = "SELECT 1 FROM dual"
+ default_doc_url = "http://docs.oracle.com/database/121/SQLRF/toc.htm"
@classmethod
def get_col_type(cls, col_type, scale):
@@ -65,6 +67,11 @@ def configuration_schema(cls):
"servicename": {
"type": "string",
"title": "DSN Service Name"
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"required": ["servicename", "user", "password", "host", "port"],
diff --git a/redash/query_runner/pg.py b/redash/query_runner/pg.py
index fe3375b896..6cfb2976e9 100644
--- a/redash/query_runner/pg.py
+++ b/redash/query_runner/pg.py
@@ -47,6 +47,7 @@ def _wait(conn, timeout=None):
class PostgreSQL(BaseSQLQueryRunner):
noop_query = "SELECT 1"
+ default_doc_url = "https://www.postgresql.org/docs/current/"
@classmethod
def configuration_schema(cls):
@@ -75,6 +76,11 @@ def configuration_schema(cls):
"type": "string",
"title": "SSL Mode",
"default": "prefer"
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"order": ['host', 'port', 'user', 'password'],
@@ -179,6 +185,9 @@ def run_query(self, query, user):
class Redshift(PostgreSQL):
+ default_doc_url = ("http://docs.aws.amazon.com/redshift/latest/"
+ "dg/cm_chap_SQLCommandRef.html")
+
@classmethod
def type(cls):
return "redshift"
@@ -223,6 +232,11 @@ def configuration_schema(cls):
"type": "string",
"title": "SSL Mode",
"default": "prefer"
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"order": ['host', 'port', 'user', 'password'],
diff --git a/redash/query_runner/presto.py b/redash/query_runner/presto.py
index d910a7c56b..6fbaad8f7b 100644
--- a/redash/query_runner/presto.py
+++ b/redash/query_runner/presto.py
@@ -33,6 +33,7 @@
class Presto(BaseQueryRunner):
noop_query = 'SHOW TABLES'
+ default_doc_url = 'https://prestodb.io/docs/current/'
@classmethod
def configuration_schema(cls):
@@ -53,6 +54,11 @@ def configuration_schema(cls):
},
'username': {
'type': 'string'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['host']
diff --git a/redash/query_runner/python.py b/redash/query_runner/python.py
index f8e69f96ce..275b44fa16 100644
--- a/redash/query_runner/python.py
+++ b/redash/query_runner/python.py
@@ -46,6 +46,9 @@ class Python(BaseQueryRunner):
'tuple', 'set', 'list', 'dict', 'bool',
)
+ default_doc_url = ("http://redash.readthedocs.io/en/latest/"
+ "datasources.html#python")
+
@classmethod
def configuration_schema(cls):
return {
@@ -57,6 +60,11 @@ def configuration_schema(cls):
},
'additionalModulesPaths': {
'type': 'string'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
}
diff --git a/redash/query_runner/script.py b/redash/query_runner/script.py
index 75bee08cec..0b10f9ded5 100644
--- a/redash/query_runner/script.py
+++ b/redash/query_runner/script.py
@@ -6,6 +6,9 @@
class Script(BaseQueryRunner):
+ default_doc_url = ("http://redash.readthedocs.io/en/latest/"
+ "datasources.html#python")
+
@classmethod
def annotate_query(cls):
return False
@@ -26,6 +29,11 @@ def configuration_schema(cls):
'shell': {
'type': 'boolean',
'title': 'Execute command through the shell'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['path']
diff --git a/redash/query_runner/sqlite.py b/redash/query_runner/sqlite.py
index 2bab1f27c4..18bef51816 100644
--- a/redash/query_runner/sqlite.py
+++ b/redash/query_runner/sqlite.py
@@ -13,6 +13,7 @@
class Sqlite(BaseSQLQueryRunner):
noop_query = "pragma quick_check"
+ default_doc_url = "http://sqlite.org/lang.html"
@classmethod
def configuration_schema(cls):
@@ -22,6 +23,11 @@ def configuration_schema(cls):
"dbpath": {
"type": "string",
"title": "Database Path"
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
"required": ["dbpath"],
diff --git a/redash/query_runner/treasuredata.py b/redash/query_runner/treasuredata.py
index 2f68d6d7e5..441dea89eb 100644
--- a/redash/query_runner/treasuredata.py
+++ b/redash/query_runner/treasuredata.py
@@ -36,6 +36,7 @@
class TreasureData(BaseQueryRunner):
noop_query = "SELECT 1"
+ default_doc_url = "https://docs.treasuredata.com/categories/hive"
@classmethod
def configuration_schema(cls):
@@ -59,6 +60,11 @@ def configuration_schema(cls):
'type': 'boolean',
'title': 'Auto Schema Retrieval',
'default': False
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
},
'required': ['apikey','db']
diff --git a/redash/query_runner/url.py b/redash/query_runner/url.py
index 8763b63ed2..5da7659390 100644
--- a/redash/query_runner/url.py
+++ b/redash/query_runner/url.py
@@ -3,6 +3,9 @@
class Url(BaseQueryRunner):
+ default_doc_url = ("http://redash.readthedocs.io/en/latest/"
+ "datasources.html#url")
+
@classmethod
def configuration_schema(cls):
return {
@@ -11,6 +14,11 @@ def configuration_schema(cls):
'url': {
'type': 'string',
'title': 'URL base path'
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
}
}
}
diff --git a/redash/query_runner/vertica.py b/redash/query_runner/vertica.py
index 0bfe2b6611..97b28ebfcb 100644
--- a/redash/query_runner/vertica.py
+++ b/redash/query_runner/vertica.py
@@ -30,6 +30,10 @@
class Vertica(BaseSQLQueryRunner):
noop_query = "SELECT 1"
+ default_doc_url = (
+ "https://my.vertica.com/docs/8.0.x/HTML/index.htm#Authoring/"
+ "ConceptsGuide/Other/SQLOverview.htm%3FTocPath%3DSQL"
+ "%2520Reference%2520Manual%7C_____1")
@classmethod
def configuration_schema(cls):
@@ -56,7 +60,12 @@ def configuration_schema(cls):
"read_timeout": {
"type": "number",
"title": "Read Timeout"
- },
+ },
+ "doc_url": {
+ "type": "string",
+ "title": "Documentation URL",
+ "default": cls.default_doc_url
+ }
},
'required': ['database'],
'secret': ['password']
diff --git a/tests/handlers/test_data_sources.py b/tests/handlers/test_data_sources.py
index f07a2b3719..4590056fd4 100644
--- a/tests/handlers/test_data_sources.py
+++ b/tests/handlers/test_data_sources.py
@@ -60,7 +60,8 @@ def test_updates_data_source(self):
new_name = 'New Name'
new_options = {"dbname": "newdb"}
rv = self.make_request('post', self.path,
- data={'name': new_name, 'type': 'pg', 'options': new_options},
+ data={'name': new_name, 'type': 'pg', 'options': new_options,
+ 'doc_url': None},
user=admin)
self.assertEqual(rv.status_code, 200)
@@ -101,7 +102,9 @@ def test_returns_400_when_configuration_invalid(self):
def test_creates_data_source(self):
admin = self.factory.create_admin()
rv = self.make_request('post', '/api/data_sources',
- data={'name': 'DS 1', 'type': 'pg', 'options': {"dbname": "redash"}}, user=admin)
+ data={'name': 'DS 1', 'type': 'pg',
+ 'options': {"dbname": "redash"},
+ 'doc_url': None}, user=admin)
self.assertEqual(rv.status_code, 200)
diff --git a/tests/test_cli.py b/tests/test_cli.py
index b46da2094e..550ab577e7 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -16,7 +16,7 @@ def test_interactive_new(self):
result = runner.invoke(
manager,
['ds', 'new'],
- input="test\n%s\n\n\nexample.com\n\n\ntestdb\n" % (pg_i,))
+ input="test\n%s\n\n\n\nexample.com\n\n\ntestdb\n" % (pg_i,))
self.assertFalse(result.exception)
self.assertEqual(result.exit_code, 0)
self.assertEqual(DataSource.query.count(), 1)