diff --git a/fret-electron/app/components/ExportRequirementsDialog.js b/fret-electron/app/components/ExportRequirementsDialog.js index 9006ff21..c1c9b5e5 100644 --- a/fret-electron/app/components/ExportRequirementsDialog.js +++ b/fret-electron/app/components/ExportRequirementsDialog.js @@ -62,12 +62,17 @@ const styles = theme => ({ menu: { width: 200, }, + dataTypeMenu: { + width: 250, + }, }); class ExportRequirementsDialog extends React.Component { state = { open: false, - projects:[] + projects:[], + project: 'All Projects', + dataType: 'requirements', }; export_to_md = (R, P) => { @@ -78,13 +83,13 @@ class ExportRequirementsDialog extends React.Component { // ({reqid, parent_reqid, project, rationale, comments, fulltext, semantics, input}))(r.doc) R.forEach((r) => { - s=s + "| " + r.reqid + - " | " + r.parent_reqid + + s=s + "| " + r.reqid + + " | " + r.parent_reqid + " | " + r.fulltext.replace(/\|/g,",").replace(/\n/g," ").replace(/\r/g,"") + " | " + r.rationale.replace(/\|/g,",").replace(/\n/g," ").replace(/\r/g,""); s=s + "\n"; }) - + return s; } // REPLACE/Quote UTF-8 chars by \u BLA @@ -109,15 +114,11 @@ class ExportRequirementsDialog extends React.Component { // context isolation var argList = [project, output_format ] - ipcRenderer.invoke('exportRequirements',argList).then((result) => { - // export requirements doesn't change any Redux state - // console.log('Exported requirements in project ',project) - }).catch((err) => { + const channel = this.state.dataType === 'requirements' ? 'exportRequirements' : this.state.dataType === 'variables' ? 'exportVariables' : 'exportRequirementsAndVariables' + ipcRenderer.invoke(channel, argList).catch((err) => { console.log(err); }) - this.setState({ projectName: '' }); - } componentWillReceiveProps = (props) => { @@ -125,13 +126,18 @@ class ExportRequirementsDialog extends React.Component { open: props.open, projects: props.fretProjects, dialogCloseListener : props.handleDialogClose, - project: '', - output_format: 'json' + project: 'All Projects', + output_format: 'json', + dataType: 'requirements', }) } handleChange = name => event => { - this.setState({ [name]: event.target.value }); + const { value } = event.target + if(name === 'dataType' && value && value.includes('variables')) { + this.setState({ output_format: 'json'}); + } + this.setState({ [name]: value}); }; render() { @@ -142,6 +148,7 @@ class ExportRequirementsDialog extends React.Component { @@ -151,7 +158,7 @@ class ExportRequirementsDialog extends React.Component { Select specific project or export all requirements by selecting All Projects. - + {All Projects} {this.state.projects.map(name => ( - + {name} ))} + + Requirements + + + Variables + + + Requirements & Variables + + + - + JSON - + Markdown (MD) - - diff --git a/fret-electron/app/components/Glossary.js b/fret-electron/app/components/Glossary.js index 405b8dfe..58028474 100644 --- a/fret-electron/app/components/Glossary.js +++ b/fret-electron/app/components/Glossary.js @@ -98,6 +98,7 @@ class Glossary extends React.Component { componentDidMount = () => { this.getProjectRequirements(); + this.getVariables(); if (process.env.EXTERNAL_TOOL == '1') { this.setVariablesToComponents(); } @@ -117,6 +118,7 @@ class Glossary extends React.Component { componentDidUpdate(prevProps, prevState, snapshot) { if (prevProps.projectName !== this.props.projectName) { this.getProjectRequirements(); + this.getVariables(); this.setState({selectedComponent: ''}) } if(prevState.selectedComponent !== this.state.selectedComponent || prevState.checked !== this.state.checked){ @@ -128,9 +130,6 @@ class Glossary extends React.Component { if(this.props.projectRequirements !== prevProps.projectRequirements) { this.setComponentsNames(); } - if(this.state.components !== prevState.components) { - this.getVariables(); - } if(this.props.variables !== prevProps.variables || process.env.EXTERNAL_TOOL == '1' && this.props.editVariables !== prevProps.editVariables) { this.setVariablesToComponents(); } @@ -151,7 +150,8 @@ class Glossary extends React.Component { getVariables = async () => { if (process.env.EXTERNAL_TOOL !== '1') { - ipcRenderer.invoke('selectGlossaryVariables', [this.props.projectName, Object.keys(this.state.components)]).then((result) => { + ipcRenderer.invoke('selectGlossaryVariables', [this.props.projectName]).then((result) => { + console.log('result', result.docs) this.props.setGlossaryVariables({ glossaryVariables: result.docs }) @@ -218,6 +218,9 @@ class Glossary extends React.Component { }) variable['reqs'] = variableRequirements.sort().join(', '); } + if(!componentsWithVariables[v.component_name]) { + componentsWithVariables[v.component_name] = []; + } componentsWithVariables[v.component_name].push(variable); }) this.setState({ componentsWithVariables }) diff --git a/fret-electron/app/components/ImportedVariablesWarningDialog.js b/fret-electron/app/components/ImportedVariablesWarningDialog.js new file mode 100644 index 00000000..e1d4285b --- /dev/null +++ b/fret-electron/app/components/ImportedVariablesWarningDialog.js @@ -0,0 +1,33 @@ +import React from 'react'; +import {Button, Dialog, DialogActions, DialogContent, DialogContentText} from "@material-ui/core"; +import PropTypes from "prop-types"; + +class ImportedVariablesWarningDialog extends React.Component { + + render() { + const {open, handleClose} = this.props; + return ( + + + Certain variables were not imported because of missing information about project and component. + Please see FRET manual for more information. + + + + + + ) + } + +} + +ImportedVariablesWarningDialog.propTypes = { + open: PropTypes.bool.isRequired, + handleClose: PropTypes.func.isRequired, +} + +export default ImportedVariablesWarningDialog; diff --git a/fret-electron/app/components/MainView.js b/fret-electron/app/components/MainView.js index 67a29b75..d9ef875b 100644 --- a/fret-electron/app/components/MainView.js +++ b/fret-electron/app/components/MainView.js @@ -89,6 +89,7 @@ import { importRequirements, mapVariables, } from '../reducers/allActionsSlice'; +import ImportedVariablesWarningDialog from "./ImportedVariablesWarningDialog"; const app = require('electron').remote.app const dialog = require('electron').remote.dialog @@ -224,6 +225,7 @@ class MainView extends React.Component { externalVariables: {}, missingExternalImportDialogOpen: false, missingExternalImportDialogReason: 'unknown', + warningDialogOpen: false, }; constructor(props) { @@ -300,6 +302,7 @@ class MainView extends React.Component { }) } + this.setState({warningDialogOpen: result.areThereIgnoredVariables}) if (result.fileExtension){ this.handleCSVImport(result.csvFields, result.importedReqs) } @@ -388,6 +391,13 @@ class MainView extends React.Component { }); } + handleWarningDialogClose = () => { + this.setState( + { + warningDialogOpen: false, + }); + } + // Handling project management END handleDrawerOpen = () => { @@ -589,7 +599,7 @@ class MainView extends React.Component { render() { const { classes, theme, listOfProjects, requirements } = this.props; - const { anchorEl } = this.state; + const { anchorEl, warningDialogOpen } = this.state; return (
@@ -778,6 +788,7 @@ class MainView extends React.Component { selection='BROWSE' reason={this.state.missingExternalImportDialogReason} /> +
{ return result }) +ipcMain.handle('exportVariables', async (evt, arg) => { + const result = await fretModel.exportVariables(evt, arg); + return result +}) + +ipcMain.handle('exportRequirementsAndVariables', async (evt, arg) => { + const result = await fretModel.exportRequirementsAndVariables(evt, arg); + return result +}) + ipcMain.handle('selectVariable', async(evt, arg) => { const result = await fretModel.selectVariable(evt, arg); return result diff --git a/fret-electron/app/reducers/allActionsSlice.js b/fret-electron/app/reducers/allActionsSlice.js index 05e0013c..d89f45ce 100644 --- a/fret-electron/app/reducers/allActionsSlice.js +++ b/fret-electron/app/reducers/allActionsSlice.js @@ -11,17 +11,9 @@ const allActionsSlice = createSlice({ // * requirements * requirements: [], // requirements from all proljects projectRequirements: [], // requirements for selectedProject + glossaryProjectRequirements: [], - // * analysis * - components: [], // for a specific project: this is an array of all the components - completedComponents: [], // for a specific project: this is an array of all components - // that we have completed the variable mappings - cocospecData: {}, // for a specific project: this is an object where each - // key is a component of this project, and the value of each key is an array of variables - cocospecModes: {}, // for a specific project: this is an object where each - // key is a component of this project, and the value of each key is an array of modes - - // variables + // * variables * variable_data: {}, // for a specific project: this is an object where each // key is a component of this project, and the value is // an array[rowid: counter, variable_name, modeldoc_id, idType, dataType, description] @@ -32,6 +24,15 @@ const allActionsSlice = createSlice({ importedComponents: [], // array of imported simulink model components glossaryVariables: [], // variables for Glossary component + // * analysis * + components: [], // for a specific project: this is an array of all the components + completedComponents: [], // for a specific project: this is an array of all components + // that we have completed the variable mappings + cocospecData: {}, // for a specific project: this is an object where each + // key is a component of this project, and the value of each key is an array of variables + cocospecModes: {}, // for a specific project: this is an object where each + // key is a component of this project, and the value of each key is an array of modes + // realizability rlz_data: [], // array of requirements given a specific project and system component monolithic: undefined, @@ -39,8 +40,8 @@ const allActionsSlice = createSlice({ ccSelected: undefined, projectReport: undefined, diagnosisRequirements: undefined, - prevState: undefined, - glossaryProjectRequirements: [] + //prevState: undefined, + }, @@ -72,7 +73,7 @@ const allActionsSlice = createSlice({ state.compositional = action.payload.compositional; state.ccSelected = action.payload.ccSelected; state.diagnosisRequirements = action.payload.diagnosisRequirements; - state.prevState = action.payload.prevState; + //state.prevState = action.payload.prevState; }, // project slice diff --git a/fret-electron/model/FretModel.js b/fret-electron/model/FretModel.js index 0baf171c..9f96f049 100644 --- a/fret-electron/model/FretModel.js +++ b/fret-electron/model/FretModel.js @@ -177,6 +177,7 @@ export default class FretModel { } + async synchAnalysesAndVariablesWithDB(){ // if a project is selected, update project dependent states @@ -500,10 +501,6 @@ export default class FretModel { } async importRequirements(evt,args){ - // console.log('FretModel importRequirements: ', args) - await leveldbDB.info().then(function (info) { - // console.log('levelDB infor: ',info); - }) var homeDir = app.getPath('home'); var listOfProjects = args; @@ -517,6 +514,7 @@ export default class FretModel { } ], properties: ['openFile']}); + let areThereIgnoredVariables = false; if (filepaths && filepaths.length > 0) { // console.log('FretModel.importRequirements filepaths: ', filepaths) const filepath = filepaths[0]; @@ -543,34 +541,32 @@ export default class FretModel { var content = fs.readFileSync(filepath); // maybe add "utf8" to return a string instead of a buffer var data = JSON.parse(content); - - /* - // Version using readTextFile defined above, works. - readTextFile(filepath, function (text) { - let data = JSON.parse(text); - }) - // */ - // Version using readFile, works. - - /* - fs.readFile(filepath, function (err,buffer) { - if (err) throw err; - //console.log('buffer: ', buffer) - let data = JSON.parse(buffer); - //console.log('FretModel.importRequirements data: ', data) - //from MODEL - requirementsImport.importRequirements(data, listOfProjects); - }); - */ -/* - var content = fs.readFileSync(filepath); // maybe add "utf8" to return a string instead of a buffer - var data = JSON.parse(content); - console.log('FretModel.importRequirements data: ', data) - //from MODEL - await requirementsImport.importRequirements(data, listOfProjects); -*/ - - await requirementsImport.importRequirements(data, listOfProjects) + let requirements = []; + let variables = [] ; + + if(Array.isArray(data) && data.length > 0) { + if(data[0].reqid) { + requirements = data; + } else { + variables = data; + } + }else { + if (data.requirements) { + requirements = data.requirements + } + + if (data.variables) { + variables = data.variables + } + } + + if(requirements.length) { + await requirementsImport.importRequirements(requirements, listOfProjects) + } + if(variables.length) { + const createdVariables = await requirementsImport.importVariables(variables) + areThereIgnoredVariables = variables.length - createdVariables.length > 0 + } await this.synchAnalysesAndVariablesWithDB() await this.mapVariables(this.components) @@ -605,6 +601,7 @@ export default class FretModel { modelVariables : this.modelVariables, selectedVariable : this.selectedVariable, importedComponents : this.importedComponents, + areThereIgnoredVariables, } return jsonFilestates @@ -641,61 +638,120 @@ export default class FretModel { } + exportRequirementsAndVariables = async (_, args) => { + const [project, output_format] = args; + const filepath = this.selectExportFilePath(output_format) + if (filepath) { + const filterOff = project == "All Projects"; + const requirements = [] + await leveldbDB.allDocs({ + include_docs: true, + }).then((result) => { + var filteredReqs = result.rows + .filter(r => !system_DBkeys.includes(r.key)) + .filter(r => filterOff || r.doc.project == project) + filteredReqs.forEach((r) => { + var doc = (({reqid, parent_reqid, project, rationale, comments, fulltext, semantics, input}) => + ({reqid, parent_reqid, project, rationale, comments, fulltext, semantics, input}))(r.doc) + doc._id = uuidv1() + requirements.push(doc) + }) + }).catch((err) => { + console.log(err); + }); + const variables = [] + const selector = filterOff ? {} : {project: project} + await modelDB.find({ + selector + }).then((result) => { + console.log('result', result) + result.docs.forEach(variable => { + delete variable._rev + delete variable.modeldoc + variables.push(variable) + }) + }).catch((err) => { + console.log(err); + }); + this.writeFile({requirements, variables}, output_format, filepath, project) + } + } + + exportVariables =async (_, args) => { + const [project, output_format] = args; + const filepath = this.selectExportFilePath(output_format) + if (filepath) { + const content = [] + const filterOff = project == "All Projects"; + const selector = filterOff ? {} : {project: project} + await modelDB.find({ + selector + }).then((result) => { + console.log('result', result) + result.docs.forEach(variable => { + delete variable._rev + delete variable.modeldoc + content.push(variable) + }) + }).catch((err) => { + console.log(err); + }); + this.writeFile(content, output_format, filepath, project) + } + } + + selectExportFilePath = (output_format) => { + var homeDir = app.getPath('home'); + var filepath = dialog.showSaveDialogSync( + { + defaultPath : homeDir, + title : 'Export Requirements', + buttonLabel : 'Export', + filters: [ + { name: "Documents", extensions: [ output_format ] } + ], + }) + return filepath + } + writeFile(data, output_format, filepath, project) { + var content; + if (output_format === "md"){ + content=export_to_md(data, project) + } + else { + content = JSON.stringify(data, null, 4) + } + + fs.writeFile(filepath, content, (err) => { + if(err) { + return console.log(err); + } + }); + } + async exportRequirements(evt,args){ - // console.log('FretModel exportRequirements: ', args) - var project = args[0]; - var output_format = args[1]; + const [project, output_format] = args; + const filepath = this.selectExportFilePath(output_format) + if(filepath){ + const filteredResult = [] const filterOff = project == "All Projects"; - var homeDir = app.getPath('home'); - var filepath = dialog.showSaveDialogSync( - { - defaultPath : homeDir, - title : 'Export Requirements', - buttonLabel : 'Export', - filters: [ - { name: "Documents", extensions: [ output_format ] } - ], - }) - if (filepath) { - leveldbDB.allDocs({ + await leveldbDB.allDocs({ include_docs: true, }).then((result) => { var filteredReqs = result.rows .filter(r => !system_DBkeys.includes(r.key)) .filter(r => filterOff || r.doc.project == project) - var filteredResult = [] filteredReqs.forEach((r) => { var doc = (({reqid, parent_reqid, project, rationale, comments, fulltext, semantics, input}) => ({reqid, parent_reqid, project, rationale, comments, fulltext, semantics, input}))(r.doc) doc._id = uuidv1() filteredResult.push(doc) }) - // - // produce output - // - var content; - // console.log(output_format) - if (output_format === "md"){ - // console.log("MD") - content=export_to_md(filteredResult, project) - } - else { - // console.log("JSON") - content = JSON.stringify(filteredResult, null, 4) - } - //console.log(content) - - fs.writeFile(filepath, content, (err) => { - if(err) { - return console.log(err); - } - // console.log("The file was saved!"); - }); }).catch((err) => { console.log(err); }); + this.writeFile(filteredResult, output_format, filepath, project) } - return ({}) } @@ -730,11 +786,10 @@ export default class FretModel { } - async selectGlossaryVariables(projectName, componentsNames) { + async selectGlossaryVariables(projectName) { return modelDB.find({ selector: { project: projectName, - component_name: { $in: componentsNames } } });