From cd25e2a84230b3fcaf81088c0bd3e472ee3b995c Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 14 Aug 2019 16:07:21 -0400 Subject: [PATCH] stuff for #119 --- .../components/DependentColumnInput/index.jsx | 28 +- .../components/FileUploadForm/index.js | 27 +- lab/webapp/src/components/FileUpload/index.js | 874 +----------------- 3 files changed, 33 insertions(+), 896 deletions(-) diff --git a/lab/webapp/src/components/FileUpload/components/DependentColumnInput/index.jsx b/lab/webapp/src/components/FileUpload/components/DependentColumnInput/index.jsx index 789309361..28d00db84 100644 --- a/lab/webapp/src/components/FileUpload/components/DependentColumnInput/index.jsx +++ b/lab/webapp/src/components/FileUpload/components/DependentColumnInput/index.jsx @@ -21,35 +21,39 @@ class DependentColumnInput extends Component { // TODO - fix dropdown values render() { const { depColDropdown, dependentCol, depColCallback } = this.props; + window.console.log('dependentCol', dependentCol); return (

Dependent Column

- - + + - + + {/* + />*/} { - window.console.log('got file', event.target.files); + //window.console.log('got file', event.target.files); const fileExtList = ['csv', 'tsv']; let papaConfig = { header: true, - preview: 5, complete: (result) => { - //window.console.log('preview of uploaded data: ', result); + window.console.log('preview of uploaded data: ', result); this.setState({datasetPreview: result}); } }; diff --git a/lab/webapp/src/components/FileUpload/index.js b/lab/webapp/src/components/FileUpload/index.js index 5fef3f2de..9695297f5 100644 --- a/lab/webapp/src/components/FileUpload/index.js +++ b/lab/webapp/src/components/FileUpload/index.js @@ -34,883 +34,11 @@ class FileUpload extends Component { */ constructor(props) { super(props); - - this.state = { - selectedFile: null, - dependentCol: '', - catFeatures: '', - ordinalFeatures: {}, - ordKeys: [], - ordinalIndex: 0, - activeAccordionIndexes: [], - ordModal: false - }; - - // enter info in text fields - this.handleDepColField = this.handleDepColField.bind(this); - this.handleCatFeatures = this.handleCatFeatures.bind(this); - this.handleOrdinalFeatures = this.handleOrdinalFeatures.bind(this); - this.getDataTablePreview = this.getDataTablePreview.bind(this); - this.getAccordionInputs = this.getAccordionInputs.bind(this); - this.generateFileData = this.generateFileData.bind(this); - this.errorPopupTimeout = this.errorPopupTimeout.bind(this); - this.getDataKeys = this.getDataKeys.bind(this); - this.getDropDown = this.getDropDown.bind(this); - this.depColDropDownClickHandler = this.depColDropDownClickHandler.bind(this); - this.catDropDownClickHandler = this.catDropDownClickHandler.bind(this); - this.ordDropDownClickHandler = this.ordDropDownClickHandler.bind(this); - this.isJson = this.isJson.bind(this); - this.ordModalClose = this.ordModalClose.bind(this); - //this.cleanedInput = this.cleanedInput.bind(this) - - // help text for dataset upload form - dependent column, categorical & ordinal features - this.depColHelpText = `The column that describes how each row is classified. - For example, if analyzing a dataset of patients with different types of diabetes, - this column may have the values "type1", "type2", or "none".`; - - this.catFeatHelpText = (

Categorical features have a discrete number of categories that do not have an intrinsic order. - Some examples include sex ("male", "female") or eye color ("brown", "green", "blue"...). -

- Describe these features using a comma separated list of the field names: - sex, eye_color

); - - this.ordFeatHelpText = (

Ordinal features have a discrete number of categories, - and the categories have a logical order. Some examples include size ("small", - "medium", "large"), or rank results ("first", "second", "third"). -

- Describe these features using a json map. The map key is the name of the field, - and the map value is an ordered list of the values the field can take: - {"{\"rank\":[\"first\", \"second\", \"third\"], \"size\":[\"small\", \"medium\", \"large\"]}"}

); - } - - /** - * React lifecycle method, when component loads into html dom, 'reset' state - */ - componentDidMount() { - this.setState({ - selectedFile: null, - dependentCol: '', - catFeatures: '', - ordinalFeatures: '', - ordinalIndex: 0, - ordOrderList: [], - activeAccordionIndexes: [], - errorResp: undefined - }); - } - - /** - * Strip input of potentially troublesome characters, from here: - * https://stackoverflow.com/questions/3780696/javascript-string-replace-with-regex-to-strip-off-illegal-characters - * need to figure out what characters will be allowed - * - * @param {string} inputText - user input. - * @returns {string} stripped user input of bad characters - */ - purgeUserInput(inputText) { - let cleanedInput = inputText.replace(/[|&;$%@<>()+]/g, ""); - return cleanedInput; - } - - /** - * Text field for entering dependent column, sets component react state with - * user input - * @param {Event} e - DOM Event from user interacting with UI text field - * @param {Object} props - react props object - * @returns {void} - no return value - */ - handleDepColField(e) { - //let safeInput = this.purgeUserInput(props.value); - //window.console.log('safe input: ', safeInput); - this.setState({ - dependentCol: e.target.value, - errorResp: undefined - }); - } - - /** - * text field/area for entering categorical features - * user input - * @param {Event} e - DOM Event from user interacting with UI text field - * @returns {void} - no return value - */ - handleCatFeatures(e) { - //let safeInput = this.purgeUserInput(e.target.value); - //window.console.log('safe input cat: ', safeInput); - this.setState({ - catFeatures: e.target.value, - errorResp: undefined - }); - } - - /** - * text field/area for entering ordinal features - * user input - * @param {Event} e - DOM Event from user interacting with UI text field - * @param {Object} props - react props object - * @returns {void} - no return value - */ - handleOrdinalFeatures(e) { - //window.console.log('ord props: ', props); - //let safeInput = this.purgeUserInput(props.value); - //window.console.log('safe input ord: ', safeInput); - this.setState({ - ordinalFeatures: e.target.value, - errorResp: undefined - }); - } - - /** - * Helper method to consolidate user input to send with file upload form - * @returns {FormData} - FormData object containing user input data - */ - generateFileData = () => { - const data = new FormData(); - this.setState({errorResp: undefined}); - let depCol = this.state.dependentCol; - let ordFeatures = this.state.ordinalFeatures; - let catFeatures = this.state.catFeatures; - let selectedFile = this.state.selectedFile; - let tempOrdinalFeats = ''; - if(selectedFile && selectedFile.name) { - // get raw user input from state - - // try to parse ord features input as JSON if not empty - if(ordFeatures !== '' ) { - try { - // only try to parse input with JSON.parse() if string - if(typeof ordFeatures === 'string') { - tempOrdinalFeats = JSON.parse(ordFeatures); - } else if(this.isJson(ordFeatures)) { - tempOrdinalFeats = ordFeatures; - } - } catch(e) { - // if expecting oridinal stuff, return error to stop upload process - return { errorResp: e.toString() }; - } - } - - if(catFeatures !== '') { - // remove all whitespace - catFeatures = catFeatures.replace(/ /g, ''); - // parse on comma - catFeatures = catFeatures.split(','); - // if input contains empty items - ex: 'one,,two,three' - // filter out resulting empty item - catFeatures = catFeatures.filter(item => { - return item !== '' - }) - } - - // keys specified for server to upload repsective fields - let metadata = JSON.stringify({ - 'name': this.state.selectedFile.name, - 'username': 'testuser', - 'timestamp': Date.now(), - 'dependent_col' : depCol, - 'categorical_features': catFeatures, - 'ordinal_features': tempOrdinalFeats - }); - - data.append('_metadata', metadata); - data.append('_files', this.state.selectedFile); - - } else { - window.console.log('no file available'); - } - - return data; - } - - /** - * Event handler for selecting files, takes user file from html file input, stores - * selected file in component react state, generates file preview and stores that - * in the state as well. If file is valid does the abovementioned, else error - * is generated - * @param {Event} event - DOM Event from user interacting with UI text field - * @returns {void} - no return value - */ - handleSelectedFile = event => { - - const fileExtList = ['csv', 'tsv']; // acceptable file extensions - let papaConfig = { - header: true, - preview: 5, - complete: (result) => { - //window.console.log('preview of uploaded data: ', result); - this.setState({datasetPreview: result}); - } - }; - - // check for selected file - if(event.target.files && event.target.files[0]) { - // immediately try to get dataset preview on file input html element change - // need to be mindful of garbage data/files - let uploadFile = event.target.files[0] - let fileExt = uploadFile.name.split('.').pop(); - - //Papa.parse(event.target.files[0], papaConfig); - // check file extensions - if (fileExtList.includes(fileExt)) { - // use try/catch block to deal with potential bad file input when trying to - // generate file/csv preview, use filename to check file extension - try { - Papa.parse(uploadFile, papaConfig); - } - catch(error) { - console.error('Error generating preview for selected file:', error); - this.setState({ - selectedFile: undefined, - errorResp: JSON.stringify(error), - datasetPreview: null, - openFileTypePopup: false, - dependentCol: '', - catFeatures: '', - ordinalFeatures: '', - ordKeys: [] - }); - } - // set state with file preview if parse successful, reset form input - this.setState({ - selectedFile: event.target.files[0], - errorResp: undefined, - datasetPreview: null, - openFileTypePopup: false, - dependentCol: '', - catFeatures: '', - ordinalFeatures: '', - ordKeys: [] - }); - } else { - // reset state as fallback if no file type is not supported - console.warn('Filetype not csv or tsv:', uploadFile); - this.setState({ - selectedFile: null, - datasetPreview: null, - errorResp: undefined, - openFileTypePopup: true, - dependentCol: '', - catFeatures: '', - ordinalFeatures: '', - ordKeys: [] - }); - } - } else { - // reset state as fallback if no file selected - this.setState({ - selectedFile: null, - datasetPreview: null, - errorResp: undefined, - openFileTypePopup: false, - dependentCol: '', - catFeatures: '', - ordinalFeatures: '', - ordKeys: [] - }); - } - } - - /** - * Starts download process, takes user input, creates a request payload (new html Form) - * and sends data to server through redux action, uploadDataset, which is a promise. - * When promise resolves update UI or redirect page depending on success/error. - * Upon error display error message to user, on success redirect to dataset page - * @returns {void} - no return value - */ - handleUpload = () => { - const { uploadDataset } = this.props; - // only attempt upload if there is a selected file with a filename - if(this.state.selectedFile && this.state.selectedFile.name) { - let data = this.generateFileData(); // should be FormData - // if trying to create FormData results in error, don't attempt upload - if (data.errorResp) { - this.setState({errorResp: data.errorResp}); - } else { - // after uploading a dataset request new list of datasets to update the page - uploadDataset(data).then(stuff => { - //window.console.log('FileUpload props after download', this.props); - - - //let resp = Object.keys(this.props.dataset.fileUploadResp); - let resp = this.props.dataset.fileUploadResp; - let errorRespObj = this.props.dataset.fileUploadError; - - // if no error message and successful upload (indicated by presence of dataset_id) - // 'refresh' page when upload response from server is not an error and - // redirect to dataset page, when error occurs set component state - // to display popup containing server/error response - if (!errorRespObj && resp.dataset_id) { - this.props.fetchDatasets(); - window.location = '#/datasets'; - } else { - this.setState({ - errorResp: errorRespObj.errorResp.error || "Something went wrong" - }) - } - }); - } - - - } else { - window.console.log('no file available'); - this.setState({ - errorResp: 'No file available' - }); - } - - } - - /** - * Accordion click handler which updates active index for different text areas - * in dataset upload form, use react state to keep track of which indicies are - * active & also clear any error message - */ - handleAccordionClick = (e, titleProps) => { - const { index } = titleProps; - const { activeAccordionIndexes } = this.state; - const newIndex = [...activeAccordionIndexes]; // make copy of array in state - const currentIndexPosition = activeAccordionIndexes.indexOf(index); - - if (currentIndexPosition > -1) { - newIndex.splice(currentIndexPosition, 1); - } else { - newIndex.push(index); - } - - this.setState({ - activeAccordionIndexes: newIndex, - errorResp: undefined - }) - - } - - /** - * Get list of keys/column names from data preview - * @returns {Array} - use js Object.keys(...) to get list of keys - */ - getDataKeys() { - const { datasetPreview } = this.state; - let dataKeys = []; - if(datasetPreview) { - //dataKeys = Object.keys(datasetPreview); - dataKeys = datasetPreview.meta.fields; - } - return dataKeys; - } - - /** - * simple click handler for selecting dependent column - */ - depColDropDownClickHandler(e, d) { - this.setState({ - dependentCol: d.text - }); } - /** - * take selected key and generate comma separated list of values for given key - */ - catDropDownClickHandler(e, d) { - const { datasetPreview, catFeatures } = this.state; - let selectedKey = d.text; - let tempList; - // if categorical features is not empty, try to split on comma - catFeatures !== '' ? tempList = catFeatures.split(',') : tempList = []; - // keep track of if currently selected category is already in list - let catIndex = tempList.indexOf(selectedKey); - // if category already in list, remove it, else add it - catIndex > -1 ? tempList.splice(catIndex, 1) : tempList.push(selectedKey); - this.setState({ - catFeatures: tempList.join() - }); - } - - /** - * take selected key and generate ordered list of values for given key - */ - ordDropDownClickHandler(e, d) { - const { datasetPreview, ordKeys, ordinalFeatures } = this.state; - let selectedKey = d.text; - //let tempOrdKeys = [...ordKeys]; - let tempOrdKeys = []; - // if ordinalFeatures is proper json, can get keys - if(typeof ordinalFeatures !== 'string' && this.isJson(ordinalFeatures)) { - tempOrdKeys = Object.keys(ordinalFeatures); - } else if(ordinalFeatures !== ""){ // else try to parse and get keys - window.console.log('trying to parse', ordinalFeatures); - let tempObj; - try { - tempObj = JSON.parse(ordinalFeatures); - tempOrdKeys = Object.keys(tempObj) - } catch (e) { - window.console.error(' uh o ----> ', e); - //return false; - } - } - - let tempOrdFeats = {}; - let ordIndex = tempOrdKeys.indexOf(selectedKey); - // keep track of currently selected ordinal feature(s) - ordIndex > -1 ? tempOrdKeys.splice(ordIndex, 1) : tempOrdKeys.push(selectedKey); - tempOrdKeys.forEach(ordKey => { - let tempVals = []; - datasetPreview.data.forEach(row => { - //tempOrdFeats[ordKey] = row[ordKey]; - !tempVals.includes(row[ordKey]) ? tempVals.push(row[ordKey]) : null; - }) - tempOrdFeats[ordKey] = tempVals; - }); - window.console.log('temp ord feats list for dropdown', tempOrdFeats); - this.setState({ - ordKeys: tempOrdKeys, - ordinalFeatures: tempOrdFeats, - ordModal: true - }); - } - - /** - * use to close popup when select - */ - ordModalClose() { - this.setState({ ordModal: false }); - } - /** - * Simple timeout function, resets error message - */ - errorPopupTimeout() { - this.setState({ - errorResp: undefined - }); - } - /* - * Basic helper to test for JSON - * https://stackoverflow.com/questions/9804777/how-to-test-if-a-string-is-json-or-not - */ - isJson(item) { - item = typeof item !== 'string' - ? JSON.stringify(item) - : item; - - try { - item = JSON.parse(item); - } catch (e) { - return false; - } - - if (typeof item === 'object' && item !== null) { - return true; - } - - return false; - } - /****************************************************************************/ - /* Helper methods to create inputs & form elements */ - /****************************************************************************/ - - /** - * create dropdown menu of data column dataKeys, pass in callback for each item - */ - getDropDown(dropDownClickHandler) { - //window.console.log('making dropdown'); - let tempKeys = this.getDataKeys(); - let dropDown = []; - let dropDownObjList = []; - tempKeys.forEach((key, i) =>{ - //window.console.log('making dropdown', i); - dropDownObjList.push({ - key: key + '_' + i, - value: key, - text: key, - onClick: dropDownClickHandler - }) - // dropDown.push(( - // - // )) - } - ); - //window.console.log('dropdown stuff ', dropDownObjList); - //return dropDown; - return dropDownObjList; - } - - /** - * Small helper method to create table for dataset preview upon selecting csv file. - * Copied from Dataset component - relies upon javascript library papaparse to - * partially read selected file and semantic ui to generate preview content, - * if no preview available return hidden paragraph, otherwise return table - * @returns {html} - html to display - */ - getDataTablePreview() { - let dataPrev = this.state.datasetPreview; - let dataPrevTable = (

hi

); - let innerContent; - - if(dataPrev && dataPrev.data) { - innerContent = dataPrev.data.slice(0, 100).map((row, i) => - - {dataPrev.meta.fields.map(field => { - let tempKey = i + field; - return ( - - {row[field]} - - ) - } - )} - - ); - - dataPrevTable = ( -
-
-
- Dataset preview -
-
- - - - {dataPrev.meta.fields.map(field => - {field} - )} - - - - {innerContent} - -
-
-
-
- ) - } - return dataPrevTable; - } - - /** - * Small helper method to create semantic ui accordion for categorical & - * ordinal text inputs - * @returns {html} - html ui input elements - */ - getAccordionInputs() { - const { activeAccordionIndexes, ordinalFeatures, ordKeys, ordOrderList, catFeatures } = this.state; - let catDropdown = this.getDropDown(this.catDropDownClickHandler); - let ordDropdown = this.getDropDown(this.ordDropDownClickHandler); - let ordTextAreaVal; - let ordIconClass; // CSS class to position help icon - - // check input type for string to prevent attempting to stringify a string - this.isJson(ordinalFeatures) && typeof ordinalFeatures !== "string" - ? ordTextAreaVal = JSON.stringify(ordinalFeatures) - : ordTextAreaVal = ordinalFeatures; - - // determine which combos of accordions are open and set respective CSS class - activeAccordionIndexes.includes(1) - ? ordIconClass = "file-upload-ord-with-cat-help-icon" - : ordIconClass = "file-upload-ordinal-help-icon"; - activeAccordionIndexes.includes(0) - ? ordIconClass = "file-upload-just-ordinal-help-icon" : null; - activeAccordionIndexes.includes(1) && activeAccordionIndexes.includes(0) - ? ordIconClass = "file-upload-ord-and-cat-help-icon" : null; - - let ordModalContent = []; - - Object.keys(ordinalFeatures).forEach(selectedOrdKey => { - ordModalContent.push( ( -
- select order for: {selectedOrdKey} -
- select_order_mock: { - -

test drag n' drop list

- { - let tempOrdState = {...ordinalFeatures}; - tempOrdState[selectedOrdKey] = items_test; - window.console.log('new order', items_test); - this.setState({ordinalFeatures: tempOrdState}) - //this.setState({items_test}); - }} - /> -
- } - )} -
- ) - ) - }) - - let accordionContent = ( -
- - - - Enter Categorical Features - - - {this.catFeatHelpText} -
- } - trigger={ - - } - /> - - - -