You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@couchdb.apache.org by michellephung <gi...@git.apache.org> on 2015/08/10 16:52:52 UTC

[GitHub] couchdb-fauxton pull request: WIP Data importer

GitHub user michellephung opened a pull request:

    https://github.com/apache/couchdb-fauxton/pull/495

    WIP Data importer

    To Do:
    
    - add error handling when file uploaded is bigger than max
    - add ability to create new database, then store data into it
    - fix ui
    - right tests

You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/michellephung/couchdb-fauxton data-importer

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/couchdb-fauxton/pull/495.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #495
    
----
commit 23c5d3dd6f84c74c45be42fd161007a47fd6bd86
Author: michellephung@gmail.com <mi...@gmail.com>
Date:   2015-06-15T19:04:55Z

    Add Data Importer

commit b12ac18ff6de19f8668d04c022974da9d166c936
Author: michellephung@gmail.com <mi...@gmail.com>
Date:   2015-07-30T18:34:02Z

    ok css

commit 40aca08e6aa299d319f0ec30c2d70320e8443428
Author: michellephung@gmail.com <mi...@gmail.com>
Date:   2015-07-31T16:43:53Z

    stuff

commit b0d8cb9f10c2c891f2445c13d515d901877ddf17
Author: Michelle Phung <mi...@apache.org>
Date:   2015-08-04T21:05:39Z

    padding

commit 939d30200239d01cf6831146cca877703a32029e
Author: Michelle Phung <mi...@apache.org>
Date:   2015-08-04T23:26:16Z

    css good to go

commit 8d87c22072e119d905496afc36820fb48a7d1716
Author: Michelle Phung <mi...@apache.org>
Date:   2015-08-06T23:00:21Z

    works!!!

commit 48292bb5859f65797f750f5c487bbccbd5ef1946
Author: Michelle Phung <mi...@apache.org>
Date:   2015-08-06T23:19:25Z

    working with bulk_docs!!!

commit 81a6ea752b6016f350c3641b5c260c3bd51079a7
Author: Michelle Phung <mi...@apache.org>
Date:   2015-08-07T14:50:02Z

    clean up

----


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960749
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    --- End diff --
    
    any reason not to use jquery?
    
    ```js
    $('preview-page').on('scroll', this.handleScroll);
    ```
    
    ```js
    $('preview-page').off('scroll');
    ```
    



---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960170
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer 
    +            getAllDBs={this.props.getAllDBs} 
    +            chunkedData={this.props.chunkedData} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel: 'Table',
    +        rightLabel: 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView != 'table') {
    +        return null;
    --- End diff --
    
    strict equal please


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39332384
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.dataBaseIsnew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    dataBaseIsnew: function (targetDB) {
    --- End diff --
    
    lol. this looks so silly now.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39320186
  
    --- Diff: app/addons/components/assets/less/small-dropdown.less ---
    @@ -0,0 +1,64 @@
    +@import "../../../../../assets/less/mixins.less";
    --- End diff --
    
    License header.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39325052
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.dataBaseIsnew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    dataBaseIsnew: function (targetDB) {
    --- End diff --
    
    Camel-case police. Should be:  `databaseIsNew`


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960930
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    --- End diff --
    
    I think if you move the error message into the `DataImporterController` or another parent element you don't have to calculate the left value all the time which is costing a lot of performance as it triggers a repaint in the browser on every pixel that is scrolled
    
    https://gist.github.com/paulirish/5d52fb081b3570c81e3a


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39514681
  
    --- Diff: .gitignore ---
    @@ -16,12 +16,14 @@ app/addons/*
     !app/addons/fauxton
     !app/addons/databases
     !app/addons/documents
    +!app/addons/dataimporter
     !app/addons/styletests
     !app/addons/cors
     settings.json*
     i18n.json
     !settings.json.default
     !assets/js/plugins/zeroclipboard/ZeroClipboard.swf
    +!assets/js/libs/papaparse.js
    --- End diff --
    
    you can remove it


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324394
  
    --- Diff: app/addons/dataimporter/routes.js ---
    @@ -0,0 +1,49 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/components.react',
    +  'addons/dataimporter/actions'
    +],
    +
    +function (app, FauxtonAPI, DataImporterComponents, DataImporterActions) {
    +
    +  //state is in case you move away from the page and return
    +  var firstTimeHere = true;
    +
    +  var DataImporterRouteObject = FauxtonAPI.RouteObject.extend({
    +    selectedHeader: 'Import Data',
    +    disableLoader: true,
    +    layout : 'one_pane',
    +    crumbs: [
    +      {'name': 'Import Data', 'link': '/dataimporter'}
    +    ],
    +
    +    routes: {
    +      'dataimporter': 'dataimporter'
    +    },
    +
    +    roles: ['fx_loggedIn'],
    +
    +    dataimporter: function () {
    --- End diff --
    
    maybe `dataImporter`


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-139666922
  
    Need to incluse papaparse.min.js license into NOTICE file.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324540
  
    --- Diff: .gitignore ---
    @@ -33,3 +35,5 @@ test/nightwatch_tests/selenium/*
     *.react.js
     selenium-debug.log
     npm-debug.log
    +
    +assets/js/libs/papaparse.js
    --- End diff --
    
    i will fix this



---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39323767
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView === 'table') {
    +        return (
    +          <table className="data-import-table">
    +            <tbody>
    +              <tr>{header}</tr>
    +              {data}
    +            </tbody>
    +          </table>
    +        );
    +      } else {
    +        return null;
    +      }
    --- End diff --
    
    You could probably refactor this a bit to just add a:
    ```if (this.props.getPreviewView !== 'table') { 
      return false;
    }``` at the top, then you can drop the if-else.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r40654510
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    --- End diff --
    
    no special reason for not using jquery. I just felt like it. 
    
    this jquery rerendering part was a tough decision. It's expensive performance-wise, but when I spent a while on the css for this, and considered how it would look to people who have the scrolls always on. It looks weird.
    
    It gets even more hairy when we put the component into its own parent element, then also the parent element won't move out of screen when we scroll down.  This is the UI I wanted, but the it's at the expense of performance, for now. There a few logic things I'd like to tackle, before I redo the CSS.  I'd like to redo that in another PR.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-130388716
  
    https://gist.github.com/michellephung/c5dbf354862a447ae2bd other color options



---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960592
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    --- End diff --
    
    ```js
    
        render: function () {
    
          var fileInfoMessage =
                this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
              style = {'left': this.state.left};
    
          var view = (<JSONView
                  data={this.props.data}
                  isBigFile={this.props.isBigFile}
                  meta={this.props.meta}
                  getSmallPreviewOfData={this.props.getSmallPreviewOfData}
                  getHeaderConfig={this.props.getHeaderConfig} />);
    
          if (this.props.getPreviewView === 'table') {
            view = (<TableView
                  data={this.props.data}
                  isBigFile={this.props.isBigFile}
                  meta={this.props.meta}
                  getSmallPreviewOfData={this.props.getSmallPreviewOfData}
                  getHeaderConfig={this.props.getHeaderConfig} />);
    
          }
     
          return (
            <div id="preview-page" >
              <div id="data-import-options" style={style}>
                {fileInfoMessage}
                <OptionsRow 
                  getDelimiterChosen={this.props.getDelimiterChosen}
                  filesize={this.props.filesize} />
              </div>
              <div className="preview-data-space">
              {view}
              </div>
              <Footer 
                getAllDBs={this.props.getAllDBs} 
                chunkedData={this.props.chunkedData} />
            </div>
          );
        }
      });
    ```


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-140043355
  
    can you format the code, especially for the components? the look quite different compared to the ones we have right now


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39515042
  
    --- Diff: .gitignore ---
    @@ -16,12 +16,14 @@ app/addons/*
     !app/addons/fauxton
     !app/addons/databases
     !app/addons/documents
    +!app/addons/dataimporter
     !app/addons/styletests
     !app/addons/cors
     settings.json*
     i18n.json
     !settings.json.default
     !assets/js/plugins/zeroclipboard/ZeroClipboard.swf
    +!assets/js/libs/papaparse.js
    --- End diff --
    
    reason: assets is not ignored in this gitignore, so we also don't have to whitelist the file. the reason zeroclipboard is listed here is that *swf files where blacklisted some time ago, but it seems that isn't the case any more


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39383090
  
    --- Diff: app/addons/dataimporter/tests/componentsSpec.react.jsx ---
    @@ -0,0 +1,114 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +define([
    +  'api',
    +  'addons/dataimporter/components.react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'testUtils',
    +  'react'
    +], function (FauxtonAPI, Components, Stores, Actions, utils, React) {
    +  FauxtonAPI.router = new FauxtonAPI.Router([]);
    +
    +  var assert = utils.assert;
    +  var TestUtils = React.addons.TestUtils;
    +  var restore = utils.restore;
    +  var maxSize = 150000000;
    +
    +  describe('Data Importer -- Components', function () {
    +    var spy, start_container, preview_container, El, dataImportStore, importer, previewScreen;
    +
    +    beforeEach(function () {
    +      start_container = document.createElement('div');
    +      preview_container = document.createElement('div');
    --- End diff --
    
    snake_case?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39639094
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,382 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.js';
    +
    +  var DATA_IMPORTER_NUMBERS = {
    +    BIG_FILE_SIZE_CAP: 750000,
    +    FILE_MAX_SIZE: 150000000,  //in bytes
    +    REPEAT_EVERY_3_SECONDS: 3000,
    +    MAX_ROWS_TO_PREVIEW: 500,
    +    DATA_ROW_CHUNKS: 500
    +  };
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this.reset();
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +    },
    +
    +    reset: function () {
    +      this._isDataCurrentlyLoading = false;
    +      this._hasDataLoaded = false;
    +      this._hasErrored = false;
    +      this._theData = [];
    +      this._theMetadata = [];
    +      this._smallData = [];
    +      this._showView = 'table';
    +      this._theFile = { size: 0 };
    +      this._config = this.getDefaultConfig();
    +      this._completeFn = this._config.complete;
    +      this._errorFn = this._config.error;
    +      this._fileSize = 0;
    +      this._time = "just started";
    +      this._repeatTimeID;
    +      this.fetchAllDBs();
    +      this._chunkedData = [];
    +      this._maxSize = DATA_IMPORTER_NUMBERS.FILE_MAX_SIZE;
    +      this._showErrorScreen = false;
    +      this._errorMessageArray = [];
    +      this._loadingInDBInProgress = false;
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > DATA_IMPORTER_NUMBERS.BIG_FILE_SIZE_CAP ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), DATA_IMPORTER_NUMBERS.REPEAT_EVERY_3_SECONDS);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = DATA_IMPORTER_NUMBERS.BIG_FILE_SIZE_CAP,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = (numberOfRowsToShow < DATA_IMPORTER_NUMBERS.MAX_ROWS_TO_PREVIEW) ?
    +        DATA_IMPORTER_NUMBERS.MAX_ROWS_TO_PREVIEW : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += DATA_IMPORTER_NUMBERS.DATA_ROW_CHUNKS) {
    +        var oneChunk = this._theData.slice(i, i + DATA_IMPORTER_NUMBERS.DATA_ROW_CHUNKS);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +
    +      // the following object shows all of the options available for papaparse
    +      // some are defaulted to undefined
    +      // this is from http://papaparse.com/docs#config
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.databaseIsNew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    databaseIsNew: function (targetDB) {
    +      return _.some(this._all_dbs, targetDB);
    +    },
    +
    +    showErrorScreen: function () {
    +      return this._showErrorScreen;
    +    },
    +
    +    getErrorMsg: function () {
    +      return this._errorMessageArray;
    +    },
    +
    +    goToErrorScreen: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this._showErrorScreen = true;
    +      if (resp) {
    +        messageArray.push(resp);
    +      }
    +      this._errorMessageArray.unshift(messageArray);
    +      this.triggerChange();
    +    },
    +
    +    loadIntoNewDB: function (targetDB) {
    +      $.ajax({
    +        url: FauxtonAPI.urls('databaseBaseURL', 'server', targetDB),
    +        xhrFields: { withCredentials: true },
    +        contentType: 'application/json; charset=UTF-8',
    +        method: 'PUT'
    +      }).then(function (resp) {
    +        this.loadDataIntoTarget(targetDB);
    +      }.bind(this), function (resp) {
    +        this.importFailed();
    +      }.bind(this));
    +    },
    +
    +    loadDataIntoTarget: function (targetDB) {
    +      var loadURL = FauxtonAPI.urls('document', 'server', targetDB, '_bulk_docs');
    +      _.each(this._chunkedData, function (data, i) {
    +        var payload = JSON.stringify({ 'docs': data });
    +        var prettyprint = JSON.stringify({ 'docs': data }, null, 2);
    +        $.ajax({
    +          url: loadURL,
    +          xhrFields: { withCredentials: true },
    +          contentType: 'application/json; charset=UTF-8',
    +          method: 'POST',
    +          data: payload
    +        }).then(function (resp) {
    +          i++;
    +          if (i === this._chunkedData.length ) {
    +            //console.log("alldone");
    +            this.successfulImport(targetDB);
    +            this.reset();
    +          }
    +        }.bind(this), function (resp) {
    +          this.importFailed(resp, ['There was an error loading documents into ' + targetDB, 'Data that failed to load:' + prettyprint]);
    +        }.bind(this));
    +      }.bind(this));
    +    },
    --- End diff --
    
    same here, ajax call in store


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39353479
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    --- End diff --
    
    Ah, I see.That's super weird of that lib. I'd leave it as you have it right now and just add a comment above the settings saying that that's what they are: the default values that may be configured for papaparse (+ the link to the doc).


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39638895
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,382 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.js';
    +
    +  var DATA_IMPORTER_NUMBERS = {
    +    BIG_FILE_SIZE_CAP: 750000,
    +    FILE_MAX_SIZE: 150000000,  //in bytes
    +    REPEAT_EVERY_3_SECONDS: 3000,
    +    MAX_ROWS_TO_PREVIEW: 500,
    +    DATA_ROW_CHUNKS: 500
    +  };
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this.reset();
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +    },
    +
    +    reset: function () {
    +      this._isDataCurrentlyLoading = false;
    +      this._hasDataLoaded = false;
    +      this._hasErrored = false;
    +      this._theData = [];
    +      this._theMetadata = [];
    +      this._smallData = [];
    +      this._showView = 'table';
    +      this._theFile = { size: 0 };
    +      this._config = this.getDefaultConfig();
    +      this._completeFn = this._config.complete;
    +      this._errorFn = this._config.error;
    +      this._fileSize = 0;
    +      this._time = "just started";
    +      this._repeatTimeID;
    +      this.fetchAllDBs();
    +      this._chunkedData = [];
    +      this._maxSize = DATA_IMPORTER_NUMBERS.FILE_MAX_SIZE;
    +      this._showErrorScreen = false;
    +      this._errorMessageArray = [];
    +      this._loadingInDBInProgress = false;
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    --- End diff --
    
    i think this belongs into an action?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung closed the pull request at:

    https://github.com/apache/couchdb-fauxton/pull/495


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39323903
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView === 'table') {
    +        return (
    +          <table className="data-import-table">
    +            <tbody>
    +              <tr>{header}</tr>
    +              {data}
    +            </tbody>
    +          </table>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id={"<UUID>_" + i}
    +                content={JSON.stringify(obj, null, ' ')}
    +                key={i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView === "json") {
    +        return (
    +          <div id="doc-list" className="json-view">
    +            {this.rows()}
    +          </div>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var Footer = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        targetDB: this.props.getAllDBs[0],
    +        selectExistingDB: true
    +      };
    +    },
    +
    +    startOverButton: function () {
    +      return (
    +        <a className="start-import-over-link footer-button"
    +          onClick={this.startover}>
    +          <span className="fonticon icon-repeat"></span>
    +            Start Over
    +        </a>
    +      );
    +    },
    +
    +    startover: function () {
    --- End diff --
    
    `startOver`


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39332362
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    --- End diff --
    
    http://papaparse.com/docs#config 
    
    it shows all of the options for papaparse. I left them in from the default so we could always see what options are available.
    
    Should I take them out?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39382801
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,796 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg = {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown = {this.state.rowShown}
    +            rowsTotal = {this.state.rowsTotal}
    +            data = {this.state.data}
    +            isBigFile = {this.state.isBigFile}
    +            meta = {this.state.meta}
    +            getPreviewView = {this.state.getPreviewView}
    +            getSmallPreviewOfData = {this.state.getSmallPreviewOfData}
    +            getHeaderConfig = {this.state.getHeaderConfig}
    +            getDelimiterChosen = {this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize = {this.state.getFileSize}
    +            isLoadingInDBInProgress = {this.state.isLoadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading = {this.state.isDataCurrentlyLoading}
    +          isBigFile = {this.state.isBigFile}
    +          getFileSize = {this.state.getFileSize}
    +          getTimeSinceLoad = {this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ 
    +          fileTooBig: true,
    +          fileSize: file.size, 
    +          draggingOver: false 
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ 
    +          loading: true,
    +          fileSize: file.size, 
    +          draggingOver: false 
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className = "fonticon icon-file-text-alt">
    +            <span className = "file-upload btn">
    +              <span className = "icon icon-search"></span>
    +              Choose File
    +              <input type = "file" className = "upload" onChange = {this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className = "filetype-txt">
    +          <a href = "#"
    +            className = "import-data-limit-info-link"
    +            onClick = {this.onFileLimitInfoToggle}
    +            data-bypass = "true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className = "loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className = "file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className = "dropzone"
    +          onDragOver = {this.dragOver}
    +          onDragLeave = {this.endDragOver}
    +          onDrop = {this.drop} >
    +          <div className = "dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className = "dropzone dragging-file-in-background"
    +          onDragOver = {this.dragOver}
    +          onDragLeave = {this.endDragOver}
    +          onDrop = {this.drop}>
    +          <div className = "dropzone-msg draggingover">
    +            <span className = "fonticon icon-file-text-alt">
    +            </span>
    +            <span className = "loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className = {"dropzone loading-background"}>
    +          <div className = "dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className = "dropzone limit-info"
    +          onDragOver = {this.dragOver}
    +          onDragLeave = {this.endDragOver}
    +          onDrop = {this.drop}>
    +          <div className = "dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className = "top-row">
    +          <div className = "big-file-info-message">
    +            <p className = "big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className = "dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className = "top-row">
    +          <div className = "big-file-info-message">
    +            <p className = "big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id = "preview-page" >
    +          <div id = "data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen = {this.props.getDelimiterChosen}
    +              filesize = {this.props.filesize} />
    +          </div>
    +          <div className = "preview-data-space">
    +            <TableView
    +              data = {this.props.data}
    +              isBigFile = {this.props.isBigFile}
    +              meta = {this.props.meta}
    +              getPreviewView = {this.props.getPreviewView}
    +              getSmallPreviewOfData = {this.props.getSmallPreviewOfData}
    +              getHeaderConfig = {this.props.getHeaderConfig} />
    +            <JSONView
    +              data = {this.props.data}
    +              isBigFile = {this.props.isBigFile}
    +              meta = {this.props.meta}
    +              getPreviewView = {this.props.getPreviewView}
    +              getSmallPreviewOfData = {this.props.getSmallPreviewOfData}
    +              getHeaderConfig = {this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs = {this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel: 'Table',
    +        rightLabel: 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig = {config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig = {config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup = {setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className = "no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className = "import-options-row">
    +          <div className = "options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key = {i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key = {dataKey} title = {dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key = {i} title = {field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key = {i} title = {i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView != 'table') {
    +        return null;
    +      }
    +      return (
    +        <table className = "data-import-table">
    +          <tbody>
    +            <tr>{header}</tr>
    +            {data}
    +          </tbody>
    +        </table>
    +      );
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id = {"<UUID>_" + i}
    +                content = {JSON.stringify(obj, null, ' ')}
    +                key = {i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView != "json") {
    +        return null;
    +      }
    +      return (
    +        <div id = "doc-list" className = "json-view">
    +          {this.rows()}
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var Footer = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        targetDB: this.props.getAllDBs[0],
    +        selectExistingDB: true
    +      };
    +    },
    +
    +    startOverButton: function () {
    +      return (
    +        <a className = "start-import-over-link footer-button"
    +          onClick = {this.startOver}>
    +          <span className = "fonticon icon-repeat"></span>
    +            Start Over
    +        </a>
    +      );
    +    },
    +
    +    startOver: function () {
    +      Actions.dataImporterInit(true);
    +    },
    +
    +    getExistingDBList: function () {
    +      var allDBs = this.props.getAllDBs,
    +          options = [],
    +          setTargetDB = this.setTargetDB;
    +
    +      _.each(allDBs, function (dbName, i) {
    +        options.push({
    +          name: dbName,
    +          onClick: function () { setTargetDB(dbName); }
    +        });
    +      });
    +
    +      return options;
    +    },
    +
    +    setTargetDB: function (dbName) {
    +      this.setState({ targetDB: dbName });
    +    },
    +
    +    newOrExistingToggle : function () {
    +      var config = {
    +        title: 'Load into',
    +        leftLabel : 'Existing database',
    +        rightLabel : 'New database',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          this.setState({
    +            selectExistingDB: true,
    +            targetDB: this.props.getAllDBs[0]
    +          });
    +        }.bind(this),
    +        rightClick: function () {
    +          this.setState({ selectExistingDB: false });
    +        }.bind(this),
    +        enclosingID: 'choose-database-to-load-data-into'
    +      };
    +
    +      return <Components.ToggleState toggleConfig = {config} />;
    +    },
    +
    +    chooseDatabaseFromDropdown : function () {
    +      var selected = this.state.targetDB;
    +
    +      var setup = {
    +        title: 'Choose a database',
    +        id: 'data-importer-choose-db',
    +        selected: selected,
    +        selectOptions: this.getExistingDBList()
    +      };
    +      return <Components.SmallDropdown dropdownSetup = {setup} />;
    +    },
    +
    +    createNewDB: function () {
    +      return (
    +        <div id = "data-import-name-new-target-database">
    --- End diff --
    
    that's quite unusual formatting and it's in almost every component in this PR


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324937
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    --- End diff --
    
    Defining a var as `undefined` is pretty darn odd... any good reason for this? You can't use null, perhaps?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960109
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer 
    +            getAllDBs={this.props.getAllDBs} 
    +            chunkedData={this.props.chunkedData} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel: 'Table',
    +        rightLabel: 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView != 'table') {
    +        return null;
    +      }
    +      return (
    +        <table className="data-import-table">
    +          <tbody>
    +            <tr>{header}</tr>
    +            {data}
    +          </tbody>
    +        </table>
    +      );
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    --- End diff --
    
    you can make this decision (which data to use) already in the store. 
    
    use one `getData()` getter and the store then returns the right data. this keeps the views dumb and makes testing easier.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39322645
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    --- End diff --
    
    Capital `o` too, for consistency with `dragOver`


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39332365
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    --- End diff --
    
    good idea. will do on monday


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324555
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    --- End diff --
    
    Wonder if all the var initialization here could be moved to a `reset()` method, which is called from `init()`? It's really nice having the `reset` method around - especially for tests, so you can reset it in before/afterEach()


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-141932850
  
    the other issue, the cog icon disappears after navigating: https://cloudup.com/cS8Pv1P49sF


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-139660346
  
    :( test suite.
    ok re:papaparse


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324753
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    --- End diff --
    
    I don't much like all the hardcoded values like this, 60000, 3000 and others 'cause I just don't know what they mean. I'd favour having a constants file for stuff like this just so they're in one place and can have meaningful names. 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39320175
  
    --- Diff: app/addons/dataimporter/resources.js ---
    @@ -0,0 +1,5 @@
    +define([],
    --- End diff --
    
    License header.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960280
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer 
    +            getAllDBs={this.props.getAllDBs} 
    +            chunkedData={this.props.chunkedData} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel: 'Table',
    +        rightLabel: 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView != 'table') {
    +        return null;
    --- End diff --
    
    please try to keep logic in the controller components. the table or json view should not need to know which option the user selected. this makes everything easier to test and reuse (see comment flagged A)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-139659781
  
    cool can't wait! 
    
    i haven't looked in detail yet, but can you add the external dependencies like `papaparse` unminified (for debugging, as we minify during the production build anyway) and as separate commit (as they make reading the code for the review harder)
    
    btw: i saw the testsuite is red right now


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324424
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    --- End diff --
    
    Will this work for bundled environments? 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324323
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView === 'table') {
    +        return (
    +          <table className="data-import-table">
    +            <tbody>
    +              <tr>{header}</tr>
    +              {data}
    +            </tbody>
    +          </table>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id={"<UUID>_" + i}
    +                content={JSON.stringify(obj, null, ' ')}
    +                key={i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView === "json") {
    +        return (
    +          <div id="doc-list" className="json-view">
    +            {this.rows()}
    +          </div>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var Footer = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        targetDB: this.props.getAllDBs[0],
    +        selectExistingDB: true
    +      };
    +    },
    +
    +    startOverButton: function () {
    +      return (
    +        <a className="start-import-over-link footer-button"
    +          onClick={this.startover}>
    +          <span className="fonticon icon-repeat"></span>
    +            Start Over
    +        </a>
    +      );
    +    },
    +
    +    startover: function () {
    +      Actions.dataImporterInit(true);
    +    },
    +
    +    getExistingDBList: function () {
    +      var allDBs = this.props.getAllDBs,
    +          options = [],
    +          setTargetDB = this.setTargetDB;
    +
    +      _.each(allDBs, function (dbName, i) {
    +        options.push({
    +          name: dbName,
    +          onClick: function () { setTargetDB(dbName); }
    +        });
    +      });
    +
    +      return options;
    +    },
    +
    +    setTargetDB: function (dbName) {
    +      this.setState({ targetDB: dbName });
    +    },
    +
    +    newOrExistingToggle : function () {
    +      var config = {
    +        title: 'Load into',
    +        leftLabel : 'Existing database',
    +        rightLabel : 'New database',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          this.setState({
    +            selectExistingDB: true,
    +            targetDB: this.props.getAllDBs[0]
    +          });
    +        }.bind(this),
    +        rightClick: function () {
    +          this.setState({ selectExistingDB: false });
    +        }.bind(this),
    +        enclosingID: 'choose-database-to-load-data-into'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    chooseDatabaseFromDropdown : function () {
    +      var selected = this.state.targetDB;
    +
    +      var setup = {
    +        title: 'Choose a database',
    +        id: 'data-importer-choose-db',
    +        selected: selected,
    +        selectOptions: this.getExistingDBList()
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup} />;
    +    },
    +
    +    createNewDB: function () {
    +      return (
    +        <div id="data-import-name-new-target-database">
    +          <div id="title">Name New Database</div>
    +          <input
    +            id="type-new-db-name-here"
    +            type="text"
    +            placeholder="Type new database name..."
    +            value={this.state.newDatabaseName}
    +            onChange={this.newDatabaseNameChange} />
    +        </div>
    +      );
    +    },
    +
    +    newDatabaseNameChange: function (e) {
    +      var newName = e.target.value;
    +      this.setState({ targetDB : newName });
    +    },
    +
    +    importButton : function () {
    +      return (
    +        <div id="data-import-load-button"
    +          className="footer-button data-import-load-button"
    +          onClick={ this.importData }>
    +          <span className="icon-download-alt fonticon"></span>
    +          Load
    +        </div>
    +      );
    +    },
    +
    +    importData : function () {
    +      var createNewDB = !this.state.selectExistingDB;
    +      Actions.loadDataIntoDatabase(createNewDB, this.state.targetDB);
    +    },
    +
    +    render: function () {
    +      var startOverButton = this.startOverButton(),
    +          targetDatabaseInput = this.state.selectExistingDB ?
    +        this.chooseDatabaseFromDropdown() : this.createNewDB();
    +
    +      return (
    +        <div id="preview-page-footer-container">
    +          <div id="data-import-controls">
    +            {this.newOrExistingToggle()}
    +            {targetDatabaseInput}
    +            <div className="restart-or-load">
    +              {startOverButton}
    +              {this.importButton()}
    +            </div>
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var DataImporterError = React.createClass({
    +    makeMessage: function () {
    +      var messagesArray = this.props.errorMsg;
    +
    +      return messagesArray.map(function (message, i) {
    +        return (
    +          <div key={i}>
    +            <hr/>
    +            {this.subMessages(message)}
    +          </div>
    +        );
    +      }.bind(this));
    +    },
    +
    +    subMessages: function (messages) {
    +      return messages.map(function (msg, i) {
    +        return <pre key={i}>{msg}</pre>;
    +      });
    +    },
    +
    +    startOverButton: function () {
    +      return (
    +        <a className="start-import-over-link footer-button"
    +          onClick={this.startover}>
    +          <span className="fonticon icon-repeat"></span>
    +            Start Over
    +        </a>
    +      );
    +    },
    +
    +    startover: function () {
    --- End diff --
    
    camelcase `startOver`, for compatibility with `startOverButton`


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39323411
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    --- End diff --
    
    Do we have to use inline CSS? Be nice to just add a class instead.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39321685
  
    --- Diff: app/addons/components/react-components.react.jsx ---
    @@ -891,6 +891,30 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) {
         }
       });
     
    +  var SimpleDoc = React.createClass({
    +    //must be enclosed in tag with id = "doc-list"
    +    render: function () {
    +      return (
    +        <div className="doc-row">
    +          <div className="doc-item">
    +            <header>
    +              <span className="header-keylabel">
    +                _id
    +              </span>
    +              <span className="header-doc-id">
    +                {this.props.id}
    +              </span>
    +            </header>
    +              <div className="doc-data">
    +                <pre className="prettyprint">{this.props.content}</pre>
    +              </div>
    --- End diff --
    
    indentation


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39332185
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    --- End diff --
    
    not sure why, but semicolon here causes errors


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-139690803
  
    todo: add button to cancel upload


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-141943009
  
    the query options button disappeared:
    
    <img width="637" alt="bildschirmfoto 2015-09-21 um 13 04 42" src="https://cloud.githubusercontent.com/assets/298166/9990430/6ecfd6d8-6061-11e5-8575-e102af756257.png">



---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39322701
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    --- End diff --
    
    These two lines can be grouped.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960665
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    --- End diff --
    
    together with putting the decision if you use small preview data `getSmallPreviewOfData` (see other comment) into the store you get quite logic less components that just render your state


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39322241
  
    --- Diff: app/addons/dataimporter/assets/less/dataimporter.less ---
    @@ -0,0 +1,406 @@
    +//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +//  use this file except in compliance with the License. You may obtain a copy of
    +//  the License at
    +//
    +//    http://www.apache.org/licenses/LICENSE-2.0
    +//
    +//  Unless required by applicable law or agreed to in writing, software
    +//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +//  License for the specific language governing permissions and limitations under
    +//  the License.
    +@import "../../../../../assets/less/bootstrap/variables.less";
    +@import "../../../../../assets/less/variables.less";
    +@import "../../../../../assets/less/bootstrap/mixins.less";
    +
    +.dropzone {
    +  border: 1px solid gray;
    +  background-color: #E8E8E8;
    +  height: calc(100% ~"-" 114px);
    +  min-height: 200px;
    +  min-width: 200px;
    +  margin: 50px;
    +  color: @darkRed;
    +  padding-top: 0px !important;
    +  
    +  a {
    +    color: @darkRed;
    +  }
    +  
    +  &.dragging-file-in-background {
    +    border: 1px solid @darkRed;
    +    background-color: #F0DAD9;
    +    z-index: 100;
    +    .icon-file-text-alt {
    +      padding-right: 10px;
    +    }
    +  }
    +
    +  &.loading-background {
    +    border: 1px solid @darkRed;
    +    .icon-file-text-alt {
    +      padding-right: 10px;
    +    }
    +  }
    +
    +  .loading-big-file-msg {
    +    color: black;
    +    font-size: 14px;
    +    padding: 10px;
    +  }
    +
    +  &.limit-info {
    +    background-color: #FFF;
    +    border-color: #FFF;
    +  }
    + 
    +  .file-exceeds-max-msg {
    +    color: black;
    +    font-size: 13px;
    +  }
    +
    +  .dropzone-msg {
    +    position: relative;
    +    top: 50%;
    +    transform: translateY(-50%);
    +    text-align: center;
    +    vertical-align: middle;
    +
    +    .icon-file-text-alt {
    +      font-size: 50px;
    +    }
    +
    +    &.draggingover { 
    +      .loading-msg {
    +        position: relative;
    +        top: -10px;
    +      }
    +    }
    +    
    +    .loading-lines {
    +      padding-top: 15px;
    +      padding-left: 10px;
    +      #line1, #line2, #line3, #line4 {
    +        background-color: @darkRed;
    +      }
    +    }
    +  }
    +
    +  .filetype-txt {
    +    text-align: center;
    +    color: gray;
    +    font-size: 12px !important;
    +    width: 100%;
    +    position: relative;
    +    top: calc(100% ~"-" 114px);
    +    text-align: center;
    +  }
    +
    +  .file-upload {
    +    position: relative;
    +    overflow: hidden;
    +    margin-left: 10px;
    +    margin-bottom: 3px;
    +    color: white;
    +    background-color: @darkRed;
    +    font-size: 13px !important;
    +    border: none;
    +
    +    .icon-search {
    +      margin-right: 5px;
    +    }
    +
    +    &:hover {
    +      background-color: @linkColor;
    +    }
    +  }
    +
    +  .file-upload input.upload {
    +      position: absolute;
    +      top: 0;
    +      right: 0;
    +      bottom: 2px;
    +      margin: 0;
    +      padding: 0;
    +      font-size: 20px;
    +      cursor: pointer;
    +      opacity: 0;
    +      filter: alpha(opacity=0);
    +  } 
    +
    +  .import-data-limit-info{
    +    visibility: hidden;
    +    &.shown {
    +      visibility: visible;
    +      background-color: #E8E8E8;
    +    }
    +  }
    +
    +  &.limit-info {
    +    .dropzone-msg {
    +      height: 90px;
    +    }
    +  }
    +
    +  .error-window {
    +    overflow: auto;
    +    height: 100%;
    +    color: black;
    +    padding-top: 10px;
    +    padding-bottom: 10px;
    +
    +    hr {
    +      border: 1px solid #a2a2a2;
    +    }
    +  }
    +}
    +
    +@footerHeight : 120px;
    +@fakeMargin: 15px solid #f1f1f1;
    +@footerWidthSingtons : 190px;
    +@smallMedia : ~"only screen and (max-width: 845px)";
    --- End diff --
    
    Bit weird having these vars in the middle of the file. Maybe move to top?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39325327
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.dataBaseIsnew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    dataBaseIsnew: function (targetDB) {
    +      return _.some(this._all_dbs, targetDB);
    +    },
    +
    +    showErrorScreen: function () {
    +      return this._showErrorScreen;
    +    },
    +
    +    getErrorMsg: function () {
    +      return this._errorMessageArray;
    +    },
    +
    +    goToErrorScreen: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this._showErrorScreen = true;
    +      if (resp) messageArray.push(resp);
    +      this._errorMessageArray.unshift(messageArray);
    +      this.triggerChange();
    +    },
    +
    +    loadIntoNewDB: function (targetDB) {
    +      $.ajax({
    +        url: FauxtonAPI.urls('databaseBaseURL', 'server', targetDB),
    +        xhrFields: { withCredentials: true },
    +        contentType: 'application/json; charset=UTF-8',
    +        method: 'PUT'
    +      }).then(function (resp) {
    +        this.loadDataIntoTarget(targetDB);
    +      }.bind(this), function (resp) {
    +        this.importFailed();
    +      }.bind(this));
    +    },
    +
    +    loadDataIntoTarget: function (targetDB) {
    +      var loadURL = FauxtonAPI.urls('document', 'server', targetDB, '_bulk_docs');
    +      _.each(this._chunkedData, function (data, i) {
    +        var payload = JSON.stringify({ 'docs': data });
    +        var prettyprint = JSON.stringify({ 'docs': data }, null, 2);
    +        $.ajax({
    +          url: loadURL,
    +          xhrFields: { withCredentials: true },
    +          contentType: 'application/json; charset=UTF-8',
    +          method: 'POST',
    +          data: payload
    +        }).then(function (resp) {
    +          i++;
    +          if (i === this._chunkedData.length ) {
    +            //console.log("alldone");
    +            this.successfulImport(targetDB);
    +            this.init(true);
    +          }
    +        }.bind(this), function (resp) {
    +          this.importFailed(resp, ['There was an error loading documents into ' + targetDB, 'Data that failed to load:' + prettyprint]);
    +        }.bind(this));
    +      }.bind(this));
    +    },
    +
    +    successfulImport: function (targetDB) {
    +      FauxtonAPI.navigate(FauxtonAPI.urls('allDocs', 'app', targetDB, '?include_docs=true'));
    +    },
    +
    +    importFailed: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this.goToErrorScreen(resp, messageArray);
    +    },
    +
    +    getIsloadingInDBInProgress: function () {
    --- End diff --
    
    Bit of a mouthful this one. Should be uppercase for the `l` in loading, but maybe rename to something like `dbPopulationInProgress` or something even simpler.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-140559614
  
    I still have to debug how it will work bundled with couchdb


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-141932396
  
    i found some issues, there is an exception and mutiple warnings in the console (not on master, just this branch) https://cloudup.com/cu4eYMSICiJ


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-139662408
  
    yes, testsuites can get really annoying if you want to write all tests and fix all existing tests at the very end.
    
    i always compare it to lawn mowing: if you keep the grass growing all summer and don't run with the mower in between, the "final cut" can get very hard work.
    
    same for css: if i write the whole time css without checking the browser in between the results at the amount of work at the very end that i have to put in my css can get very boring and discouraging :(


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39322544
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    --- End diff --
    
    The `l` in loading should be capitalized: `getIsLoadingInDBInProgress`


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39323495
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    --- End diff --
    
    Could we drop the space before `:` in this chunk


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39455639
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    --- End diff --
    
    Cool.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960144
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer 
    +            getAllDBs={this.props.getAllDBs} 
    +            chunkedData={this.props.chunkedData} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel: 'Table',
    +        rightLabel: 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView != 'table') {
    +        return null;
    +      }
    +      return (
    +        <table className="data-import-table">
    +          <tbody>
    +            <tr>{header}</tr>
    +            {data}
    +          </tbody>
    +        </table>
    +      );
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id={"<UUID>_" + i}
    +                content={JSON.stringify(obj, null, ' ')}
    +                key={i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView != "json") {
    --- End diff --
    
    strict equal and single quotes


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39382921
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,796 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg = {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown = {this.state.rowShown}
    +            rowsTotal = {this.state.rowsTotal}
    +            data = {this.state.data}
    +            isBigFile = {this.state.isBigFile}
    +            meta = {this.state.meta}
    +            getPreviewView = {this.state.getPreviewView}
    +            getSmallPreviewOfData = {this.state.getSmallPreviewOfData}
    +            getHeaderConfig = {this.state.getHeaderConfig}
    +            getDelimiterChosen = {this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize = {this.state.getFileSize}
    +            isLoadingInDBInProgress = {this.state.isLoadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading = {this.state.isDataCurrentlyLoading}
    +          isBigFile = {this.state.isBigFile}
    +          getFileSize = {this.state.getFileSize}
    +          getTimeSinceLoad = {this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ 
    +          fileTooBig: true,
    +          fileSize: file.size, 
    +          draggingOver: false 
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ 
    +          loading: true,
    +          fileSize: file.size, 
    +          draggingOver: false 
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className = "fonticon icon-file-text-alt">
    +            <span className = "file-upload btn">
    +              <span className = "icon icon-search"></span>
    +              Choose File
    +              <input type = "file" className = "upload" onChange = {this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className = "filetype-txt">
    +          <a href = "#"
    +            className = "import-data-limit-info-link"
    +            onClick = {this.onFileLimitInfoToggle}
    +            data-bypass = "true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className = "loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className = "file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className = "dropzone"
    +          onDragOver = {this.dragOver}
    +          onDragLeave = {this.endDragOver}
    +          onDrop = {this.drop} >
    +          <div className = "dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className = "dropzone dragging-file-in-background"
    +          onDragOver = {this.dragOver}
    +          onDragLeave = {this.endDragOver}
    +          onDrop = {this.drop}>
    +          <div className = "dropzone-msg draggingover">
    +            <span className = "fonticon icon-file-text-alt">
    +            </span>
    +            <span className = "loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className = {"dropzone loading-background"}>
    +          <div className = "dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className = "dropzone limit-info"
    +          onDragOver = {this.dragOver}
    +          onDragLeave = {this.endDragOver}
    +          onDrop = {this.drop}>
    +          <div className = "dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className = "top-row">
    +          <div className = "big-file-info-message">
    +            <p className = "big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className = "dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className = "top-row">
    +          <div className = "big-file-info-message">
    +            <p className = "big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id = "preview-page" >
    +          <div id = "data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen = {this.props.getDelimiterChosen}
    +              filesize = {this.props.filesize} />
    +          </div>
    +          <div className = "preview-data-space">
    +            <TableView
    +              data = {this.props.data}
    +              isBigFile = {this.props.isBigFile}
    +              meta = {this.props.meta}
    +              getPreviewView = {this.props.getPreviewView}
    +              getSmallPreviewOfData = {this.props.getSmallPreviewOfData}
    +              getHeaderConfig = {this.props.getHeaderConfig} />
    +            <JSONView
    +              data = {this.props.data}
    +              isBigFile = {this.props.isBigFile}
    +              meta = {this.props.meta}
    +              getPreviewView = {this.props.getPreviewView}
    +              getSmallPreviewOfData = {this.props.getSmallPreviewOfData}
    +              getHeaderConfig = {this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs = {this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel: 'Table',
    +        rightLabel: 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig = {config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig = {config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup = {setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className = "no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className = "import-options-row">
    +          <div className = "options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key = {i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key = {dataKey} title = {dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key = {i} title = {field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key = {i} title = {i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView != 'table') {
    +        return null;
    +      }
    +      return (
    +        <table className = "data-import-table">
    +          <tbody>
    +            <tr>{header}</tr>
    +            {data}
    +          </tbody>
    +        </table>
    +      );
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id = {"<UUID>_" + i}
    +                content = {JSON.stringify(obj, null, ' ')}
    +                key = {i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView != "json") {
    +        return null;
    +      }
    +      return (
    +        <div id = "doc-list" className = "json-view">
    +          {this.rows()}
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var Footer = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        targetDB: this.props.getAllDBs[0],
    +        selectExistingDB: true
    +      };
    +    },
    +
    +    startOverButton: function () {
    +      return (
    +        <a className = "start-import-over-link footer-button"
    +          onClick = {this.startOver}>
    +          <span className = "fonticon icon-repeat"></span>
    +            Start Over
    +        </a>
    +      );
    +    },
    +
    +    startOver: function () {
    +      Actions.dataImporterInit(true);
    +    },
    +
    +    getExistingDBList: function () {
    +      var allDBs = this.props.getAllDBs,
    +          options = [],
    +          setTargetDB = this.setTargetDB;
    +
    +      _.each(allDBs, function (dbName, i) {
    +        options.push({
    +          name: dbName,
    +          onClick: function () { setTargetDB(dbName); }
    +        });
    +      });
    +
    +      return options;
    +    },
    +
    +    setTargetDB: function (dbName) {
    +      this.setState({ targetDB: dbName });
    +    },
    +
    +    newOrExistingToggle : function () {
    +      var config = {
    +        title: 'Load into',
    +        leftLabel : 'Existing database',
    +        rightLabel : 'New database',
    --- End diff --
    
    whitepaces :space_invader: 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by kxepal <gi...@git.apache.org>.
Github user kxepal commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-159523613
  
    @michellephung 
    Closed means merged?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324226
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView === 'table') {
    +        return (
    +          <table className="data-import-table">
    +            <tbody>
    +              <tr>{header}</tr>
    +              {data}
    +            </tbody>
    +          </table>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id={"<UUID>_" + i}
    +                content={JSON.stringify(obj, null, ' ')}
    +                key={i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView === "json") {
    +        return (
    +          <div id="doc-list" className="json-view">
    +            {this.rows()}
    +          </div>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var Footer = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        targetDB: this.props.getAllDBs[0],
    +        selectExistingDB: true
    +      };
    +    },
    +
    +    startOverButton: function () {
    +      return (
    +        <a className="start-import-over-link footer-button"
    +          onClick={this.startover}>
    +          <span className="fonticon icon-repeat"></span>
    +            Start Over
    +        </a>
    +      );
    +    },
    +
    +    startover: function () {
    +      Actions.dataImporterInit(true);
    +    },
    +
    +    getExistingDBList: function () {
    +      var allDBs = this.props.getAllDBs,
    +          options = [],
    +          setTargetDB = this.setTargetDB;
    +
    +      _.each(allDBs, function (dbName, i) {
    +        options.push({
    +          name: dbName,
    +          onClick: function () { setTargetDB(dbName); }
    +        });
    +      });
    +
    +      return options;
    +    },
    +
    +    setTargetDB: function (dbName) {
    +      this.setState({ targetDB: dbName });
    +    },
    +
    +    newOrExistingToggle : function () {
    +      var config = {
    +        title: 'Load into',
    +        leftLabel : 'Existing database',
    +        rightLabel : 'New database',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          this.setState({
    +            selectExistingDB: true,
    +            targetDB: this.props.getAllDBs[0]
    +          });
    +        }.bind(this),
    +        rightClick: function () {
    +          this.setState({ selectExistingDB: false });
    +        }.bind(this),
    +        enclosingID: 'choose-database-to-load-data-into'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    chooseDatabaseFromDropdown : function () {
    +      var selected = this.state.targetDB;
    +
    +      var setup = {
    +        title: 'Choose a database',
    +        id: 'data-importer-choose-db',
    +        selected: selected,
    +        selectOptions: this.getExistingDBList()
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup} />;
    +    },
    +
    +    createNewDB: function () {
    +      return (
    +        <div id="data-import-name-new-target-database">
    +          <div id="title">Name New Database</div>
    --- End diff --
    
    My CSS friend would shoot you for the lack of semantics here (as he's shot me). :) Titles should be <h1>, <h2>, etc. Can be a bit irritating if you have to reset the CSS on them a bit, but it definitely makes for more readable markup.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-141296594
  
    To do: Need it to work when bundle with couchDB.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39321825
  
    --- Diff: app/addons/components/react-components.react.jsx ---
    @@ -1081,6 +1105,114 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) {
     
       });
     
    +  var ToggleState = React.createClass({
    +    getDefaultProps: function () {
    +      return {
    +        defaultLeft: 'true'
    +      };
    +    },
    +    render: function () {
    +      var config = this.props.toggleConfig,
    +          defaultLeft = false,
    +          defaultRight = false;
    +
    +      var title = config.title,
    +          leftLabel = config.leftLabel,
    +          rightLabel = config.rightLabel,
    +          leftClick = config.leftClick,
    +          rightClick = config.rightClick,
    +          enclosingID = config.enclosingID;
    +
    +      if (config.defaultLeft) {
    +        defaultLeft = "checked";
    +        defaultRight = false;
    +      } else {
    +        defaultRight = "checked";
    +        defaultLeft = false;
    +      }
    +
    +      return (
    +        <div id={enclosingID} className="toggle-states">
    +          <div className="toggle-title noselect">{title}</div>
    +          <div className="toggles">
    +            <input type="radio" 
    +              id = {"toggle-state-left-id-" + enclosingID}
    --- End diff --
    
    no spaces around the `=`, smame with defaultChecked + a few other attributes in this chunk of code


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-139655463
  
    ready for review! :D 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39450846
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    --- End diff --
    
    I am going to leave this as is for now.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39321970
  
    --- Diff: app/addons/components/react-components.react.jsx ---
    @@ -1081,6 +1105,114 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) {
     
       });
     
    +  var ToggleState = React.createClass({
    +    getDefaultProps: function () {
    +      return {
    +        defaultLeft: 'true'
    +      };
    +    },
    +    render: function () {
    +      var config = this.props.toggleConfig,
    +          defaultLeft = false,
    +          defaultRight = false;
    +
    +      var title = config.title,
    +          leftLabel = config.leftLabel,
    +          rightLabel = config.rightLabel,
    +          leftClick = config.leftClick,
    +          rightClick = config.rightClick,
    +          enclosingID = config.enclosingID;
    +
    +      if (config.defaultLeft) {
    +        defaultLeft = "checked";
    +        defaultRight = false;
    +      } else {
    +        defaultRight = "checked";
    +        defaultLeft = false;
    +      }
    +
    +      return (
    +        <div id={enclosingID} className="toggle-states">
    +          <div className="toggle-title noselect">{title}</div>
    +          <div className="toggles">
    +            <input type="radio" 
    +              id = {"toggle-state-left-id-" + enclosingID}
    +              name={"toggle_" + enclosingID}
    +              className="input-toggle-hidden"
    +              defaultChecked = {defaultLeft}
    +              onClick={leftClick} />
    +              <label 
    +                htmlFor={"toggle-state-left-id-" + enclosingID} 
    +                className="checkbox-label toggle-state-button left noselect">
    +                {leftLabel}
    +              </label>
    +            <input type="radio"
    +              id = {"toggle-state-right-id-" + enclosingID} 
    +              name={"toggle_" + enclosingID}
    +              defaultChecked = {defaultRight}
    +              className="input-toggle-hidden "
    +              onClick={rightClick} />
    +              <label 
    +                htmlFor={"toggle-state-right-id-" + enclosingID}
    +                className="checkbox-label toggle-state-button right noselect">
    +                {rightLabel}
    +              </label>
    +            </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var SmallDropdown = React.createClass({
    +    getInitialState: function () {
    --- End diff --
    
    Could we add a `propTypes` to this class? It's nice to know at a glance exactly what props are being passed into a class so there are no surprises.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39974676
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer 
    +            getAllDBs={this.props.getAllDBs} 
    +            chunkedData={this.props.chunkedData} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel: 'Table',
    +        rightLabel: 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    --- End diff --
    
    this implementation needs tests, no header just creates 5 elements for a usual csv: https://cloudup.com/cT0Ox75_cNC -- got the sample CSV here: http://www.microsoft.com/en-us/download/confirmation.aspx?id=45485


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39323670
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    --- End diff --
    
    I think this needs a semicolon or the style checker will whine.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-148505260
  
    // if (!this.state.visible || !this.state.endpoint) {
          //   return null;
          // }
    
    what is this


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39960390
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,797 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress(),
    +        getChunkData: dataImporterStore.getChunkData()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      Actions.fetchAllDBs();
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg={this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig={this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs={this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isLoadingInDBInProgress={this.state.isLoadingInDBInProgress}
    +            chunkedData={this.state.getChunkData} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize={this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      if (file.size > this.props.maxSize) {
    +        this.setState({
    +          fileTooBig: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({
    +          loading: true,
    +          fileSize: file.size,
    +          draggingOver: false
    +        });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className="dropzone dragging-file-in-background"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragOver}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isLoadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    --- End diff --
    
    comment A: 
    
    you don't need to pass down `getPreviewView` so the `JSONView` and `TableView` decide on their own when they should render and when they don't render.
    
    You can already solve that problem on the controller level:
    



---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39749501
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,382 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.js';
    +
    +  var DATA_IMPORTER_NUMBERS = {
    +    BIG_FILE_SIZE_CAP: 750000,
    +    FILE_MAX_SIZE: 150000000,  //in bytes
    +    REPEAT_EVERY_3_SECONDS: 3000,
    +    MAX_ROWS_TO_PREVIEW: 500,
    +    DATA_ROW_CHUNKS: 500
    +  };
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this.reset();
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +    },
    +
    +    reset: function () {
    +      this._isDataCurrentlyLoading = false;
    +      this._hasDataLoaded = false;
    +      this._hasErrored = false;
    +      this._theData = [];
    +      this._theMetadata = [];
    +      this._smallData = [];
    +      this._showView = 'table';
    +      this._theFile = { size: 0 };
    +      this._config = this.getDefaultConfig();
    +      this._completeFn = this._config.complete;
    +      this._errorFn = this._config.error;
    +      this._fileSize = 0;
    +      this._time = "just started";
    +      this._repeatTimeID;
    +      this.fetchAllDBs();
    +      this._chunkedData = [];
    +      this._maxSize = DATA_IMPORTER_NUMBERS.FILE_MAX_SIZE;
    +      this._showErrorScreen = false;
    +      this._errorMessageArray = [];
    +      this._loadingInDBInProgress = false;
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > DATA_IMPORTER_NUMBERS.BIG_FILE_SIZE_CAP ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), DATA_IMPORTER_NUMBERS.REPEAT_EVERY_3_SECONDS);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = DATA_IMPORTER_NUMBERS.BIG_FILE_SIZE_CAP,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = (numberOfRowsToShow < DATA_IMPORTER_NUMBERS.MAX_ROWS_TO_PREVIEW) ?
    +        DATA_IMPORTER_NUMBERS.MAX_ROWS_TO_PREVIEW : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += DATA_IMPORTER_NUMBERS.DATA_ROW_CHUNKS) {
    +        var oneChunk = this._theData.slice(i, i + DATA_IMPORTER_NUMBERS.DATA_ROW_CHUNKS);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +
    +      // the following object shows all of the options available for papaparse
    +      // some are defaulted to undefined
    +      // this is from http://papaparse.com/docs#config
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.databaseIsNew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    databaseIsNew: function (targetDB) {
    +      return _.some(this._all_dbs, targetDB);
    +    },
    +
    +    showErrorScreen: function () {
    +      return this._showErrorScreen;
    +    },
    +
    +    getErrorMsg: function () {
    +      return this._errorMessageArray;
    +    },
    +
    +    goToErrorScreen: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this._showErrorScreen = true;
    +      if (resp) {
    +        messageArray.push(resp);
    +      }
    +      this._errorMessageArray.unshift(messageArray);
    +      this.triggerChange();
    +    },
    +
    +    loadIntoNewDB: function (targetDB) {
    +      $.ajax({
    +        url: FauxtonAPI.urls('databaseBaseURL', 'server', targetDB),
    +        xhrFields: { withCredentials: true },
    +        contentType: 'application/json; charset=UTF-8',
    +        method: 'PUT'
    +      }).then(function (resp) {
    +        this.loadDataIntoTarget(targetDB);
    +      }.bind(this), function (resp) {
    +        this.importFailed();
    +      }.bind(this));
    +    },
    +
    +    loadDataIntoTarget: function (targetDB) {
    +      var loadURL = FauxtonAPI.urls('document', 'server', targetDB, '_bulk_docs');
    +      _.each(this._chunkedData, function (data, i) {
    +        var payload = JSON.stringify({ 'docs': data });
    +        var prettyprint = JSON.stringify({ 'docs': data }, null, 2);
    +        $.ajax({
    +          url: loadURL,
    +          xhrFields: { withCredentials: true },
    +          contentType: 'application/json; charset=UTF-8',
    +          method: 'POST',
    +          data: payload
    +        }).then(function (resp) {
    +          i++;
    --- End diff --
    
    every ajax request returns a promise. you can push all ajax requests onto an array, so you have an array of promises and then use `FauxtonAPI.when` which will resolve when all promises in the array resolve - it is slightly more elegant than manually counting and has less side effects.
    
    example: https://github.com/apache/couchdb-fauxton/blob/5de1fe6682fc5a9dee44022498357febd73c3600/app/addons/cors/actions.js#L22


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39450819
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    --- End diff --
    
    this is super tricky. So I am using jquery to move the top controls to the center of the page whenever we scroll left and right, so the 'the.state.left.' is the variable, it changes from 0 to n. A class won't work, but the CSS I had tried was not really good either. One option would be to have a floating element that happens sometimes, but then it breaks more than it helps.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39322408
  
    --- Diff: app/addons/dataimporter/assets/less/dataimporter.less ---
    @@ -0,0 +1,406 @@
    +//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +//  use this file except in compliance with the License. You may obtain a copy of
    +//  the License at
    +//
    +//    http://www.apache.org/licenses/LICENSE-2.0
    +//
    +//  Unless required by applicable law or agreed to in writing, software
    +//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +//  License for the specific language governing permissions and limitations under
    +//  the License.
    +@import "../../../../../assets/less/bootstrap/variables.less";
    +@import "../../../../../assets/less/variables.less";
    +@import "../../../../../assets/less/bootstrap/mixins.less";
    +
    +.dropzone {
    +  border: 1px solid gray;
    +  background-color: #E8E8E8;
    +  height: calc(100% ~"-" 114px);
    +  min-height: 200px;
    +  min-width: 200px;
    +  margin: 50px;
    +  color: @darkRed;
    +  padding-top: 0px !important;
    +  
    +  a {
    +    color: @darkRed;
    +  }
    +  
    +  &.dragging-file-in-background {
    +    border: 1px solid @darkRed;
    +    background-color: #F0DAD9;
    +    z-index: 100;
    +    .icon-file-text-alt {
    +      padding-right: 10px;
    +    }
    +  }
    +
    +  &.loading-background {
    +    border: 1px solid @darkRed;
    +    .icon-file-text-alt {
    +      padding-right: 10px;
    +    }
    +  }
    +
    +  .loading-big-file-msg {
    +    color: black;
    +    font-size: 14px;
    +    padding: 10px;
    +  }
    +
    +  &.limit-info {
    +    background-color: #FFF;
    +    border-color: #FFF;
    +  }
    + 
    +  .file-exceeds-max-msg {
    +    color: black;
    +    font-size: 13px;
    +  }
    +
    +  .dropzone-msg {
    +    position: relative;
    +    top: 50%;
    +    transform: translateY(-50%);
    +    text-align: center;
    +    vertical-align: middle;
    +
    +    .icon-file-text-alt {
    +      font-size: 50px;
    +    }
    +
    +    &.draggingover { 
    +      .loading-msg {
    +        position: relative;
    +        top: -10px;
    +      }
    +    }
    +    
    +    .loading-lines {
    +      padding-top: 15px;
    +      padding-left: 10px;
    +      #line1, #line2, #line3, #line4 {
    +        background-color: @darkRed;
    +      }
    +    }
    +  }
    +
    +  .filetype-txt {
    +    text-align: center;
    +    color: gray;
    +    font-size: 12px !important;
    +    width: 100%;
    +    position: relative;
    +    top: calc(100% ~"-" 114px);
    +    text-align: center;
    +  }
    +
    +  .file-upload {
    +    position: relative;
    +    overflow: hidden;
    +    margin-left: 10px;
    +    margin-bottom: 3px;
    +    color: white;
    +    background-color: @darkRed;
    +    font-size: 13px !important;
    +    border: none;
    +
    +    .icon-search {
    +      margin-right: 5px;
    +    }
    +
    +    &:hover {
    +      background-color: @linkColor;
    +    }
    +  }
    +
    +  .file-upload input.upload {
    +      position: absolute;
    +      top: 0;
    +      right: 0;
    +      bottom: 2px;
    +      margin: 0;
    +      padding: 0;
    +      font-size: 20px;
    +      cursor: pointer;
    +      opacity: 0;
    +      filter: alpha(opacity=0);
    +  } 
    +
    +  .import-data-limit-info{
    +    visibility: hidden;
    +    &.shown {
    +      visibility: visible;
    +      background-color: #E8E8E8;
    +    }
    +  }
    +
    +  &.limit-info {
    +    .dropzone-msg {
    +      height: 90px;
    +    }
    +  }
    +
    +  .error-window {
    +    overflow: auto;
    +    height: 100%;
    +    color: black;
    +    padding-top: 10px;
    +    padding-bottom: 10px;
    +
    +    hr {
    +      border: 1px solid #a2a2a2;
    +    }
    +  }
    +}
    +
    +@footerHeight : 120px;
    +@fakeMargin: 15px solid #f1f1f1;
    +@footerWidthSingtons : 190px;
    +@smallMedia : ~"only screen and (max-width: 845px)";
    +
    +.chartSpacing() {
    +  max-width: 150px;
    +  white-space: nowrap;
    +  overflow: hidden;
    +  text-overflow: ellipsis;
    +  border: 1px solid black;
    +  padding: 15px;
    +}
    +
    +#preview-page {
    +  min-height: 200px;
    +  min-width: 200px;
    +  overflow: auto;
    +  height: calc(100% ~"-" @footerHeight);
    +  width: 100%;
    +  padding-right: 0px !important;
    +  padding-left: 0px !important;
    +  border-left: @fakeMargin;
    +
    +  #data-import-options {
    +    position: relative;
    +  }
    +
    +  .top-row {
    +    font-size: 13px;
    +    border-bottom: 2px solid gray;
    +    display: inline-block;
    +    width: 100%;
    +    padding-top: 10px;
    +
    +    .big-file-info-message {
    +      display: inline-block;
    +      max-width: 600px;
    +      line-height: 1.25;
    +      margin: 0 auto;
    +      vertical-align: middle;
    +
    +      .big-file-preview-limit-info-message {
    +        display: inline-block;
    +        margin: -7px 15px 10px 5px;
    +      }
    +    }
    +  }
    +
    +  .import-options-row {
    +    padding-top: 10px;
    +    padding-bottom: 20px;
    +
    +    .options-row {
    +      text-align: center;
    +      
    +      .no-options-available{
    +        font-size: 12px;
    +        padding-top: 10px;
    +      }
    +      
    +      .small-dropdown{
    +        margin-right: 20px;
    +        margin-top: 10px;
    +      }
    +
    +      .toggle-states {
    +        display: inline-block;
    +        margin-right: 20px;
    +        
    +      }
    +    }
    +  }
    +
    +  .preview-data-space {
    +    min-width: 300px;
    +
    +    .data-import-table {
    +      margin: auto;
    +      tr {
    +        height: 50px;
    +      }
    +
    +      td {
    +        color: white;
    +        background-color: #4d4d4d;
    +        .chartSpacing;
    +      }
    +
    +      th {
    +        color: @red;
    +        background-color: #3a3a3a;
    +        .chartSpacing;
    +      }
    +    }
    +  }
    +
    +  #preview-page-footer-container {
    +    background-color: #f1f1f1;
    +    color: white;
    +    position: fixed;
    +    bottom: 0;
    +    width: calc(100% ~"-" @navWidth);
    +    margin-left: -15px;
    +    height: @footerHeight;
    +
    +    .closeMenu & {
    +      width: calc(100% ~"-" @collapsedNavWidth);
    +    }
    +  }
    +
    +  #data-import-controls {
    +    position: relative;
    +    top: 40%;
    +    transform: translateY(-40%);
    +    vertical-align: middle;
    +    margin: 0px 15px 0px 15px;
    +    text-align: center; 
    +
    +    > div {
    +      display: inline-block;
    +      margin-right: 15px;
    +      vertical-align: text-top;
    +    }
    +
    +    .restart-or-load  {
    +      margin-top: 11px;
    +    }
    +  }
    +
    +  #doc-list {
    +    .prettyprint {
    +      color: white;
    +    }
    +  }
    +
    +  #choose-database-to-load-data-into {
    +    width: 300px;
    +    height: 21px;
    +
    +    .toggle-state-button {
    +      width: 50%;
    +    }
    +  }
    +
    +  #data-importer-choose-db {
    +    width: 182px;
    +
    +    ul.dropdown-select.show {
    +      position: absolute;
    +      bottom:  23px;
    +      max-height: 180px;
    +      overflow-y: scroll;
    +      overflow-x: hidden;
    +
    +      @media @smallMedia { 
    +        width:  300px;
    +
    +      }
    +      
    +      li {
    +        @media @smallMedia { 
    +          width:  300px;
    +        }
    +      }
    +    }
    +    @media @smallMedia { 
    +      width: 300px;
    +
    +      .title {
    +        visibility: hidden;
    +      }
    +    }
    +  }
    +
    +  #data-import-name-new-target-database {
    +    width: @footerWidthSingtons;
    +    height: 40px;
    +    font-size: 11px;
    +    cursor: pointer;
    +    vertical-align: text-bottom;
    +
    +    #title {
    +      color: #838383;
    +    }
    +
    +    #type-new-db-name-here {
    +      background-color: white;
    +      height: 22px;
    +      padding: 0px 10px 0px 10px;
    +      margin-top: 3px;
    +      width: 182px;
    +      font-size: 12px;
    +      
    +      @media @smallMedia { 
    +        width:  300px;
    +        
    +      }
    +    }
    +    @media @smallMedia { 
    +      width:  300px;
    +      #title {
    +        visibility: hidden;
    +      }
    +    }
    +  }
    +}
    +.footer-button {
    +  width: @footerWidthSingtons;
    --- End diff --
    
    What does Singtons mean? Singleton? 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324076
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView === 'table') {
    +        return (
    +          <table className="data-import-table">
    +            <tbody>
    +              <tr>{header}</tr>
    +              {data}
    +            </tbody>
    +          </table>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id={"<UUID>_" + i}
    +                content={JSON.stringify(obj, null, ' ')}
    +                key={i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView === "json") {
    +        return (
    +          <div id="doc-list" className="json-view">
    +            {this.rows()}
    +          </div>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var Footer = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        targetDB: this.props.getAllDBs[0],
    +        selectExistingDB: true
    +      };
    +    },
    +
    +    startOverButton: function () {
    +      return (
    +        <a className="start-import-over-link footer-button"
    +          onClick={this.startover}>
    +          <span className="fonticon icon-repeat"></span>
    +            Start Over
    +        </a>
    +      );
    +    },
    +
    +    startover: function () {
    +      Actions.dataImporterInit(true);
    +    },
    +
    +    getExistingDBList: function () {
    +      var allDBs = this.props.getAllDBs,
    +          options = [],
    +          setTargetDB = this.setTargetDB;
    +
    +      _.each(allDBs, function (dbName, i) {
    --- End diff --
    
    You could drop the `options` var here and just return _.map() where each iteration returns the object with name + onClick.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39455544
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    --- End diff --
    
    Maybe the JSX syntax? `( ... )` ? No worries.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39978737
  
    --- Diff: app/addons/dataimporter/assets/less/dataimporter.less ---
    @@ -0,0 +1,410 @@
    +//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +//  use this file except in compliance with the License. You may obtain a copy of
    +//  the License at
    +//
    +//    http://www.apache.org/licenses/LICENSE-2.0
    +//
    +//  Unless required by applicable law or agreed to in writing, software
    +//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +//  License for the specific language governing permissions and limitations under
    +//  the License.
    +@import "../../../../../assets/less/bootstrap/variables.less";
    +@import "../../../../../assets/less/variables.less";
    +@import "../../../../../assets/less/bootstrap/mixins.less";
    +@footerHeight : 120px;
    +@fakeMargin: 15px solid #f1f1f1;
    +@footerWidthSingletons : 190px;
    +@smallMedia : ~"only screen and (max-width: 845px)";
    +
    +.dropzone {
    +  border: 1px solid gray;
    +  background-color: #E8E8E8;
    +  height: calc(100% ~"-" 114px);
    +  min-height: 200px;
    +  min-width: 200px;
    +  margin: 50px;
    +  color: @darkRed;
    +  padding-top: 0px !important;
    +  
    +  a {
    +    color: @darkRed;
    +  }
    +  
    +  &.dragging-file-in-background {
    +    border: 1px solid @darkRed;
    +    background-color: #F0DAD9;
    +    z-index: 100;
    +    .icon-file-text-alt {
    +      padding-right: 10px;
    +    }
    +  }
    +
    +  &.loading-background {
    +    border: 1px solid @darkRed;
    +    .icon-file-text-alt {
    +      padding-right: 10px;
    +    }
    +  }
    +
    +  .loading-big-file-msg {
    +    color: black;
    +    font-size: 14px;
    +    padding: 10px;
    +  }
    +
    +  &.limit-info {
    +    background-color: #FFF;
    +    border-color: #FFF;
    +  }
    + 
    +  .file-exceeds-max-msg {
    +    color: black;
    +    font-size: 13px;
    +  }
    +
    +  .dropzone-msg {
    +    position: relative;
    +    top: 50%;
    +    transform: translateY(-50%);
    +    text-align: center;
    +    vertical-align: middle;
    +
    +    .icon-file-text-alt {
    +      font-size: 50px;
    +    }
    +
    +    &.draggingover { 
    +      .loading-msg {
    +        position: relative;
    +        top: -10px;
    +      }
    +    }
    +    
    +    .loading-lines {
    +      padding-top: 15px;
    +      padding-left: 10px;
    +      #line1, #line2, #line3, #line4 {
    +        background-color: @darkRed;
    +      }
    +    }
    +  }
    +
    +  .filetype-txt {
    +    text-align: center;
    +    color: gray;
    +    font-size: 12px !important;
    +    width: 100%;
    +    position: relative;
    +    top: calc(100% ~"-" 114px);
    +    text-align: center;
    +  }
    +
    +  .file-upload {
    +    position: relative;
    +    overflow: hidden;
    +    margin-left: 10px;
    +    margin-bottom: 3px;
    +    color: white;
    +    background-color: @darkRed;
    +    font-size: 13px !important;
    +    border: none;
    +
    +    .icon-search {
    +      margin-right: 5px;
    +    }
    +
    +    &:hover {
    +      background-color: @linkColor;
    +    }
    +  }
    +
    +  .file-upload input.upload {
    +    position: absolute;
    +    top: 0;
    +    right: 0;
    +    bottom: 2px;
    +    margin: 0;
    +    padding: 0;
    +    font-size: 20px;
    +    cursor: pointer;
    +    opacity: 0;
    +    filter: alpha(opacity=0);
    +  } 
    +
    +  .import-data-limit-info{
    +    visibility: hidden;
    +    &.shown {
    +      visibility: visible;
    +      background-color: #E8E8E8;
    +    }
    +  }
    +
    +  &.limit-info {
    +    .dropzone-msg {
    +      height: 90px;
    +    }
    +  }
    +
    +  .error-window {
    +    overflow: auto;
    +    height: 100%;
    +    color: black;
    +    padding-top: 10px;
    +    padding-bottom: 10px;
    +
    +    hr {
    +      border: 1px solid #a2a2a2;
    +    }
    +  }
    +}
    +
    +.chartSpacing() {
    +  max-width: 150px;
    +  white-space: nowrap;
    +  overflow: hidden;
    +  text-overflow: ellipsis;
    +  border: 1px solid black;
    +  padding: 15px;
    +}
    +
    +#preview-page {
    +  min-height: 200px;
    +  min-width: 200px;
    +  overflow: auto;
    +  height: calc(100% ~"-" @footerHeight);
    +  width: 100%;
    +  padding-right: 0px !important;
    +  padding-left: 0px !important;
    +  border-left: @fakeMargin;
    +
    +  #data-import-options {
    +    position: relative;
    +  }
    +
    +  .top-row {
    +    font-size: 13px;
    +    border-bottom: 2px solid gray;
    +    display: inline-block;
    +    width: 100%;
    +    padding-top: 10px;
    +
    +    .big-file-info-message {
    +      display: inline-block;
    +      max-width: 600px;
    +      line-height: 1.25;
    +      margin: 0 auto;
    +      vertical-align: middle;
    +
    +      .big-file-preview-limit-info-message {
    +        display: inline-block;
    +        margin: -7px 15px 10px 5px;
    +      }
    +    }
    +  }
    +
    +  .import-options-row {
    +    padding-top: 10px;
    +    padding-bottom: 20px;
    +
    +    .options-row {
    +      text-align: center;
    +      
    +      .no-options-available{
    +        font-size: 12px;
    +        padding-top: 10px;
    +      }
    +      
    +      .small-dropdown{
    +        margin-right: 20px;
    +        margin-top: 10px;
    +      }
    +
    +      .toggle-states {
    +        display: inline-block;
    +        margin-right: 20px;
    +        
    +      }
    +    }
    +  }
    +
    +  .preview-data-space {
    +    min-width: 300px;
    +
    +    .data-import-table {
    +      margin: auto;
    +      tr {
    +        height: 50px;
    +      }
    +
    +      td {
    +        color: white;
    +        background-color: #4d4d4d;
    +        .chartSpacing;
    +      }
    +
    +      th {
    +        color: @red;
    +        background-color: #3a3a3a;
    +        .chartSpacing;
    +      }
    +    }
    +  }
    +
    +  #preview-page-footer-container {
    +    background-color: #f1f1f1;
    +    color: white;
    +    position: fixed;
    +    bottom: 0;
    +    width: calc(100% ~"-" @navWidth);
    +    margin-left: -15px;
    +    height: @footerHeight;
    +
    +    .closeMenu & {
    +      width: calc(100% ~"-" @collapsedNavWidth);
    +    }
    +  }
    +
    +  #data-import-controls {
    +    position: relative;
    +    top: 40%;
    +    transform: translateY(-40%);
    +    vertical-align: middle;
    +    margin: 0px 15px 0px 15px;
    +    text-align: center; 
    +
    +    > div {
    +      display: inline-block;
    +      margin-right: 15px;
    +      vertical-align: text-top;
    +    }
    +
    +    .restart-or-load  {
    +      margin-top: 11px;
    +    }
    +  }
    +
    +  #doc-list {
    +    .prettyprint {
    +      color: white;
    +    }
    +  }
    +
    +  #choose-database-to-load-data-into {
    +    width: 300px;
    +    height: 21px;
    +
    +    .toggle-state-button {
    +      width: 50%;
    +    }
    +  }
    +
    +  #data-importer-choose-db {
    +    width: 182px;
    +
    +    ul.dropdown-select.show {
    +      position: absolute;
    +      bottom:  23px;
    +      max-height: 180px;
    +      overflow-y: scroll;
    +      overflow-x: hidden;
    +
    +      @media @smallMedia { 
    +        width:  300px;
    +      }
    +      
    +      li {
    +        @media @smallMedia { 
    +          width:  300px;
    +        }
    +      }
    +    }
    +    @media @smallMedia { 
    +      width: 300px;
    +
    +      .title {
    +        visibility: hidden;
    +      }
    +    }
    +  }
    +
    +  #data-import-name-new-target-database {
    +    width: @footerWidthSingletons;
    +    height: 40px;
    +    font-size: 11px;
    +    cursor: pointer;
    +    vertical-align: text-bottom;
    +
    +    #title {
    +      color: #838383;
    +    }
    +
    +    #type-new-db-name-here {
    +      background-color: white;
    +      height: 22px;
    +      padding: 0px 10px 0px 10px;
    +      margin-top: 3px;
    +      width: 182px;
    +      font-size: 12px;
    +      
    +      @media @smallMedia { 
    +        width:  300px;
    +        
    +      }
    +    }
    +    @media @smallMedia { 
    +      width:  300px;
    +      #title {
    +        visibility: hidden;
    +      }
    +    }
    +  }
    +
    +  #title {
    +    font-size : 10px;
    +    margin: 0px 0px 3px 0px;
    +    line-height: 12px;
    +  }
    +}
    +.footer-button {
    +  width: @footerWidthSingletons;
    +  height: 21px;
    +  line-height: 22px;
    +  font-size: 11px;
    +  margin-top: 5px;
    +  text-align: center;
    +  cursor: pointer;
    +  display: inline-block;
    +
    +  &.start-import-over-link {
    +    background-color: white;
    +    border: 1px solid #ccc;
    +    color: #908F8F;
    +    width: 100px;
    +    padding-bottom: 20px;
    +    vertical-align: text-top;
    +
    +    &:hover {
    +      text-decoration: none;
    +      color: white;
    +      background-color: #4D4D4D;
    +      border: 1px solid #4D4D4D;
    +    }
    +  }
    +
    +  &#data-import-load-button {
    +    background-color: #5bb75b;
    +    border: 1px solid #5bb75b;
    +    height: 22px;
    +    vertical-align: text-top;
    +  }
    +
    +  .fonticon{
    +    padding-right: 10px;
    +  }
    +}
    +
    +.dataIsLoading {
    --- End diff --
    
    no snake case for css


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39321763
  
    --- Diff: app/addons/components/react-components.react.jsx ---
    @@ -1081,6 +1105,114 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) {
     
       });
     
    +  var ToggleState = React.createClass({
    +    getDefaultProps: function () {
    +      return {
    +        defaultLeft: 'true'
    +      };
    +    },
    +    render: function () {
    +      var config = this.props.toggleConfig,
    +          defaultLeft = false,
    +          defaultRight = false;
    +
    +      var title = config.title,
    +          leftLabel = config.leftLabel,
    +          rightLabel = config.rightLabel,
    +          leftClick = config.leftClick,
    +          rightClick = config.rightClick,
    +          enclosingID = config.enclosingID;
    +
    +      if (config.defaultLeft) {
    +        defaultLeft = "checked";
    +        defaultRight = false;
    --- End diff --
    
    Can these just be true/false? Seems a little odd to have the values be both strings and booleans. I think "true" works.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39639065
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,382 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.js';
    +
    +  var DATA_IMPORTER_NUMBERS = {
    +    BIG_FILE_SIZE_CAP: 750000,
    +    FILE_MAX_SIZE: 150000000,  //in bytes
    +    REPEAT_EVERY_3_SECONDS: 3000,
    +    MAX_ROWS_TO_PREVIEW: 500,
    +    DATA_ROW_CHUNKS: 500
    +  };
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this.reset();
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +    },
    +
    +    reset: function () {
    +      this._isDataCurrentlyLoading = false;
    +      this._hasDataLoaded = false;
    +      this._hasErrored = false;
    +      this._theData = [];
    +      this._theMetadata = [];
    +      this._smallData = [];
    +      this._showView = 'table';
    +      this._theFile = { size: 0 };
    +      this._config = this.getDefaultConfig();
    +      this._completeFn = this._config.complete;
    +      this._errorFn = this._config.error;
    +      this._fileSize = 0;
    +      this._time = "just started";
    +      this._repeatTimeID;
    +      this.fetchAllDBs();
    +      this._chunkedData = [];
    +      this._maxSize = DATA_IMPORTER_NUMBERS.FILE_MAX_SIZE;
    +      this._showErrorScreen = false;
    +      this._errorMessageArray = [];
    +      this._loadingInDBInProgress = false;
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > DATA_IMPORTER_NUMBERS.BIG_FILE_SIZE_CAP ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), DATA_IMPORTER_NUMBERS.REPEAT_EVERY_3_SECONDS);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = DATA_IMPORTER_NUMBERS.BIG_FILE_SIZE_CAP,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = (numberOfRowsToShow < DATA_IMPORTER_NUMBERS.MAX_ROWS_TO_PREVIEW) ?
    +        DATA_IMPORTER_NUMBERS.MAX_ROWS_TO_PREVIEW : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += DATA_IMPORTER_NUMBERS.DATA_ROW_CHUNKS) {
    +        var oneChunk = this._theData.slice(i, i + DATA_IMPORTER_NUMBERS.DATA_ROW_CHUNKS);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +
    +      // the following object shows all of the options available for papaparse
    +      // some are defaulted to undefined
    +      // this is from http://papaparse.com/docs#config
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.databaseIsNew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    databaseIsNew: function (targetDB) {
    +      return _.some(this._all_dbs, targetDB);
    +    },
    +
    +    showErrorScreen: function () {
    +      return this._showErrorScreen;
    +    },
    +
    +    getErrorMsg: function () {
    +      return this._errorMessageArray;
    +    },
    +
    +    goToErrorScreen: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this._showErrorScreen = true;
    +      if (resp) {
    +        messageArray.push(resp);
    +      }
    +      this._errorMessageArray.unshift(messageArray);
    +      this.triggerChange();
    +    },
    +
    +    loadIntoNewDB: function (targetDB) {
    +      $.ajax({
    +        url: FauxtonAPI.urls('databaseBaseURL', 'server', targetDB),
    +        xhrFields: { withCredentials: true },
    +        contentType: 'application/json; charset=UTF-8',
    +        method: 'PUT'
    +      }).then(function (resp) {
    +        this.loadDataIntoTarget(targetDB);
    +      }.bind(this), function (resp) {
    +        this.importFailed();
    +      }.bind(this));
    --- End diff --
    
    i think async operations belong in actions


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39323064
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    --- End diff --
    
    You don't need the `{ }` when just setting a string.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39323876
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView === 'table') {
    +        return (
    +          <table className="data-import-table">
    +            <tbody>
    +              <tr>{header}</tr>
    +              {data}
    +            </tbody>
    +          </table>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id={"<UUID>_" + i}
    +                content={JSON.stringify(obj, null, ' ')}
    +                key={i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView === "json") {
    +        return (
    +          <div id="doc-list" className="json-view">
    +            {this.rows()}
    +          </div>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    --- End diff --
    
    Same thing in this method (see prev comment).


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: WIP Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-133551520
  
    there is one bug i did not account for currently:
    
    if you only have 1-4 databases, the small dropdown to choose an existing database will show up floating in the wrong place (it's not sticking to the dropdownmenu)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39382847
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,796 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isLoadingInDBInProgress: dataImporterStore.dbPopulationInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg = {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown = {this.state.rowShown}
    +            rowsTotal = {this.state.rowsTotal}
    +            data = {this.state.data}
    +            isBigFile = {this.state.isBigFile}
    +            meta = {this.state.meta}
    +            getPreviewView = {this.state.getPreviewView}
    +            getSmallPreviewOfData = {this.state.getSmallPreviewOfData}
    +            getHeaderConfig = {this.state.getHeaderConfig}
    +            getDelimiterChosen = {this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize = {this.state.getFileSize}
    +            isLoadingInDBInProgress = {this.state.isLoadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading = {this.state.isDataCurrentlyLoading}
    +          isBigFile = {this.state.isBigFile}
    +          getFileSize = {this.state.getFileSize}
    +          getTimeSinceLoad = {this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragOver: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    --- End diff --
    
    any reason to stop propagation?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39324258
  
    --- Diff: app/addons/dataimporter/components.react.jsx ---
    @@ -0,0 +1,793 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'api',
    +  'react',
    +  'addons/dataimporter/stores',
    +  'addons/dataimporter/actions',
    +  'addons/components/react-components.react',
    +  'helpers'
    +], function (FauxtonAPI, React, Stores, Actions, Components, Helpers) {
    +
    +  var dataImporterStore = Stores.dataImporterStore;
    +
    +  var DataImporterController = React.createClass({
    +    getStoreState: function () {
    +      return {
    +        isDataCurrentlyLoading: dataImporterStore.isDataCurrentlyLoading(),
    +        hasDataLoaded: dataImporterStore.hasDataLoaded(),
    +        isBigFile: dataImporterStore.isThisABigFile(),
    +        rowShown: dataImporterStore.getRowsShown(),
    +        rowsTotal: dataImporterStore.getTotalRows(),
    +        data: dataImporterStore.getTheData(),
    +        meta: dataImporterStore.getTheMetadata(),
    +        getPreviewView: dataImporterStore.getPreviewView(),
    +        getSmallPreviewOfData: dataImporterStore.getSmallPreviewOfData(),
    +        getHeaderConfig: dataImporterStore.getConfigSetting('header'),
    +        getDelimiterChosen: dataImporterStore.getConfigSetting('delimiter'),
    +        getAllDBs: dataImporterStore.getAllDBs(),
    +        getFileSize: dataImporterStore.getFileSize(),
    +        getTimeSinceLoad: dataImporterStore.getTimeSinceLoad,
    +        getMaxSize: dataImporterStore.getMaxSize(),
    +        showErrorScreen: dataImporterStore.showErrorScreen(),
    +        errorMsg: dataImporterStore.getErrorMsg(),
    +        isloadingInDBInProgress: dataImporterStore.getIsloadingInDBInProgress()
    +      };
    +    },
    +
    +    getInitialState: function () {
    +      return this.getStoreState();
    +    },
    +
    +    componentDidMount: function () {
    +      dataImporterStore.on('change', this.onChange, this);
    +    },
    +
    +    componentWillUnmount: function () {
    +      dataImporterStore.off('change', this.onChange, this);
    +    },
    +
    +    onChange: function () {
    +      this.setState(this.getStoreState());
    +    },
    +
    +    render: function () {
    +      if (this.state.showErrorScreen) {
    +        return <DataImporterError errorMsg= {this.state.errorMsg} />;
    +      }
    +
    +      if (this.state.hasDataLoaded) {
    +        return (
    +          <DataImporterPreviewData
    +            rowShown={this.state.rowShown}
    +            rowsTotal={this.state.rowsTotal}
    +            data={this.state.data}
    +            isBigFile={this.state.isBigFile}
    +            meta={this.state.meta}
    +            getPreviewView={this.state.getPreviewView}
    +            getSmallPreviewOfData={this.state.getSmallPreviewOfData}
    +            getHeaderConfig= {this.state.getHeaderConfig}
    +            getDelimiterChosen={this.state.getDelimiterChosen}
    +            getAllDBs = {this.state.getAllDBs}
    +            filesize={this.state.getFileSize}
    +            isloadingInDBInProgress={this.state.isloadingInDBInProgress} />
    +        );
    +      }
    +
    +      return (
    +        <DataImporterDropZone
    +          isLoading={this.state.isDataCurrentlyLoading}
    +          isBigFile={this.state.isBigFile}
    +          getFileSize={this.state.getFileSize}
    +          getTimeSinceLoad={this.state.getTimeSinceLoad}
    +          maxSize = {this.state.getMaxSize} />
    +      );
    +    }
    +  });
    +
    +  var DataImporterDropZone = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        draggingOver: false,
    +        loading: this.props.isLoading,
    +        showLimitInfo: false,
    +        fileSize: this.props.getFileSize,
    +        timeSinceLoad: this.props.getTimeSinceLoad,
    +        fileTooBig: false
    +      };
    +    },
    +
    +    dragOver: function (e) {
    +      e.preventDefault();
    +      this.setState({ draggingOver: true });
    +    },
    +
    +    endDragover: function (e) {
    +      e.preventDefault();
    +      e.stopPropagation();
    +      this.setState({draggingOver: false});
    +    },
    +
    +    drop: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.dataTransfer.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    filechosen: function (e) {
    +      e.preventDefault();
    +      var file = e.nativeEvent.target.files[0];
    +      this.checkSize(file);
    +    },
    +
    +    checkSize: function (file) {
    +      this.setState({ fileSize: file.size });
    +      this.setState({ draggingOver: false });
    +
    +      if (file.size > this.props.maxSize) {
    +        this.setState({ fileTooBig: true });
    +      } else {
    +        Actions.loadFile(file);
    +        this.setState({ loading: true });
    +        Actions.dataIsCurrentlyLoading();
    +      }
    +    },
    +
    +    uploadButton: function () {
    +      return (
    +        <p>
    +          <span className="fonticon icon-file-text-alt">
    +            <span className="file-upload btn">
    +              <span className="icon icon-search"></span>
    +              Choose File
    +              <input type="file" className="upload" onChange={this.filechosen} />
    +            </span>
    +          </span>
    +        </p>
    +      );
    +    },
    +
    +    onFileLimitInfoToggle: function (e) {
    +      e.preventDefault();
    +      var toggle = this.state.showLimitInfo ? false : true;
    +      this.setState({ showLimitInfo : toggle });
    +    },
    +
    +    fileLimitLink: function (msg) {
    +      return (
    +        <div className="filetype-txt">
    +          <a href="#"
    +            className="import-data-limit-info-link"
    +            onClick={this.onFileLimitInfoToggle}
    +            data-bypass="true">
    +            {msg}
    +          </a>
    +        </div>
    +      );
    +    },
    +
    +    loadingBoxBigFileMessage: function () {
    +      return (
    +        <div className="loading-big-file-msg">
    +          <div>This is a large file: {Helpers.formatSize(this.state.fileSize)}</div>
    +          <div>Large files may take up to 5 minutes to load</div>
    +          <div>Elapsed time: {this.state.timeSinceLoad()}</div>
    +        </div>
    +      );
    +    },
    +
    +    fileTooBigMsg: function () {
    +      return (
    +        <p className="file-exceeds-max-msg">
    +          Your file was too big. Please choose another one.
    +        </p>
    +      );
    +    },
    +
    +    defaultBox: function () {
    +      var fileTooBig = this.state.fileTooBig ? this.fileTooBigMsg() : '';
    +
    +      return (
    +        <div className="dropzone"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop} >
    +          <div className="dropzone-msg default">
    +            {fileTooBig}
    +            {this.uploadButton()}
    +            <p>Or drag a file into box.</p>
    +          </div>
    +          {this.fileLimitLink("File Limitations")}
    +        </div>
    +      );
    +    },
    +
    +    boxIsDraggingOver: function () {
    +      return (
    +        <div className={"dropzone dragging-file-in-background"}
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg draggingover">
    +            <span className="fonticon icon-file-text-alt">
    +            </span>
    +            <span className="loading-msg">
    +              Drop your file.
    +            </span>
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxIsLoading: function () {
    +      var loadingBoxBigFileMessage = this.props.isBigFile ?
    +        this.loadingBoxBigFileMessage() : '';
    +
    +      return (
    +        <div className={"dropzone loading-background"}>
    +          <div className="dropzone-msg loading">
    +            Loading...
    +            {loadingBoxBigFileMessage}
    +            <Components.LoadLines />
    +          </div>
    +        </div>
    +      );
    +    },
    +
    +    boxShowLimitInfo: function () {
    +      return (
    +         <div className="dropzone limit-info"
    +          onDragOver={this.dragOver}
    +          onDragLeave={this.endDragover}
    +          onDrop={this.drop}>
    +          <div className="dropzone-msg">
    +            <p>150 MB filesize limit</p>
    +            <p>Only .csv files will import correctly</p>
    +            <p>Fine grained import options are only for files under 200KB</p>
    +          </div>
    +          {this.fileLimitLink("Close")}
    +        </div>
    +      );
    +    },
    +
    +    render: function () {
    +      var box = this.defaultBox();
    +
    +      if (this.state.draggingOver) {
    +        box = this.boxIsDraggingOver();
    +      } else if (this.state.loading) {
    +        box = this.boxIsLoading();
    +      } else if (this.state.showLimitInfo) {
    +        box = this.boxShowLimitInfo();
    +      }
    +      return box;
    +    }
    +  });
    +
    +  var DataImporterPreviewData = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        left: 0
    +      };
    +    },
    +    bigFilePreviewWarning: function () {
    +      var rowShown = this.props.rowShown,
    +          totalRows = this.props.rowsTotal;
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              Because of the size of this file, this preview only shows the
    +              first {rowShown} rows, out of {totalRows} rows total.
    +              However, if you choose to load the data into a database, the
    +              entirety of the file (all {totalRows} rows) will be imported.
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    componentDidMount: function () {
    +      document.getElementById('preview-page')
    +        .addEventListener('scroll', this.handleScroll);
    +    },
    +    componentWillUnmount: function () {
    +      document.getElementById('preview-page')
    +        .removeEventListener('scroll', this.handleScroll);
    +    },
    +    handleScroll: function (e) {
    +      this.setState({left: document.getElementById('preview-page').scrollLeft});
    +    },
    +
    +    loadingIntoDB: function () {
    +      if (this.props.isloadingInDBInProgress) {
    +        return (
    +          <div className="dataIsLoading">
    +            <Components.LoadLines />
    +          </div>
    +        );
    +      }
    +      return null;
    +    },
    +
    +    fileMetadataInfo: function () {
    +      var totalRows = this.props.rowsTotal,
    +          previewMsg = totalRows > 500 ? 'This is a 500 row (max) preview of the file.' : '';
    +
    +      return (
    +        <div className="top-row">
    +          <div className="big-file-info-message">
    +            <p className="big-file-preview-limit-info-message">
    +              All {totalRows} rows from this file will be imported. {previewMsg}
    +            </p>
    +          </div>
    +          {this.loadingIntoDB()}
    +        </div>
    +      );
    +    },
    +    render: function () {
    +
    +      var fileInfoMessage =
    +            this.props.isBigFile ? this.bigFilePreviewWarning() :  this.fileMetadataInfo(),
    +          style = {'left': this.state.left};
    +
    +      return (
    +        <div id="preview-page" >
    +          <div id="data-import-options" style={style}>
    +            {fileInfoMessage}
    +            <OptionsRow 
    +              getDelimiterChosen={this.props.getDelimiterChosen}
    +              filesize={this.props.filesize} />
    +          </div>
    +          <div className="preview-data-space">
    +            <TableView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +            <JSONView
    +              data={this.props.data}
    +              isBigFile={this.props.isBigFile}
    +              meta={this.props.meta}
    +              getPreviewView={this.props.getPreviewView}
    +              getSmallPreviewOfData={this.props.getSmallPreviewOfData}
    +              getHeaderConfig={this.props.getHeaderConfig} />
    +          </div>
    +          <Footer getAllDBs={this.props.getAllDBs} />
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var OptionsRow = React.createClass({
    +    previewToggle: function () {
    +      var config = {
    +        title: 'Preview View',
    +        leftLabel : 'Table',
    +        rightLabel : 'JSON',
    +        defaultLeft: true,
    +        leftClick: function () { Actions.setPreviewView('table'); },
    +        rightClick: function () { Actions.setPreviewView('json'); },
    +        enclosingID: 'preview-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    header: function () {
    +      var config = {
    +        title: 'Header',
    +        leftLabel : 'First Line',
    +        rightLabel : 'No Header',
    +        defaultLeft: true,
    +        leftClick: function () {  Actions.setParseConfig('header', true); },
    +        rightClick: function () { Actions.setParseConfig('header', false); },
    +        enclosingID: 'header-toggle-id'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    numbersFormat: function () {
    +      var config = {
    +        title: 'Numbers are',
    +        leftLabel : 'Numbers',
    +        rightLabel : 'Strings',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          Actions.setParseConfig('dynamicTyping', true);
    +        },
    +        rightClick: function () {
    +          Actions.setParseConfig('dynamicTyping', false);
    +        },
    +        enclosingID: 'numbers-toggle-id'
    +      };
    +      return <Components.ToggleState toggleConfig={config} />;
    +
    +    },
    +
    +    delimiter: function () {
    +      var selected = this.props.getDelimiterChosen;
    +      selected = selected === '' ? 'Automatic' : selected;
    +      selected = selected === '\t' ? 'Tab' : selected;
    +
    +      var setup = {
    +        title: 'Delimiter',
    +        id: 'data-importer-delimiter',
    +        selected: selected,
    +        selectOptions: [
    +          {
    +            name: 'Automatic',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '');
    +            }
    +          },
    +          {
    +            name: 'Comma',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ',');
    +            }
    +          },
    +          {
    +            name: 'Tab',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '\t');
    +            }
    +          },
    +          {
    +            name: 'Semicolon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ';');
    +            }
    +          },
    +          {
    +            name: 'Colon',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', ':');
    +            }
    +          },
    +          {
    +            name: 'Hyphen',
    +            onClick: function () {
    +              Actions.setParseConfig('delimiter', '-');
    +            }
    +          },
    +        ]
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup}/>;
    +    },
    +
    +    renderControls: function () {
    +      if (this.props.filesize < 200000) {
    +        return (
    +          <span>
    +            {this.header()}
    +            {this.numbersFormat()}
    +            {this.delimiter()}
    +          </span>
    +        );
    +      }
    +
    +      return (
    +        <p className="no-options-available">
    +          Fine grained import options are disabled for files exceeding 200KB
    +        </p>
    +      );
    +    },
    +
    +    render: function () {
    +      var controls = this.renderControls();
    +
    +      return (
    +        <div className="import-options-row">
    +          <div className="options-row">
    +            {this.previewToggle()}
    +            {controls}
    +          </div>
    +        </div>
    +      );
    +    }
    +  });
    +
    +  var TableView = React.createClass({
    +    eachRow: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          if (i < 500) {
    +            return <tr key={i}>{this.insideEachRow(dataObj)}</tr>;
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    insideEachRow: function (dataObj) {
    +      return _.map(dataObj, function (dataVal, dataKey) {
    +        return <td key={dataKey} title={dataVal}>{dataVal}</td>;
    +      });
    +    },
    +
    +    header: function () {
    +      var header = null;
    +
    +      if (this.props.getHeaderConfig) {
    +        header = this.props.meta.fields;
    +        return (
    +          header.map(function (field, i) {
    +            return <th key={i} title={field}>{field}</th>;
    +          })
    +        );
    +      } else {
    +        header = this.props.data;
    +        return (
    +          header.map(function (field, i) {
    +           return <th key={i} title={i}>{i}</th>;
    +          })
    +        );
    +      }
    +    },
    +
    +    render: function () {
    +      var data = this.eachRow(),
    +          header = this.header();
    +
    +      if (this.props.getPreviewView === 'table') {
    +        return (
    +          <table className="data-import-table">
    +            <tbody>
    +              <tr>{header}</tr>
    +              {data}
    +            </tbody>
    +          </table>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var JSONView = React.createClass({
    +    objectify: function (array) {
    +      return _.reduce(array, function (obj, val, i) {
    +        obj[i] = val;
    +        return obj;
    +      }, {});
    +    },
    +
    +    rows: function () {
    +      var data = this.props.data;
    +
    +      if (this.props.isBigFile) {
    +        data = this.props.getSmallPreviewOfData;
    +      }
    +
    +      return (
    +        data.map(function (dataObj, i) {
    +          var obj = this.props.getHeaderConfig ? dataObj :
    +            this.objectify(dataObj);
    +          if (i < 500) {
    +            return (
    +              <Components.SimpleDoc
    +                id={"<UUID>_" + i}
    +                content={JSON.stringify(obj, null, ' ')}
    +                key={i} />
    +            );
    +          }
    +        }.bind(this))
    +      );
    +    },
    +
    +    render: function () {
    +      if (this.props.getPreviewView === "json") {
    +        return (
    +          <div id="doc-list" className="json-view">
    +            {this.rows()}
    +          </div>
    +        );
    +      } else {
    +        return null;
    +      }
    +    }
    +  });
    +
    +  var Footer = React.createClass({
    +    getInitialState: function () {
    +      return {
    +        targetDB: this.props.getAllDBs[0],
    +        selectExistingDB: true
    +      };
    +    },
    +
    +    startOverButton: function () {
    +      return (
    +        <a className="start-import-over-link footer-button"
    +          onClick={this.startover}>
    +          <span className="fonticon icon-repeat"></span>
    +            Start Over
    +        </a>
    +      );
    +    },
    +
    +    startover: function () {
    +      Actions.dataImporterInit(true);
    +    },
    +
    +    getExistingDBList: function () {
    +      var allDBs = this.props.getAllDBs,
    +          options = [],
    +          setTargetDB = this.setTargetDB;
    +
    +      _.each(allDBs, function (dbName, i) {
    +        options.push({
    +          name: dbName,
    +          onClick: function () { setTargetDB(dbName); }
    +        });
    +      });
    +
    +      return options;
    +    },
    +
    +    setTargetDB: function (dbName) {
    +      this.setState({ targetDB: dbName });
    +    },
    +
    +    newOrExistingToggle : function () {
    +      var config = {
    +        title: 'Load into',
    +        leftLabel : 'Existing database',
    +        rightLabel : 'New database',
    +        defaultLeft: true,
    +        leftClick: function () {
    +          this.setState({
    +            selectExistingDB: true,
    +            targetDB: this.props.getAllDBs[0]
    +          });
    +        }.bind(this),
    +        rightClick: function () {
    +          this.setState({ selectExistingDB: false });
    +        }.bind(this),
    +        enclosingID: 'choose-database-to-load-data-into'
    +      };
    +
    +      return <Components.ToggleState toggleConfig={config} />;
    +    },
    +
    +    chooseDatabaseFromDropdown : function () {
    +      var selected = this.state.targetDB;
    +
    +      var setup = {
    +        title: 'Choose a database',
    +        id: 'data-importer-choose-db',
    +        selected: selected,
    +        selectOptions: this.getExistingDBList()
    +      };
    +      return <Components.SmallDropdown dropdownSetup={setup} />;
    +    },
    +
    +    createNewDB: function () {
    +      return (
    +        <div id="data-import-name-new-target-database">
    +          <div id="title">Name New Database</div>
    +          <input
    +            id="type-new-db-name-here"
    +            type="text"
    +            placeholder="Type new database name..."
    +            value={this.state.newDatabaseName}
    +            onChange={this.newDatabaseNameChange} />
    +        </div>
    +      );
    +    },
    +
    +    newDatabaseNameChange: function (e) {
    +      var newName = e.target.value;
    +      this.setState({ targetDB : newName });
    +    },
    +
    +    importButton : function () {
    --- End diff --
    
    extra space before `:`


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39325012
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.dataBaseIsnew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    dataBaseIsnew: function (targetDB) {
    +      return _.some(this._all_dbs, targetDB);
    --- End diff --
    
    Ooohh I love it. I don't think I've used `_.some()`


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#issuecomment-140223712
  
    OK ready for review :) (monday 14 sept 2015) . I've changed most of the comments that I could change. 
    Some things could be improved (the two comments I didn't change), but they are working as is. Until I can think of a better way, I think these will have to do.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by robertkowalski <gi...@git.apache.org>.
Github user robertkowalski commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39639173
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,382 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.js';
    +
    +  var DATA_IMPORTER_NUMBERS = {
    +    BIG_FILE_SIZE_CAP: 750000,
    +    FILE_MAX_SIZE: 150000000,  //in bytes
    +    REPEAT_EVERY_3_SECONDS: 3000,
    +    MAX_ROWS_TO_PREVIEW: 500,
    +    DATA_ROW_CHUNKS: 500
    +  };
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this.reset();
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +    },
    +
    +    reset: function () {
    +      this._isDataCurrentlyLoading = false;
    +      this._hasDataLoaded = false;
    +      this._hasErrored = false;
    +      this._theData = [];
    +      this._theMetadata = [];
    +      this._smallData = [];
    +      this._showView = 'table';
    +      this._theFile = { size: 0 };
    +      this._config = this.getDefaultConfig();
    +      this._completeFn = this._config.complete;
    +      this._errorFn = this._config.error;
    +      this._fileSize = 0;
    +      this._time = "just started";
    +      this._repeatTimeID;
    +      this.fetchAllDBs();
    +      this._chunkedData = [];
    +      this._maxSize = DATA_IMPORTER_NUMBERS.FILE_MAX_SIZE;
    +      this._showErrorScreen = false;
    +      this._errorMessageArray = [];
    +      this._loadingInDBInProgress = false;
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > DATA_IMPORTER_NUMBERS.BIG_FILE_SIZE_CAP ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), DATA_IMPORTER_NUMBERS.REPEAT_EVERY_3_SECONDS);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = DATA_IMPORTER_NUMBERS.BIG_FILE_SIZE_CAP,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = (numberOfRowsToShow < DATA_IMPORTER_NUMBERS.MAX_ROWS_TO_PREVIEW) ?
    +        DATA_IMPORTER_NUMBERS.MAX_ROWS_TO_PREVIEW : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += DATA_IMPORTER_NUMBERS.DATA_ROW_CHUNKS) {
    +        var oneChunk = this._theData.slice(i, i + DATA_IMPORTER_NUMBERS.DATA_ROW_CHUNKS);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +
    +      // the following object shows all of the options available for papaparse
    +      // some are defaulted to undefined
    +      // this is from http://papaparse.com/docs#config
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.databaseIsNew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    databaseIsNew: function (targetDB) {
    +      return _.some(this._all_dbs, targetDB);
    +    },
    +
    +    showErrorScreen: function () {
    +      return this._showErrorScreen;
    +    },
    +
    +    getErrorMsg: function () {
    +      return this._errorMessageArray;
    +    },
    +
    +    goToErrorScreen: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this._showErrorScreen = true;
    +      if (resp) {
    +        messageArray.push(resp);
    +      }
    +      this._errorMessageArray.unshift(messageArray);
    +      this.triggerChange();
    +    },
    +
    +    loadIntoNewDB: function (targetDB) {
    +      $.ajax({
    +        url: FauxtonAPI.urls('databaseBaseURL', 'server', targetDB),
    +        xhrFields: { withCredentials: true },
    +        contentType: 'application/json; charset=UTF-8',
    +        method: 'PUT'
    +      }).then(function (resp) {
    +        this.loadDataIntoTarget(targetDB);
    +      }.bind(this), function (resp) {
    +        this.importFailed();
    +      }.bind(this));
    +    },
    +
    +    loadDataIntoTarget: function (targetDB) {
    +      var loadURL = FauxtonAPI.urls('document', 'server', targetDB, '_bulk_docs');
    +      _.each(this._chunkedData, function (data, i) {
    +        var payload = JSON.stringify({ 'docs': data });
    +        var prettyprint = JSON.stringify({ 'docs': data }, null, 2);
    +        $.ajax({
    +          url: loadURL,
    +          xhrFields: { withCredentials: true },
    +          contentType: 'application/json; charset=UTF-8',
    +          method: 'POST',
    +          data: payload
    +        }).then(function (resp) {
    +          i++;
    +          if (i === this._chunkedData.length ) {
    +            //console.log("alldone");
    +            this.successfulImport(targetDB);
    +            this.reset();
    +          }
    +        }.bind(this), function (resp) {
    +          this.importFailed(resp, ['There was an error loading documents into ' + targetDB, 'Data that failed to load:' + prettyprint]);
    +        }.bind(this));
    +      }.bind(this));
    +    },
    +
    +    successfulImport: function (targetDB) {
    +      FauxtonAPI.navigate(FauxtonAPI.urls('allDocs', 'app', targetDB, '?include_docs=true'));
    --- End diff --
    
    this is also something you want to do usually in an action


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39325110
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.dataBaseIsnew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    dataBaseIsnew: function (targetDB) {
    +      return _.some(this._all_dbs, targetDB);
    +    },
    +
    +    showErrorScreen: function () {
    +      return this._showErrorScreen;
    +    },
    +
    +    getErrorMsg: function () {
    +      return this._errorMessageArray;
    +    },
    +
    +    goToErrorScreen: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this._showErrorScreen = true;
    +      if (resp) messageArray.push(resp);
    --- End diff --
    
    I'm surprised this doesn't freak out the style checker. All `if`'s should have curly braces, even one liners.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by benkeen <gi...@git.apache.org>.
Github user benkeen commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39325466
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    +      } // else keeps store as it was when you left the page
    +      this.fetchAllDBs();
    +
    +    },
    +
    +    fetchAllDBs: function () {
    +      $.ajax({
    +        method: "GET",
    +        dataType: "json",
    +        url: window.location.origin + "/_all_dbs"
    +      }).then(function (resp) {
    +        this._all_dbs = _.filter(resp, function (dbName) {
    +          return dbName[0] !== '_'; //_all_dbs without _ as first letter
    +        });
    +        this.triggerChange();
    +      }.bind(this));
    +    },
    +
    +    getMaxSize: function () {
    +      return this._maxSize;
    +    },
    +
    +    getAllDBs: function () {
    +      return this._all_dbs;
    +    },
    +
    +    isDataCurrentlyLoading: function () {
    +      return this._isDataCurrentlyLoading;
    +    },
    +
    +    dataIsLoading: function () {
    +      this._startTime = app.helpers.moment();
    +      this._isDataCurrentlyLoading = true;
    +    },
    +
    +    hasDataLoaded: function () {
    +      return this._hasDataLoaded;
    +    },
    +
    +    dataLoaded: function () {
    +      this._hasDataLoaded = true;
    +      clearInterval(this._repeatTimeID);
    +    },
    +
    +    loadData: function (data) {
    +      this._theData = data;
    +    },
    +
    +    getFileSize: function () {
    +      return this._theFile.size;
    +    },
    +
    +    isThisABigFile: function () {
    +      return this._theFile.size > 750000 ? true : false;
    +    },
    +
    +    getTimeSinceLoad: function () {
    +      return this._time;
    +    },
    +
    +    repeatTime: function () {
    +      this._repeatTimeID = setInterval(function () {
    +        var secondsElapsed = app.helpers.moment().diff(this._startTime);
    +        this._time = app.helpers.getDateFromNow(this._startTime);
    +        if (secondsElapsed < 60000) {
    +          this._time = '~' + Math.ceil(secondsElapsed / 1000) + ' seconds ago';
    +        }
    +        this.triggerChange();
    +      }.bind(this), 3000);
    +    },
    +
    +    loadMeta: function (meta) {
    +      this._theMetadata = meta;
    +    },
    +
    +    loadFile: function (file) {
    +      this._theFile = file;
    +      this.repeatTime();
    +    },
    +
    +    getTotalRows: function () {
    +      return this._totalRows;
    +    },
    +
    +    getPreviewView: function () {
    +      return this._showView;
    +    },
    +
    +    setPreviewView: function (type) {
    +      this._showView = type;
    +    },
    +
    +    calcSmallPreviewOfData: function () {
    +      //this is incase the file has large rows
    +      var filesize = this._theFile.size,
    +          rows = this._totalRows,
    +          sizeOfEachRow = filesize / rows, //this is approximate
    +          sizeCap = 800000,  //in bytes
    +          numberOfRowsToShow;
    +
    +      numberOfRowsToShow = Math.ceil(sizeCap / sizeOfEachRow);
    +      numberOfRowsToShow = numberOfRowsToShow < 500 ? 500 : numberOfRowsToShow;
    +
    +      this._rowsShown = numberOfRowsToShow;
    +      this._smallData = this._theData.slice(0, this._rowsShown);
    +    },
    +
    +    getSmallPreviewOfData: function () {
    +      return this._smallData;
    +    },
    +
    +    getRowsShown: function () {
    +      return this._rowsShown;
    +    },
    +
    +    getTheMetadata: function () {
    +      return this._theMetadata;
    +    },
    +
    +    getTheData: function () {
    +      return this._theData;
    +    },
    +
    +    papaparse: function () {
    +      //for some reason this._config.complete gets overwritten on setconfig
    +      this._config.complete = this._completeFn;
    +      this._config.error = this._errorFn;
    +      Papa.parse(this._theFile, this._config);
    +    },
    +
    +    loadingComplete: function (results) {
    +      this.loadMeta(results.meta);
    +      this.loadData(results.data);
    +      this._totalRows = this._theData.length;
    +
    +      if (this.isThisABigFile()) {
    +        this.calcSmallPreviewOfData();
    +      }
    +
    +      this.chunkData();
    +      this.dataLoaded();
    +      this.triggerChange();
    +    },
    +
    +    chunkData: function () {
    +      for (var i = 0; i < this._theData.length; i += 500) {
    +        var oneChunk = this._theData.slice(i, i + 500);
    +        this._chunkedData.push(oneChunk);
    +      }
    +    },
    +
    +    clearData: function () {
    +      this._theData = [];
    +    },
    +
    +    setParseConfig: function (key, value) {
    +      this._config[key] = value;
    +    },
    +
    +    getConfigSetting: function (key) {
    +      return this._config[key];
    +    },
    +
    +    getDefaultConfig: function () {
    +      return {
    +        delimiter : '',  // auto-detect
    +        newline: '',  // auto-detect
    +        header: true,
    +        dynamicTyping: true,
    +        preview: 0,
    +        encoding: '',
    +        worker: true, //true = page doesn't lock up
    +        comments: false,
    +        complete: function (results) {
    +          this.loadingComplete(results);
    +          this._changingConfig = false;
    +        }.bind(this),
    +        error: function () {
    +          var msg1 = 'There was an error while parsing the file.',
    +              msg2 = 'Please try again.';
    +
    +          this.goToErrorScreen('', [msg1, msg2]);
    +        }.bind(this),
    +        download: false,
    +        skipEmptyLines: false,
    +        chunk: undefined, //define function for streaming
    +        beforeFirstChunk: undefined,
    +      };
    +    },
    +
    +    loadDataIntoDatabase: function (createNewDB, targetDB) {
    +      this._loadingInDBInProgress = true;
    +      this.triggerChange();
    +      if (createNewDB) {
    +        if (this.dataBaseIsnew(targetDB)) {
    +          var msg1 = 'The database ' + targetDB + ' already exists.';
    +          this.goToErrorScreen('', [msg1]);
    +        }
    +        this.loadIntoNewDB(targetDB);
    +      } else {
    +        this.loadDataIntoTarget(targetDB);
    +      }
    +    },
    +
    +    dataBaseIsnew: function (targetDB) {
    +      return _.some(this._all_dbs, targetDB);
    +    },
    +
    +    showErrorScreen: function () {
    +      return this._showErrorScreen;
    +    },
    +
    +    getErrorMsg: function () {
    +      return this._errorMessageArray;
    +    },
    +
    +    goToErrorScreen: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this._showErrorScreen = true;
    +      if (resp) messageArray.push(resp);
    +      this._errorMessageArray.unshift(messageArray);
    +      this.triggerChange();
    +    },
    +
    +    loadIntoNewDB: function (targetDB) {
    +      $.ajax({
    +        url: FauxtonAPI.urls('databaseBaseURL', 'server', targetDB),
    +        xhrFields: { withCredentials: true },
    +        contentType: 'application/json; charset=UTF-8',
    +        method: 'PUT'
    +      }).then(function (resp) {
    +        this.loadDataIntoTarget(targetDB);
    +      }.bind(this), function (resp) {
    +        this.importFailed();
    +      }.bind(this));
    +    },
    +
    +    loadDataIntoTarget: function (targetDB) {
    +      var loadURL = FauxtonAPI.urls('document', 'server', targetDB, '_bulk_docs');
    +      _.each(this._chunkedData, function (data, i) {
    +        var payload = JSON.stringify({ 'docs': data });
    +        var prettyprint = JSON.stringify({ 'docs': data }, null, 2);
    +        $.ajax({
    +          url: loadURL,
    +          xhrFields: { withCredentials: true },
    +          contentType: 'application/json; charset=UTF-8',
    +          method: 'POST',
    +          data: payload
    +        }).then(function (resp) {
    +          i++;
    +          if (i === this._chunkedData.length ) {
    +            //console.log("alldone");
    +            this.successfulImport(targetDB);
    +            this.init(true);
    +          }
    +        }.bind(this), function (resp) {
    +          this.importFailed(resp, ['There was an error loading documents into ' + targetDB, 'Data that failed to load:' + prettyprint]);
    +        }.bind(this));
    +      }.bind(this));
    +    },
    +
    +    successfulImport: function (targetDB) {
    +      FauxtonAPI.navigate(FauxtonAPI.urls('allDocs', 'app', targetDB, '?include_docs=true'));
    +    },
    +
    +    importFailed: function (resp, messageArray) {
    +      this._loadingInDBInProgress = false;
    +      this.goToErrorScreen(resp, messageArray);
    +    },
    +
    +    getIsloadingInDBInProgress: function () {
    +      return this._loadingInDBInProgress;
    +    },
    +
    +    dispatch: function (action) {
    +      switch (action.type) {
    +        case ActionTypes.DATA_IMPORTER_INIT:
    +          this.init(action.firstTimeHere);
    +          this.triggerChange();
    +        break;
    +
    +        case ActionTypes.DATA_IMPORTER_DATA_IS_CURRENTLY_LOADING:
    +          this.dataIsLoading();
    +          this.triggerChange();
    +        break;
    +
    +        case ActionTypes.DATA_IMPORTER_LOAD_FILE:
    +          this.loadFile(action.file);
    +          this.papaparse(action.file);
    +          this.triggerChange();
    +        break;
    +
    +        case ActionTypes.DATA_IMPORTER_SET_PREVIEW_VIEW:
    +          this.setPreviewView(action.view);
    +          this.triggerChange();
    +        break;
    +
    +        case ActionTypes.DATA_IMPORTER_SET_PARSE_CONFIG:
    +          this.setParseConfig(action.key, action.value);
    +          this.clearData();
    +          this.papaparse(this._theFile);
    +        break;
    +
    +        case ActionTypes.DATA_IMPORTER_GET_ALL_DBS:
    +          this.getAllDBs(action.databases);
    +        break;
    --- End diff --
    
    What's the purpose of this being here? Is it used at all? 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] couchdb-fauxton pull request: Data importer

Posted by michellephung <gi...@git.apache.org>.
Github user michellephung commented on a diff in the pull request:

    https://github.com/apache/couchdb-fauxton/pull/495#discussion_r39332369
  
    --- Diff: app/addons/dataimporter/stores.js ---
    @@ -0,0 +1,369 @@
    +// Licensed under the Apache License, Version 2.0 (the "License"); you may not
    +// use this file except in compliance with the License. You may obtain a copy of
    +// the License at
    +//
    +//   http://www.apache.org/licenses/LICENSE-2.0
    +//
    +// Unless required by applicable law or agreed to in writing, software
    +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    +// License for the specific language governing permissions and limitations under
    +// the License.
    +
    +define([
    +  'app',
    +  'api',
    +  'addons/dataimporter/actiontypes',
    +  'papaparse'
    +], function (app, FauxtonAPI, ActionTypes, Papa) {
    +
    +  Papa.SCRIPT_PATH = '../../assets/js/libs/papaparse.min.js';
    +
    +  var DataImporterStore = FauxtonAPI.Store.extend({
    +
    +    init: function (firstTimeHere) { //to reset, call this with true
    +      if (firstTimeHere) {
    +        this._isDataCurrentlyLoading = false;
    +        this._hasDataLoaded = false;
    +        this._hasErrored = false;
    +        this._theData = [];
    +        this._theMetadata = [];
    +        this._smallData = [];
    +        this._showView = 'table';
    +        this._theFile = { size: 0 };
    +        this._config = this.getDefaultConfig();
    +        this._completeFn = this._config.complete;
    +        this._errorFn = this._config.error;
    +        this._fileSize = 0;
    +        this._time = "just started";
    +        this._repeatTimeID;
    +        this.fetchAllDBs();
    +        this._chunkedData = [];
    +        this._maxSize = 150000000;  //in bytes
    +        this._showErrorScreen = false;
    +        this._errorMessageArray = [];
    +        this._loadingInDBInProgress = false;
    --- End diff --
    
    i like reset(). will do on monday


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---