diff --git a/app/components/Settings/ConnectButton/ConnectButton.react.js b/app/components/Settings/ConnectButton/ConnectButton.react.js
index 82eea5b95..d8038b37a 100644
--- a/app/components/Settings/ConnectButton/ConnectButton.react.js
+++ b/app/components/Settings/ConnectButton/ConnectButton.react.js
@@ -2,19 +2,19 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {pathOr} from 'ramda';
-/**
- * The following is the Connect Button which triggers the connection
- * @param {function} connect - Connect function
- * @param {object} connectRequest - Connection Request
- * @param {number || string} connectRequest.status -- 400 or loading
- * @param {Error} connectRequest.error
- * @param {object} saveConnectionsRequest - Saved Connection Request
- * @param {number || string } saveConnectionsRequest.status -- 400 or loading
- * @param {Error} saveConnectionsRequest.error
- * @param {boolean} editMode - Enabled if Editting credentials
- * @returns {ConnectButton}
- */
export default class ConnectButton extends Component {
+ /**
+ * Component props
+ * @type {object} props
+ * @property {function} props.connect - Connect function
+ * @property {object} props.connectRequest - Connection Request
+ * @property {(number|string)} props.connectRequest.status - 400 or loading
+ * @property {Error} props.connectRequest.error
+ * @property {object} props.saveConnectionsRequest - Saved Connection Request
+ * @property {(number|string)} props.saveConnectionsRequest.status - 400 or loading
+ * @property {Error} props.saveConnectionsRequest.error
+ * @property {boolean} props.editMode - Enabled if editing credentials
+ */
static propTypes = {
connect: PropTypes.func,
connectRequest: PropTypes.object,
@@ -118,4 +118,4 @@ export default class ConnectButton extends Component {
);
}
-}
\ No newline at end of file
+}
diff --git a/app/components/Settings/Preview/TableTree.react.js b/app/components/Settings/Preview/TableTree.react.js
index 2d268fa5b..13dba6e98 100644
--- a/app/components/Settings/Preview/TableTree.react.js
+++ b/app/components/Settings/Preview/TableTree.react.js
@@ -30,11 +30,13 @@ class TableTree extends Component {
getLabel(connectionObject) {
switch (connectionObject.dialect) {
case DIALECTS.SQLITE:
- return BASENAME_RE.exec(connectionObject.storage)[0] || connectionObject.storage;
+ return BASENAME_RE.exec(connectionObject.storage)[0] || connectionObject.storage;
case DIALECTS.DATA_WORLD:
- return getPathNames(connectionObject.url)[2];
+ return getPathNames(connectionObject.url)[2];
+ case DIALECTS.CSV:
+ return connectionObject.label || connectionObject.id || connectionObject.database;
default:
- return connectionObject.database;
+ return connectionObject.database;
}
}
diff --git a/app/components/Settings/Tabs/Tab.react.js b/app/components/Settings/Tabs/Tab.react.js
index 03be19323..a62d4f05d 100644
--- a/app/components/Settings/Tabs/Tab.react.js
+++ b/app/components/Settings/Tabs/Tab.react.js
@@ -33,7 +33,7 @@ export default class ConnectionTab extends Component {
} else if (dialect === DIALECTS.APACHE_SPARK) {
label = `Apache Spark (${connectionObject.host}:${connectionObject.port})`;
} else if (connectionObject.dialect === DIALECTS.CSV) {
- label = `CSV (${connectionObject.database})`;
+ label = connectionObject.label || connectionObject.id || connectionObject.database;
} else if (connectionObject.dialect === DIALECTS.ELASTICSEARCH) {
label = `Elasticsearch (${connectionObject.host})`;
} else if (connectionObject.dialect === DIALECTS.SQLITE) {
diff --git a/app/components/Settings/UserConnections/UserConnections.react.js b/app/components/Settings/UserConnections/UserConnections.react.js
index c9939144d..04058fdb1 100644
--- a/app/components/Settings/UserConnections/UserConnections.react.js
+++ b/app/components/Settings/UserConnections/UserConnections.react.js
@@ -1,6 +1,9 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
+import Filedrop from './filedrop.jsx';
+
import {contains} from 'ramda';
+
import {CONNECTION_CONFIG, SAMPLE_DBS} from '../../../constants/constants';
import {dynamicRequireElectron} from '../../../utils/utils';
@@ -151,6 +154,15 @@ export default class UserConnections extends Component {
);
+ } else if (setting.type === 'filedrop') {
+ input = (
+
+ );
}
return (
diff --git a/app/components/Settings/UserConnections/filedrop.jsx b/app/components/Settings/UserConnections/filedrop.jsx
new file mode 100644
index 000000000..7f1f52094
--- /dev/null
+++ b/app/components/Settings/UserConnections/filedrop.jsx
@@ -0,0 +1,172 @@
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+
+import {SAMPLE_DBS} from '../../../constants/constants';
+
+export default class Filedrop extends Component {
+ static propTypes = {
+ settings: PropTypes.object,
+ connection: PropTypes.object,
+ updateConnection: PropTypes.func,
+ sampleCredentialsStyle: PropTypes.object
+ }
+
+ /**
+ * Filedrop is an input component where users can type an URL or drop a file
+ *
+ * @param {object} props - Component properties
+ *
+ * @param {object} props.settings - FileDrop settings
+ * @param {string} props.settings.type - Set to 'filedrop'
+ * @param {string} props.settings.value - Target property in the connection object
+ * @param {string} props.settings.inputLabel - Label for input box
+ * @param {string} props.settings.dropLabel - Label for drop box
+ * @param {string} props.settings.placeholder - Placeholder for input box
+ *
+ * @param {object} props.connection - Connection object
+ * @param {string} props.connection.dialect - Connection dialect
+ * @param {string} props.connection.label - Connection label
+ *
+ * @param {function} props.updateConnection - Callback to update the connection object
+ *
+ * @param {object} props.sampleCredentialsStyle - To control the display of sample credentials
+ */
+ constructor(props) {
+ super(props);
+
+ const {
+ settings,
+ connection
+ } = this.props;
+
+ const url = connection[settings.value];
+
+ /**
+ * @member {object} state - Component state
+ * @property {string} state.inputValue - Value typed into the input box
+ * @property {string} state.dropValue - Data URL dropped into the drop box
+ */
+ this.state = (typeof url === 'string' && url.startsWith('data:')) ? {
+ inputValue: connection.label || url.slice(0, 64),
+ dropValue: url
+ } : {
+ inputValue: url || '',
+ dropValue: ''
+ };
+ }
+
+
+ render() {
+ const {
+ settings,
+ connection,
+ updateConnection,
+ sampleCredentialsStyle
+ } = this.props;
+
+ const {
+ inputValue,
+ dropValue,
+ drag
+ } = this.state;
+
+ const setState = this.setState.bind(this);
+
+ const {
+ value,
+ inputLabel,
+ dropLabel,
+ placeholder
+ } = settings;
+
+ const {dialect} = connection;
+
+ const sampleCredential = (SAMPLE_DBS[dialect]) ? SAMPLE_DBS[dialect][value] : null;
+
+ return (
+
+
+
+
+
+ {dropLabel}
+
+
+
+ {sampleCredential}
+
+
+
+
+ );
+
+ function onChange(event) {
+ setState({
+ inputValue: event.target.value,
+ dropValue: ''
+ });
+ updateConnection({
+ [value]: event.target.value,
+ label: event.target.value
+ });
+ }
+
+ function onDragEnter(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ setState({drag: true});
+ }
+
+ function onDragOver(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ event.dataTransfer.dropEffect = 'copy';
+ }
+
+ function onDragLeave(event) {
+ event.stopPropagation();
+ event.preventDefault();
+ setState({drag: false});
+ }
+
+ function onDrop(event) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ const files = event.dataTransfer.files;
+ if (!files || files.length !== 1) {
+ setState({drag: false});
+ return;
+ }
+
+ const file = files[0];
+ const reader = new FileReader();
+ reader.onload = () => {
+ setState({
+ drag: false,
+ dropValue: reader.result,
+ inputValue: file.name
+ });
+ updateConnection({
+ [value]: reader.result,
+ label: file.name
+ });
+ };
+ reader.readAsDataURL(file);
+ }
+ }
+}
diff --git a/app/constants/constants.js b/app/constants/constants.js
index 7de987bbc..187433f3c 100644
--- a/app/constants/constants.js
+++ b/app/constants/constants.js
@@ -76,9 +76,12 @@ const hadoopQLOptions = [
export const CONNECTION_CONFIG = {
[DIALECTS.APACHE_IMPALA]: hadoopQLOptions,
[DIALECTS.APACHE_SPARK]: hadoopQLOptions,
- [DIALECTS.CSV]: [
- {'label': 'URL to CSV File', 'value': 'database', 'type': 'text'}
- ],
+ [DIALECTS.CSV]: [{
+ 'inputLabel': 'Type URL to a CSV file',
+ 'dropLabel': '(or drop a CSV file here)',
+ 'value': 'database',
+ 'type': 'filedrop'
+ }],
[DIALECTS.IBM_DB2]: commonSqlOptions,
[DIALECTS.MYSQL]: commonSqlOptions,
[DIALECTS.MARIADB]: commonSqlOptions,
diff --git a/backend/init.js b/backend/init.js
new file mode 100644
index 000000000..9ac75bcb4
--- /dev/null
+++ b/backend/init.js
@@ -0,0 +1,25 @@
+import Logger from './logger';
+
+import {
+ deleteAllConnections,
+ deleteBadConnections
+} from './persistent/Connections.js';
+import {getSetting} from './settings.js';
+
+const setCSVStorageSize = require('./persistent/datastores/csv.js').setStorageSize;
+
+export default function init() {
+ try {
+ deleteBadConnections();
+ } catch (error) {
+ Logger.log(`Failed to delete bad connections: ${error.message}`);
+ deleteAllConnections();
+ }
+
+ try {
+ setCSVStorageSize(getSetting('CSV_STORAGE_SIZE'));
+ } catch (error) {
+ Logger.log(`Failed to get setting CSV_STORAGE_SIZE: ${error.message}`);
+ setCSVStorageSize(0);
+ }
+}
diff --git a/backend/persistent/Connections.js b/backend/persistent/Connections.js
index b6562143a..58c31cb07 100644
--- a/backend/persistent/Connections.js
+++ b/backend/persistent/Connections.js
@@ -5,6 +5,7 @@ import {assoc, dissoc, findIndex} from 'ramda';
import uuid from 'uuid';
import YAML from 'yamljs';
import * as Datastores from './datastores/Datastores.js';
+import {DIALECTS} from '../../app/constants/constants.js';
import {getSetting} from '../settings';
@@ -39,11 +40,32 @@ export function deleteConnectionById(id) {
const connections = getConnections();
const index = findIndex(connection => connection.id === id, connections);
if (index > -1) {
+ Datastores.disconnect(connections[index]);
connections.splice(index, 1);
fs.writeFileSync(getSetting('CONNECTIONS_PATH'), YAML.stringify(connections, 4));
}
}
+export function deleteBadConnections() {
+ getConnections().forEach(connection => {
+ const {id, dialect} = connection;
+
+ const dialects = Object.getOwnPropertyNames(DIALECTS).map(k => DIALECTS[k]);
+
+ const isUnknownDialect = (dialects.indexOf(dialect) === -1);
+ if (isUnknownDialect) {
+ deleteConnectionById(id);
+ }
+ });
+}
+
+export function deleteAllConnections() {
+ if (!fs.existsSync(getSetting('STORAGE_PATH'))) {
+ createStoragePath();
+ }
+ fs.writeFileSync(getSetting('CONNECTIONS_PATH'), YAML.stringify([], 4));
+}
+
export function getSanitizedConnections() {
const connections = getConnections();
return connections.map(cred => sanitize(cred));
@@ -60,7 +82,7 @@ export function saveConnection(connectionObject) {
return connectionId;
}
-export function validateConnection (connectionObject) {
+export function validateConnection(connectionObject) {
return Datastores.connect(connectionObject).then(() => {
return {};
}).catch(err => {
diff --git a/backend/persistent/datastores/Datastores.js b/backend/persistent/datastores/Datastores.js
index e35a85ce9..8817079e8 100644
--- a/backend/persistent/datastores/Datastores.js
+++ b/backend/persistent/datastores/Datastores.js
@@ -5,10 +5,11 @@ import * as ApacheDrill from './ApacheDrill';
import * as IbmDb2 from './ibmdb2';
import * as ApacheLivy from './livy';
import * as ApacheImpala from './impala';
-import * as CSV from './csv';
import * as DataWorld from './dataworld';
import * as DatastoreMock from './datastoremock';
+const CSV = require('./csv');
+
/*
* Switchboard to all of the different types of connections
* that we support.
@@ -55,45 +56,62 @@ function getDatastoreClient(connection) {
return Sql;
}
-/*
- * query functions take a configuration, query a connection and
- * return a promise with the results as an object:
- *
- * {
- * rows: [...],
- * columnnames: [...]
- * }
- *
+/**
+ * query makes a query
+ * @param {(object|string)} queryStatement Query
+ * @param {object} connection Connection object
+ * @returns {Promise.