Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e2b1f32
Add data.world to dialect selector
kndungu Dec 6, 2017
08d7785
Add data.world fields
kndungu Dec 6, 2017
5858033
Add data.world sample credentials
kndungu Dec 6, 2017
1d18c4e
Connect data.world
kndungu Dec 7, 2017
dc46f92
Connect to data.world with credentials
kndungu Dec 7, 2017
313e110
Add data.world tab text
kndungu Dec 7, 2017
f693cf5
Add editor to data world connector
kndungu Dec 13, 2017
84b933b
Display tables in data world dataset
kndungu Dec 13, 2017
831d33a
[WIP] Get schema of each table in dataset
kndungu Dec 13, 2017
842132e
Rename id to identifier to prevent a clash
kndungu Dec 14, 2017
b27d62c
Finish adding UI changes
kndungu Dec 14, 2017
5473fde
Implement tables function
kndungu Dec 14, 2017
e9535d6
Retrieve table schemas
kndungu Dec 14, 2017
742ae69
Implement schema function
kndungu Dec 14, 2017
5ff119d
Implement query function
kndungu Dec 14, 2017
11fae2e
Replace - with _ on queries
kndungu Dec 15, 2017
e0424ee
Use query to get all tables
kndungu Jan 2, 2018
a18eee4
Remove redundant code
kndungu Jan 2, 2018
78ab1e0
Use dataset URL
kndungu Jan 2, 2018
1c7b19d
Get tab label from provided URL
kndungu Jan 2, 2018
5b58e41
Change sample dataset URL
kndungu Jan 2, 2018
3148d22
Update text on connection tab
kndungu Jan 3, 2018
69a73b5
Get database name from supplied URL
kndungu Jan 3, 2018
4f44902
Add preview query
kndungu Jan 3, 2018
3d1b53c
Error handling
kndungu Jan 3, 2018
9b510f9
Use single API call in schema function
kndungu Jan 3, 2018
17fb8e6
Add a user agent to all API requests
kndungu Jan 3, 2018
563112f
Remove duplicate code
kndungu Jan 3, 2018
a0d7021
Add data.world tests
kndungu Jan 8, 2018
4805508
Specify type of token
kndungu Jan 9, 2018
87ff849
Remove unnecessary promises
kndungu Jan 9, 2018
2fbc1d5
Replace dashes in tables and schemas function
kndungu Jan 9, 2018
1350ec8
Return promise in connect test case
kndungu Jan 9, 2018
1ab33e8
Return promise in all data.world test cases
kndungu Jan 9, 2018
04ad51a
Add data.world test script
kndungu Jan 9, 2018
de82e14
Update filename
kndungu Jan 10, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions app/components/Settings/Preview/TableTree.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import TreeView from 'react-treeview';
import {isEmpty, has} from 'ramda';

import {DIALECTS} from '../../../constants/constants';
import {getPathNames} from '../../../utils/utils';

const BASENAME_RE = /[^\\/]+$/;

Expand All @@ -26,6 +27,17 @@ class TableTree extends Component {
})
}

getLabel(connectionObject) {
switch (connectionObject.dialect) {
case DIALECTS.SQLITE:
return BASENAME_RE.exec(connectionObject.storage)[0] || connectionObject.storage;
case DIALECTS.DATA_WORLD:
return getPathNames(connectionObject.url)[2];
default:
return connectionObject.database;
}
}

storeSchemaTree() {
const {schemaRequest, getSqlSchema, updatePreview} = this.props;

Expand Down Expand Up @@ -84,9 +96,7 @@ class TableTree extends Component {
return (<div className="loading">{'Updating'}</div>);
}

const label = (this.props.connectionObject.dialect === DIALECTS.SQLITE) ?
BASENAME_RE.exec(this.props.connectionObject.storage)[0] || this.props.connectionObject.storage :
this.props.connectionObject.database;
const label = this.getLabel(this.props.connectionObject);
const labelNode = <span className="node">{label}</span>;

return (
Expand Down
3 changes: 2 additions & 1 deletion app/components/Settings/Settings.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ class Settings extends Component {
DIALECTS.APACHE_SPARK,
DIALECTS.IBM_DB2,
DIALECTS.MYSQL, DIALECTS.MARIADB, DIALECTS.POSTGRES,
DIALECTS.REDSHIFT, DIALECTS.MSSQL, DIALECTS.SQLITE
DIALECTS.REDSHIFT, DIALECTS.MSSQL, DIALECTS.SQLITE,
DIALECTS.DATA_WORLD
])) {
if (connectRequest.status === 200 && !tablesRequest.status) {
this.setState({editMode: false});
Expand Down
8 changes: 8 additions & 0 deletions app/components/Settings/Tabs/Tab.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {LOGOS, DIALECTS} from '../../../constants/constants';
import {getPathNames} from '../../../utils/utils';

export default class ConnectionTab extends Component {
constructor(props) {
Expand Down Expand Up @@ -35,6 +36,13 @@ export default class ConnectionTab extends Component {
label = `Elasticsearch (${connectionObject.host})`;
} else if (connectionObject.dialect === DIALECTS.SQLITE) {
label = connectionObject.storage;
} else if (connectionObject.dialect === DIALECTS.DATA_WORLD) {
const pathNames = getPathNames(connectionObject.url);
if (pathNames.length >= 3) {
label = `data.world (${pathNames[1]}/${pathNames[2]})`;
} else {
label = 'data.world (/)';
}
} else {
label = `${connectionObject.database} (${connectionObject.username}@${connectionObject.host})`;
}
Expand Down
29 changes: 25 additions & 4 deletions app/constants/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export const DIALECTS = {
IBM_DB2: 'ibm db2',
APACHE_SPARK: 'apache spark',
APACHE_IMPALA: 'apache impala',
APACHE_DRILL: 'apache drill'
APACHE_DRILL: 'apache drill',
DATA_WORLD: 'data.world'
};

export const SQL_DIALECTS_USING_EDITOR = [
Expand All @@ -26,7 +27,8 @@ export const SQL_DIALECTS_USING_EDITOR = [
'sqlite',
'ibm db2',
'apache spark',
'apache impala'
'apache impala',
'data.world'
];

const commonSqlOptions = [
Expand Down Expand Up @@ -178,7 +180,21 @@ export const CONNECTION_CONFIG = {
'value': 'secretAccessKey',
'type': 'password'
}
] // TODO - password options for apache drill
], // TODO - password options for apache drill
[DIALECTS.DATA_WORLD]: [
{
'label': 'Dataset/Project URL',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to change this. Instead of having the user to input an URL and the connector to parse the URL to determine owner and dataset id. I'd like the user to input owner and id:

  • this is more consistent with the existing connectors
  • and it simplifies the code (no need for getPathNames)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @n-riesco, unless you feel strongly about this, we would like to use the URL. That would be consistent with our other connectors work (e.g. Tableau and Google Data Studio) and is more usable, as users only have one value to copy and paste, which is readily available on the browser for the dataset they are looking at. We do a little bit of extra work in code to spare the user in real life. Also, using URIs that contain information to parse out isn't without a precedent, the most common is JDBC.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't feel strongly about it.

@tarzzz @chriddyp are you OK with using an URL here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is more usable, as users only have one value to copy and paste, which is readily available on the browser for the dataset they are looking at

This sounds like a good reason to me 👍

'value': 'url',
'type': 'text',
'description': 'The URL of the dataset or project on data.world'
},
{
'label': 'Read/Write API Token',
'value': 'token',
'type': 'password',
'description': 'Your data.world read/write token. It can be obtained from https://data.world/settings/advanced'
}
]
};


Expand All @@ -194,7 +210,8 @@ export const LOGOS = {
[DIALECTS.MSSQL]: 'images/mssql-logo.png',
[DIALECTS.SQLITE]: 'images/sqlite-logo.png',
[DIALECTS.S3]: 'images/s3-logo.png',
[DIALECTS.APACHE_DRILL]: 'images/apache_drill-logo.png'
[DIALECTS.APACHE_DRILL]: 'images/apache_drill-logo.png',
[DIALECTS.DATA_WORLD]: 'images/dataworld-logo.png'
};

export function PREVIEW_QUERY (dialect, table, database = '') {
Expand All @@ -207,6 +224,7 @@ export function PREVIEW_QUERY (dialect, table, database = '') {
case DIALECTS.SQLITE:
case DIALECTS.MARIADB:
case DIALECTS.POSTGRES:
case DIALECTS.DATA_WORLD:
case DIALECTS.REDSHIFT:
return `SELECT * FROM ${table} LIMIT 1000`;
case DIALECTS.MSSQL:
Expand Down Expand Up @@ -378,5 +396,8 @@ export const SAMPLE_DBS = {
sqlite: {
dialect: 'sqlite',
storage: `${__dirname}/plotly_datasets.db`
},
[DIALECTS.DATA_WORLD]: {
url: 'https://data.world/rflprr/reported-lyme-disease-cases-by-state'
}
};
Binary file added app/images/dataworld-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions app/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,11 @@ export function homeUrl() {
'/external-data-connector' :
'';
}

export function getPathNames(url) {
const parser = document.createElement('a');
parser.href = url;
const pathNames = parser.pathname.split('/');

return pathNames;
}
3 changes: 3 additions & 0 deletions backend/persistent/datastores/Datastores.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as ApacheDrill from './ApacheDrill';
import * as IbmDb2 from './ibmdb2';
import * as ApacheLivy from './livy';
import * as ApacheImpala from './impala';
import * as DataWorld from './dataworld';
import * as DatastoreMock from './datastoremock';

/*
Expand Down Expand Up @@ -45,6 +46,8 @@ function getDatastoreClient(connection) {
return ApacheImpala;
} else if (dialect === 'ibm db2') {
return IbmDb2;
} else if (dialect === 'data.world') {
return DataWorld;
}
return Sql;
}
Expand Down
111 changes: 111 additions & 0 deletions backend/persistent/datastores/dataworld.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import fetch from 'node-fetch';
import url from 'url';

import Logger from '../../logger';

function parseUrl(datasetUrl) {
const pathnameArray = url.parse(datasetUrl).pathname.split('/');
return {
owner: pathnameArray[1],
id: pathnameArray[2]
};
}

export function connect(connection) {
const { owner, id } = parseUrl(connection.url);
return fetch(`https://api.data.world/v0/datasets/${owner}/${id}/`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${connection.token}`,
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'User-Agent': `Falcon/Plotly - ${process.env.npm_package_version}`
}
})
.then(res => res.json())
.then(json => {
// json.code is defined only when there is an error
if (json.code) {
throw new Error(JSON.stringify(json));
}
})
.catch(err => {
Logger.log(err);
throw err;
});
}

export function tables(connection) {
return query('SELECT * FROM Tables', connection).then((res) => {
const allTables = res.rows.map((table) => {
return table[0].replace(/-/g, '_');
});

return allTables;
})
.catch(err => {
Logger.log(err);
throw err;
});
}

export function schemas(connection) {
return query('SELECT * FROM TableColumns', connection).then((res) => {
const rows = res.rows.map((table) => {
const tableName = table[0].replace(/-/g, '_');
const columnName = table[3];
// Extract the datatype from datatype url e.g. http://www.w3.org/2001/XMLSchema#integer
const columnDataType = /#(.*)/.exec(table[6])[1];

return [
tableName,
columnName,
columnDataType
];
});

return ({
columnNames: [ 'tablename', 'column_name', 'data_type' ],
rows
});
})
.catch(err => {
Logger.log(err);
throw err;
});
}

export function query(queryString, connection) {
const { owner, id } = parseUrl(connection.url);
const params = `${encodeURIComponent('query')}=${encodeURIComponent(queryString)}`;

return fetch(`https://api.data.world/v0/sql/${owner}/${id}?includeTableSchema=true`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${connection.token}`,
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'User-Agent': `Falcon/Plotly - ${process.env.npm_package_version}`
},
body: params
})
.then(res => {
return res.json();
})
.then(json => {
const fields = json[0].fields;
const columnnames = fields.map((field) => {
return field.name;
});
const rows = json.slice(1).map((row) => {
return Object.values(row);
});

return ({
columnnames,
rows
});
})
.catch(err => {
Logger.log(err);
throw err;
});
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"test-unit-all-watch": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --bail --full-trace --timeout 90000 --compilers js:babel-register --recursive test/**/*.spec.js --watch",
"test-unit-watch": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --bail --full-trace --timeout 90000 --watch --compilers js:babel-register ",
"test-unit-certificates": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/certificates.spec.js",
"test-unit-dataworld": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.dataworld.spec.js",
"test-unit-ibmdb": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.ibmdb.spec.js",
"test-unit-impala": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.impala.spec.js",
"test-unit-livy": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.livy.spec.js",
Expand Down Expand Up @@ -168,6 +169,7 @@
"json-loader": "^0.5.4",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"nock": "^9.1.5",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To help with testing, could you add a script to test only this connector, i.e.:

"test-unit-dataworld": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 electron-mocha --full-trace --timeout 90000 --compilers js:babel-register test/backend/datastores.dataworld.spec.js"

"node-fetch": "^1.7.2",
"node-impala": "^2.0.4",
"plotly.js": "^1.31.2",
Expand Down
Loading