Skip to content
Open
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions src/components/BrowserCell/BrowserCell.react.js
Original file line number Diff line number Diff line change
@@ -127,8 +127,8 @@ export default class BrowserCell extends Component {
} else if (this.props.type === 'Object' || this.props.type === 'Bytes') {
this.copyableValue = content = JSON.stringify(this.props.value);
} else if (this.props.type === 'File') {
const fileName = this.props.value.url() ? getFileName(this.props.value) : 'Uploading\u2026';
content = <Pill value={fileName} fileDownloadLink={this.props.value.url()} shrinkablePill />;
const fileName = this.props.value.url?.() ? getFileName(this.props.value) : 'Uploading\u2026';
content = <Pill value={fileName} fileDownloadLink={this.props.value.url?.()} shrinkablePill />;
this.copyableValue = fileName;
} else if (this.props.type === 'ACL') {
let pieces = [];
2 changes: 1 addition & 1 deletion src/components/FileEditor/FileEditor.react.js
Original file line number Diff line number Diff line change
@@ -80,7 +80,7 @@ export default class FileEditor extends React.Component {
return (
<div ref={this.inputRef} style={{ minWidth: this.props.width, display: 'none' }} className={styles.editor}>
<a className={styles.upload}>
<input ref={this.fileInputRef} id='fileInput' type='file' onChange={this.handleChange.bind(this)} />
<input ref={this.fileInputRef} id='fileInput' type='file' onChange={this.handleChange.bind(this)} accept={this.props.accept} />
<span>{file ? 'Replace file' : 'Upload file'}</span>
</a>
</div>
2 changes: 1 addition & 1 deletion src/components/Modal/Modal.scss
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@
position: absolute;
font-size: 14px;
color: white;
top: 52px;
top: 56px;
left: 28px;
}

102 changes: 101 additions & 1 deletion src/dashboard/Data/Browser/Browser.react.js
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ import DeleteRowsDialog from 'dashboard/Data/Browser/DeleteRow
import DropClassDialog from 'dashboard/Data/Browser/DropClassDialog.react';
import EmptyState from 'components/EmptyState/EmptyState.react';
import ExportDialog from 'dashboard/Data/Browser/ExportDialog.react';
import ImportDialog from 'dashboard/Data/Browser/ImportDialog.react';
import AttachRowsDialog from 'dashboard/Data/Browser/AttachRowsDialog.react';
import AttachSelectedRowsDialog from 'dashboard/Data/Browser/AttachSelectedRowsDialog.react';
import CloneSelectedRowsDialog from 'dashboard/Data/Browser/CloneSelectedRowsDialog.react';
@@ -57,6 +58,7 @@ class Browser extends DashboardView {
showRemoveColumnDialog: false,
showDropClassDialog: false,
showExportDialog: false,
showImportDialog: false,
showExportSchemaDialog: false,
showAttachRowsDialog: false,
showEditRowDialog: false,
@@ -105,6 +107,7 @@ class Browser extends DashboardView {
this.showDeleteRows = this.showDeleteRows.bind(this);
this.showDropClass = this.showDropClass.bind(this);
this.showExport = this.showExport.bind(this);
this.showImport = this.showImport.bind(this);
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.toggleMasterKeyUsage = this.toggleMasterKeyUsage.bind(this);
@@ -282,6 +285,10 @@ class Browser extends DashboardView {
this.setState({ showExportDialog: true });
}

showImport() {
this.setState({ showImportDialog: true });
}

async login(username, password) {
if (Parse.User.current()) {
await Parse.User.logOut();
@@ -1125,6 +1132,7 @@ class Browser extends DashboardView {
this.state.showRemoveColumnDialog ||
this.state.showDropClassDialog ||
this.state.showExportDialog ||
this.state.showImportDialog ||
this.state.showExportSchema ||
this.state.rowsToDelete ||
this.state.showAttachRowsDialog ||
@@ -1445,6 +1453,89 @@ class Browser extends DashboardView {
}
}

async confirmImport(file) {
this.setState({ showImportDialog: null });
const className = this.props.params.className;
const classColumns = this.getClassColumns(className, false);
const columnsObject = {};
classColumns.forEach((column) => {
columnsObject[column.name] = column;
});
const { base64, type} = file._source;
if (type === 'text/csv') {
const csvToArray =(text) => {
let p = '', row = [''], ret = [row], i = 0, r = 0, s = !0, l;
for (l of text) {
if ('"' === l) {
if (s && l === p) row[i] += l;
s = !s;
} else if (',' === l && s) l = row[++i] = '';
else if ('\n' === l && s) {
if ('\r' === p) row[i] = row[i].slice(0, -1);
row = ret[++r] = [l = '']; i = 0;
} else row[i] += l;
p = l;
}
return ret;
};
const csv = atob(base64);
const [columns, ...rows] = csvToArray(csv);
await Parse.Object.saveAll(rows.filter(row => row.join() !== '').map(row => {
const json = {className};
for (let i = 1; i < row.length; i++) {
const column = columns[i];
const value = row[i];
if (value === 'null') {
continue;
}
const {type, targetClass, name} = columnsObject[column] || {};
if (type === 'Relation') {
json[column] = {
__type: 'Relation',
className: targetClass,
};
continue;
}
if (type === 'Pointer') {
json[column] = {
__type: 'Pointer',
className: targetClass,
objectId: value,
};
continue;
}
if (name === 'ACL') {
json.ACL = new Parse.ACL(JSON.parse(value));
continue;
}
if (type === 'Date') {
json[column] = new Date(value);
continue;
}
if (type === 'Boolean') {
json[column] = value === 'true';
continue;
}
if (type === 'String') {
json[column] = value;
continue;
}
if (type === 'Number') {
json[column] = Number(value);
continue;
}
try {
json[column] = JSON.parse(value);
} catch (e) {
/* */
}
}
return Parse.Object.fromJSON(json, false, true);
}), {useMasterKey: true});
}
this.refresh();
}

getClassRelationColumns(className) {
const currentClassName = this.props.params.className;
return this.getClassColumns(className, false)
@@ -1640,6 +1731,7 @@ class Browser extends DashboardView {
onExport={this.showExport}
onChangeCLP={this.handleCLPChange}
onRefresh={this.refresh}
onImport={this.showImport}
onAttachRows={this.showAttachRowsDialog}
onAttachSelectedRows={this.showAttachSelectedRowsDialog}
onCloneSelectedRows={this.showCloneSelectedRowsDialog}
@@ -1761,6 +1853,14 @@ class Browser extends DashboardView {
onCancel={() => this.setState({ showExportDialog: false })}
onConfirm={() => this.exportClass(className)} />
);
}
else if (this.state.showImportDialog) {
extras = (
<ImportDialog
className={className}
onCancel={() => this.setState({ showImportDialog: false })}
onConfirm={(file) => this.confirmImport(file)} />
);
} else if (this.state.showExportSchemaDialog) {
extras = (
<ExportSchemaDialog
@@ -1776,7 +1876,7 @@ class Browser extends DashboardView {
onCancel={this.cancelAttachRows}
onConfirm={this.confirmAttachRows}
/>
)
);
} else if (this.state.showAttachSelectedRowsDialog) {
extras = (
<AttachSelectedRowsDialog
6 changes: 6 additions & 0 deletions src/dashboard/Data/Browser/BrowserToolbar.react.js
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ let BrowserToolbar = ({
onDropClass,
onChangeCLP,
onRefresh,
onImport,
onEditPermissions,
hidePerms,
isUnique,
@@ -265,6 +266,11 @@ let BrowserToolbar = ({
</BrowserMenu>
)}
{onAddRow && <div className={styles.toolbarSeparator} />}
<a className={classes.join(' ')} onClick={isPendingEditCloneRows ? null : onImport}>
<Icon name="up-solid" width={14} height={14} />
<span>Import</span>
</a>
<div className={styles.toolbarSeparator} />
<a className={classes.join(' ')} onClick={isPendingEditCloneRows ? null : onRefresh}>
<Icon name="refresh-solid" width={14} height={14} />
<span>Refresh</span>
66 changes: 66 additions & 0 deletions src/dashboard/Data/Browser/ImportDialog.react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import Modal from 'components/Modal/Modal.react';
import FileEditor from 'components/FileEditor/FileEditor.react';
import React from 'react';
import Pill from 'components/Pill/Pill.react';
import getFileName from 'lib/getFileName';
import { CurrentApp } from 'context/currentApp';

export default class ImportDialog extends React.Component {
constructor() {
super();
this.state = {
file: null,
showFileEditor: false
};
}

openFileEditor() {
this.setState({
showFileEditor: true
});
}

hideFileEditor(file) {
this.setState({
showFileEditor: false,
file
});
}

render() {
return (
<div>
<Modal
type={Modal.Types.INFO}
icon='up-outline'
iconSize={40}
title={`Import Data into ${this.props.className}`}
subtitle='Note: Please make sure columns are defined in SCHEMA to import.'
confirmText='Import'
cancelText='Cancel'
disabled={!this.state.file}
buttonsInCenter={true}
onCancel={this.props.onCancel}
onConfirm={() => this.props.onConfirm(this.state.file)}>
<div style={{ padding: '25px' }}>
{this.state.file && <Pill value={getFileName(this.state.file) }/>}
<div style={{ cursor: 'pointer' }}>
<Pill
value={this.state.file ? 'Change file' : 'Select file'}
onClick={() => this.openFileEditor()}
/>
{this.state.showFileEditor && (
<FileEditor
value={this.state.file}
accept='.csv'
onCommit={(file) => this.hideFileEditor(file)}
onCancel={() => this.hideFileEditor()}
/>
)}
</div>
</div>
</Modal>
</div>
);
}
}