Skip to content

Commit

Permalink
Closes #9: Add data source version extension.
Browse files Browse the repository at this point in the history
  • Loading branch information
Marina Samuel committed Sep 13, 2018
1 parent 26f687c commit 971ba52
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 1 deletion.
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
'redash.extensions': [
'dockerflow = redash_stmo.dockerflow:dockerflow',
'datasource_health = redash_stmo.health:datasource_health',
'datasource_link = redash_stmo.datasource_link:datasource_link'
'datasource_link = redash_stmo.datasource_link:datasource_link',
'datasource_version = redash_stmo.datasource_version:datasource_version',
],
},
classifiers=[
Expand Down
63 changes: 63 additions & 0 deletions src/redash_stmo/datasource_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import json
import logging

from redash_stmo.resources import add_resource

from redash.models import DataSource
from redash.handlers.base import BaseResource, get_object_or_404
from redash.permissions import require_access, view_only

logger = logging.getLogger(__name__)


DATASOURCE_VERSION_PARSE_INFO = {
"pg": {
"version_query": "select version()",
"delimiter": " ",
"index": 1
},
"redshift": {
"version_query": "select version()",
"delimiter": " ",
"index": -1
},
"mysql": {
"version_query": "select version()",
"delimiter": "-",
"index": 0
}
}

class DataSourceVersionResource(BaseResource):
def get(self, data_source_id):
data_source = get_object_or_404(
DataSource.get_by_id_and_org,
data_source_id,
self.current_org
)
require_access(data_source.groups, self.current_user, view_only)
version_info = get_data_source_version(data_source.query_runner)
return {"version": version_info}

def get_data_source_version(query_runner):
parse_info = DATASOURCE_VERSION_PARSE_INFO.get(query_runner.type())
if parse_info is None:
return None

data, error = query_runner.run_query(parse_info["version_query"], None)
if error is not None:
logger.error(
"Unable to run version query for %s: %s", query_runner.type(), error)
return None
try:
version = json.loads(data)['rows'][0]['version']
except (KeyError, IndexError) as err:
logger.exception(
"Unable to parse data source version for %s: %s", query_runner.type(), err)
return None

version = version.split(parse_info["delimiter"])[parse_info["index"]]
return version

def datasource_version(app=None):
add_resource(app, DataSourceVersionResource, '/api/data_sources/<data_source_id>/version')
57 changes: 57 additions & 0 deletions src/redash_stmo/datasource_version/bundle/datasource_version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';

class DatasourceVersion extends React.Component {
static propTypes = {
clientConfig: PropTypes.object.isRequired,
datasourceId: PropTypes.number.isRequired,
}

constructor(props) {
super(props);
this.state = {
version: '',
};
}

loadURLData() {
fetch(`${this.props.clientConfig.basePath}api/data_sources/${this.props.datasourceId}/version`)
.then((response) => {
if (response.status === 200) {
return response.json();
}
return {};
})
.catch(error => {
console.error(`Error loading data source version: ${error}`);
return {};
})
.then((json) => {
this.setState({ version: json.version });
});
}

componentDidMount() {
this.loadURLData();
}

componentDidUpdate(prevProps) {
if (this.props.datasourceId !== prevProps.datasourceId) {
this.loadURLData();
}
}

render() {
if (!this.state.version) {
return null;
}
return (
<span>{this.state.version}</span>
);
}
}

export default function init(ngModule) {
ngModule.component('datasourceVersion', react2angular(DatasourceVersion, ['datasourceId'], ['clientConfig']));
}
70 changes: 70 additions & 0 deletions tests/test_datasource_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import json
import mock

from tests import BaseTestCase
from flask import Flask

from redash.models import DataSource
from redash.query_runner.pg import PostgreSQL
from redash_stmo.datasource_version import datasource_version


class TestDatasourceVersion(BaseTestCase):
EXPECTED_DOC_URL = "www.example.com"
def setUp(self):
super(TestDatasourceVersion, self).setUp()
self.admin = self.factory.create_admin()
self.data_source = self.factory.create_data_source()
self.patched_run_query = self._setup_mock('redash.query_runner.pg.PostgreSQL.run_query')
self.patched_runner_type = self._setup_mock('redash.query_runner.pg.PostgreSQL.type')
datasource_version(self.app)

def _setup_mock(self, function_to_patch):
patcher = mock.patch(function_to_patch)
patched_function = patcher.start()
self.addCleanup(patcher.stop)
return patched_function

def _test_expected_version_returned(self, expected_version, version_string, runner_type):
self.patched_runner_type.return_value = runner_type
self.patched_run_query.return_value = (json.dumps({
"rows":
[{ "version": version_string.format(version=expected_version) }]
}), None)
rv = self.make_request('get', '/api/data_sources/{}/version'.format(self.data_source.id), user=self.admin)
self.assertEqual(200, rv.status_code)
self.assertEqual(rv.json['version'], expected_version)

def test_gets_postgres_version(self):
RUNNER_TYPE = "pg"
DATASOURCE_VERSION = "9.5.10"
VERSION_STRING = (
"PostgreSQL {version} on x86_64-pc-linux-gnu, compiled by gcc "
"(GCC) 4.8.3 20140911 (Red Hat 4.8.3-9), 64-bit"
)
self._test_expected_version_returned(DATASOURCE_VERSION, VERSION_STRING, RUNNER_TYPE)

def test_gets_redshift_version(self):
RUNNER_TYPE = "redshift"
DATASOURCE_VERSION = "1.0.3688"
VERSION_STRING = (
"PostgreSQL 8.0.2 on i686-pc-linux-gnu, compiled by GCC "
"gcc (GCC) 3.4.2 20041017 (Red Hat 3.4.2-6.fc3), Redshift {version}"
)
self._test_expected_version_returned(DATASOURCE_VERSION, VERSION_STRING, RUNNER_TYPE)

def test_gets_mysql_version(self):
RUNNER_TYPE = "mysql"
DATASOURCE_VERSION = "5.7.16"
VERSION_STRING = "{version}-log"
self._test_expected_version_returned(DATASOURCE_VERSION, VERSION_STRING, RUNNER_TYPE)

def test_unexpected_json(self):
self.patched_runner_type.return_value = "pg"
self.patched_run_query.return_value = (json.dumps({
"rows":
[{ "bad_json": "foo" }]
}), None)
rv = self.make_request('get', '/api/data_sources/{}/version'.format(self.data_source.id), user=self.admin)
self.assertEqual(200, rv.status_code)
self.assertEqual(rv.json['version'], None)

0 comments on commit 971ba52

Please sign in to comment.