-
-
Notifications
You must be signed in to change notification settings - Fork 278
csv: implement file drag and drop #385
Changes from 6 commits
041b286
1553211
0722340
cbc7113
f3df19f
30f4161
e83bce4
2d27534
caf6f2f
1f58fdf
f0effc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
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 | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Code looks clean and easy to understand. Do you think this component is worth having a Jest test for or is this overkill? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've just added the tests |
||
* @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 ( | ||
<div | ||
className={'inputContainer'} | ||
onDragOver={onDragOver} | ||
onDrop={onDrop} | ||
> | ||
<label className={'label'}> | ||
{inputLabel} | ||
</label> | ||
<div className={'wrapInput'}> | ||
<input | ||
style={{'background-color': (drag || dropValue) ? 'lightcyan' : null}} | ||
onChange={onChange} | ||
onDragEnter={onDragEnter} | ||
onDragLeave={onDragLeave} | ||
value={inputValue} | ||
placeholder={placeholder} | ||
type={'text'} | ||
/> | ||
<small style={{ | ||
clear: 'both', | ||
float: 'left', | ||
'margin-left': '20px' | ||
}} > | ||
{dropLabel} | ||
</small> | ||
<div style={sampleCredentialsStyle}> | ||
<code> | ||
{sampleCredential} | ||
</code> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
|
||
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(); | ||
setState({drag: false}); | ||
|
||
const files = event.dataTransfer.files; | ||
if (!files || files.length !== 1) { | ||
return; | ||
} | ||
|
||
const file = files[0]; | ||
const reader = new FileReader(); | ||
reader.onload = () => { | ||
setState({ | ||
dropValue: reader.result, | ||
inputValue: file.name | ||
}); | ||
updateConnection({ | ||
[value]: reader.result, | ||
label: file.name | ||
}); | ||
}; | ||
reader.readAsDataURL(file); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -70,13 +71,24 @@ export function query(queryObject, connection) { | |
} | ||
|
||
/* | ||
* connect functions attempt to ping the connection and | ||
* return a promise that is empty | ||
* connect attempts to ping the connection and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this JSDocs format? Should it be @params {object} connection There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a commit to format it as JSDoc |
||
* returns a promise that resolves to the connection object | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @returns {promise} an empty promise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This depends on the connector and it's likely to change once Falcon becomes smarter about the connections it creates (at the moment, most of the connectors create a new connection for every request, which is OK if the DB driver implements a pool or the request is stateless). I'll reword it, so that it only states that the promise resolves once the connection succeeds. |
||
*/ | ||
export function connect(connection) { | ||
return getDatastoreClient(connection).connect(connection); | ||
} | ||
|
||
/* | ||
* disconnect closes the connection and | ||
* returns a promise that resolves to the connection object | ||
*/ | ||
export function disconnect(connection) { | ||
const client = getDatastoreClient(connection); | ||
return (client.disconnect) ? | ||
client.disconnect(connection) : | ||
Promise.resolve(connection); | ||
} | ||
|
||
/* SQL-like Connectors */ | ||
|
||
/* | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General Comment. I like the style how you have documented the JSDocs and React class structure. I have noticed that there seems to be some inconsistency with the React Classes. Do we have a standard format? I have noticed some classes have PropTypes at the bottom. Is there a standard? Should we document this somewhere? If so where is a good place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shannonlal
We don't have any guidelines. I'd keep them in a file:
CODING_GUIDELINES.md
.I don't want the guidelines to become a burden (it's easier for us to tell contributors to use given files as models; e.g: we could chose one of the files in the project as a model for stateless components, another file for stateful components, and another for components that use redux). Also, by burden, I mean:
PropTypes
go at the top is OK, because it's just a copy'n'paste.