diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d54827ab..235b41c6 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -20,14 +20,12 @@ jobs:
name: E2E testing (${{ matrix.os }})
runs-on: ${{ matrix.os-version }}
-
strategy:
fail-fast: false
matrix:
os:
- linux
- win64
- # - macos
include:
- os: linux
os-version: ubuntu-latest
@@ -42,6 +40,11 @@ jobs:
activate-environment: watertap-ui-env
miniforge-version: latest
+ - name: Set up debug logging
+ run: |
+ echo "ACTIONS_RUNNER_DEBUG=true" >> $GITHUB_ENV
+ echo "ACTIONS_STEP_DEBUG=true" >> $GITHUB_ENV
+
- name: Add theme to .env file
working-directory: ./electron/ui
run: |
@@ -127,6 +130,13 @@ jobs:
electron/ui/cypress/screenshots/
electron/ui/cypress/videos/
+ ## post-run conda often fails for weird reasons. this is a potential solution
+ ## see https://github.com/conda-incubator/setup-miniconda/issues/277#issuecomment-1431458277
+ - name: Rename conda package cache
+ if: runner.os == 'Windows'
+ shell: bash
+ run: mv "${CONDA_PKGS_DIR}" "${CONDA_PKGS_DIR}_do_not_cache"
+
pytest:
name: pytest (${{ matrix.os }})
runs-on: ${{ matrix.os-version }}
diff --git a/electron/ui/src/App.js b/electron/ui/src/App.js
index 74251b88..fb4d376b 100644
--- a/electron/ui/src/App.js
+++ b/electron/ui/src/App.js
@@ -11,6 +11,8 @@ import {getProjectName} from './services/projectName.service';
import MainContent from "./components/MainContent/MainContent";
import WaitForProject from "./components/WaitForProject/WaitForProject";
import {themes} from './theme';
+import { ThemeProvider, createTheme } from '@mui/material/styles';
+
function App() {
let navigate = useNavigate();
@@ -21,7 +23,14 @@ function App() {
const [checkAgain, setCheckAgain] = useState(1)
const WAIT_TIME = 2
- console.log("App hasTheme = ",hasTheme);
+ // use Material UI theme for styles to be consistent throughout app
+ const mui_theme = createTheme({
+ palette: {
+ primary: {
+ main: theme?.button.background,
+ },
+ },
+ });
useEffect(() => {
if (hasTheme && checkAgain !== 0)
@@ -29,7 +38,7 @@ function App() {
// Get list of flowsheets
getFlowsheetsList()
.then((data) => {
- console.log("got flowsheets list")
+ // console.log("got flowsheets list")
setHasFlowsheetsList(true);
setCheckAgain(0)
}).catch((e) => {
@@ -45,12 +54,15 @@ function App() {
const subProcState = {value: numberOfSubprocesses, setValue: setNumberOfSubprocesses}
return (
-
-
-
-
-
+
+
+
+
+
+
+
+
)
}
diff --git a/electron/ui/src/components/Graph/Graph.js b/electron/ui/src/components/Graph/Graph.js
index 424e63fe..31d04a97 100644
--- a/electron/ui/src/components/Graph/Graph.js
+++ b/electron/ui/src/components/Graph/Graph.js
@@ -19,7 +19,6 @@ export default function Graph() {
setGraphImage(URL.createObjectURL(data))
}
else {
- console.log("data.size is 0")
if (tryAgain) setTryAgain(false)
}
}).catch((err)=>{
diff --git a/electron/ui/src/components/SingleOutput/SingleOutput.js b/electron/ui/src/components/SingleOutput/SingleOutput.js
index e8b233e1..496a06f8 100644
--- a/electron/ui/src/components/SingleOutput/SingleOutput.js
+++ b/electron/ui/src/components/SingleOutput/SingleOutput.js
@@ -1,19 +1,24 @@
-import React, {useState} from "react";
+import React, {useState, useEffect, Fragment} from "react";
import {useParams} from "react-router-dom";
// MUI imports
-import Accordion from "@mui/material/Accordion";
-import AccordionDetails from "@mui/material/AccordionDetails";
-import AccordionSummary from "@mui/material/AccordionSummary";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import DownloadIcon from '@mui/icons-material/Download';
-import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import Grid from "@mui/material/Grid";
import Modal from "@mui/material/Modal";
import SaveIcon from '@mui/icons-material/Save';
import Stack from "@mui/material/Stack";
import TextField from "@mui/material/TextField";
import Toolbar from "@mui/material/Toolbar";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+} from '@mui/material'
+import Paper from '@mui/material/Paper';
export default function SingleOutput(props) {
let params = useParams();
@@ -21,10 +26,37 @@ export default function SingleOutput(props) {
const [configName, setConfigName] = useState(outputData.name)
const [openSaveConfig, setOpenSaveConfig] = React.useState(false);
const [saved, setSaved] = React.useState(false);
+ const [outputTableData, setOutputTableData] = useState({})
const handleOpenSaveConfig = () => setOpenSaveConfig(true);
const handleCloseSaveConfig = () => setOpenSaveConfig(false);
+ /**
+ * organize output data into a list of dictionaries formatted for the output table
+ */
+ useEffect(()=> {
+ let export_variables = {...outputData.outputData.exports}
+ let rows = {}
+ for (let key of Object.keys(export_variables)) {
+ let export_variable = export_variables[key]
+ let category = export_variable.output_category
+ if (!category) category = export_variable.input_category
+ let category_rows
+ if (Object.keys(rows).includes(category)) category_rows = rows[category]
+ else {
+ category_rows = []
+ rows[category] = category_rows
+ }
+ category_rows.push({
+ key: key,
+ name: export_variable.name,
+ value: export_variable.value,
+ units: export_variable.display_units,
+ rounding: export_variable.rounding || 2
+ })
+ }
+ setOutputTableData(rows)
+ }, [outputData])
const modalStyle = {
position: 'absolute',
@@ -87,101 +119,49 @@ export default function SingleOutput(props) {
});
}
- const renderOutputAccordions = () => {
- let var_sections = organizeVariables(outputData.outputData.exports)
- // console.log("var_sections",var_sections)
- return Object.entries(var_sections).map(([key, value]) => {
- let gridSize = 4;
- let _key = key + Math.floor(Math.random() * 100001);
- if (Object.keys(value.output_variables).length > 0) {
- return (
-
- }>
- {value.display_name}
-
-
- :not(style)': {m: 1},
- }}
- autoComplete="off"
+ /**
+ * generate html for table
+ * @returns table body component containing table rows
+ */
+ const renderRows = () => {
+ try {
+ return (
+
+ {Object.entries(outputTableData).map(([category, rows]) => (
+
+
+
- {
- renderFields(value.output_variables)
- }
-
-
-
- )
- }
- else {
- return null;
- }
- })
- };
-
- // renders the data in output accordions
- const renderFields = (fieldData) => {
- // console.log("field data", fieldData)
- return Object.keys(fieldData).map((key) => {
- let _key = key + Math.floor(Math.random() * 100001);
-
- // handle rounding
- let roundedValue
- if (fieldData[key].rounding != null) {
- if (fieldData[key].rounding > 0) {
- roundedValue = parseFloat((fieldData[key].value).toFixed(fieldData[key].rounding))
- } else if (fieldData[key].rounding === 0) {
- roundedValue = Math.round(fieldData[key].value)
- } else // if rounding is negative
- {
- let factor = 1
- let tempRounding = fieldData[key].rounding
- console.log('rounding is negative : ', fieldData[key].rounding)
- while (tempRounding < 0) {
- factor *= 10
- tempRounding += 1
- }
- roundedValue = Math.round((fieldData[key].value / factor)) * factor
- console.log("old value is: ", fieldData[key].value)
- console.log('new value is: ', roundedValue)
- }
- } else // if rounding is not provided, just use given value
- {
- roundedValue = fieldData[key].value
- }
- return (
- {fieldData[key].name + " "}
- {roundedValue}
- {" " + fieldData[key].display_units}
-
)
- })
- };
-
- const organizeVariables = (bvars) => {
- let var_sections = {}
- for (const [key, v] of Object.entries(bvars)) {
- let catg = v.output_category
- let is_input = v.is_input
- let is_output = v.is_output
- if (catg === null) {
- catg = ""
- }
- if (!Object.hasOwn(var_sections, catg)) {
- var_sections[catg] = {
- display_name: catg,
- variables: {},
- input_variables: {},
- output_variables: {}
- }
- }
- var_sections[catg]["variables"][key] = v
- if (is_input) var_sections[catg]["input_variables"][key] = v;
- if (is_output) var_sections[catg]["output_variables"][key] = v
+ {category}
+
+
+ {rows.map((row, idx) => (
+
+
+
+ {row.name}
+
+
+ {row.value.toLocaleString('en-US', {maximumFractionDigits:row.rounding})}
+
+
+ {row.units}
+
+
+ ))}
+
+
+ ))}
+
+
+ )
+ } catch(e) {
+ console.log("unable to render rows: ")
+ console.log(e)
}
- return var_sections
+
}
return (
@@ -228,7 +208,21 @@ export default function SingleOutput(props) {
- {renderOutputAccordions()}
+
+
+
+
+
+ Category
+ Variable
+ Value
+ Units
+
+
+ {renderRows()}
+
+
+
>
);
}
\ No newline at end of file
diff --git a/electron/ui/src/components/SolveDialog/SolveDialog.js b/electron/ui/src/components/SolveDialog/SolveDialog.js
index 7d13d5e3..4f0d545f 100644
--- a/electron/ui/src/components/SolveDialog/SolveDialog.js
+++ b/electron/ui/src/components/SolveDialog/SolveDialog.js
@@ -10,11 +10,9 @@ export default function SolveDialog(props) {
const { open, handleSolved, handleError, flowsheetData, id, isSweep } = props;
useEffect(()=>{
- console.log("solve dialog use effect")
try {
if(open)
{
- console.log("open solve dialog is true")
if(isSweep) {
sweep(id, flowsheetData.inputData)
.then(r => r.json().then(data => ({status: r.status, body: data})))
diff --git a/electron/ui/src/theme.js b/electron/ui/src/theme.js
index ae2eacd7..287312e7 100644
--- a/electron/ui/src/theme.js
+++ b/electron/ui/src/theme.js
@@ -34,7 +34,7 @@ export const themes = {
color: '#FFFFFF', background: '#67C3E4', logoBackground: '#F2F7F8'
},
button: {
- background: '#1976d2'
+ background: '#1976d2',
},
tabs: {
background: '#F1F3F3', color: '#727272'
@@ -62,7 +62,7 @@ export const themes = {
color: '#000000', background: '#F6F4F4', logoBackground: '#F8F6F6'
},
button: {
- background: '#1669B6'
+ background: '#1669B6',
},
tabs: {
background: '#F6F4F4', color: '#727272'
@@ -87,7 +87,7 @@ export const themes = {
color: '#FFFFFF', background: '#000000', logoBackground: '#333333' // FIXME?
},
button: {
- background: '#1669B6'
+ background: '#333333',
},
tabs: {
background: '#F1F3F3', color: '#727272'
diff --git a/electron/ui/src/views/FlowsheetConfig/ConfigInput/ConfigInput.js b/electron/ui/src/views/FlowsheetConfig/ConfigInput/ConfigInput.js
index 74b35a1b..32671306 100644
--- a/electron/ui/src/views/FlowsheetConfig/ConfigInput/ConfigInput.js
+++ b/electron/ui/src/views/FlowsheetConfig/ConfigInput/ConfigInput.js
@@ -179,7 +179,7 @@ export default function ConfigInput(props) {
/**
* Organize variables into sections by their 'category' attribute.
*
- * @returns Object {: [list, of, variable, objects]}
+ * @returns [Object(left) {: [list, of, variable, objects]}, Object(right) {: [list, of, variable, objects]}]
*/
const organizeVariables = (bvars) => {
let var_sections = {}
@@ -234,41 +234,110 @@ export default function ConfigInput(props) {
}
- //sorting the keys of var_sections by amount of variables into object "items"
- let items = Object.keys(var_sections).map(function (key) {
- return [key, var_sections[key]['num_variables']];
- });
- items.sort(function (first, second) {
- return second[1] - first[1];
- });
- // console.log(items)
- return var_sections
+ /**
+ * sort the keys of var_sections into two groups that have as close as possible to even amount of total variables
+ * we want the two columns to be roughly the same length if possible
+ **/
+ let var_sections_left = {}
+ let var_sections_right = {}
+ let total_variables_left = 0
+ let total_variables_right = 0
+ let next_section = "left"
+ try {
+ for (let category of Object.keys(var_sections)) {
+ let section = var_sections[category]
+ let input_data = section.input_variables
+
+ // calculate variable amount - fixed variables take up about 45% as much space as free
+ // if variable is fixed, count it as 1
+ // if variable is free, count it as 2
+ let variable_amount = 0
+ for (let input_variable_key of Object.keys(input_data)) {
+ let input_variable = input_data[input_variable_key]
+ if (input_variable.fixed) {
+ variable_amount += 1
+ }
+ else {
+ variable_amount += 2
+ }
+ }
+
+ if (next_section === "left") {
+ total_variables_left+=variable_amount
+ var_sections_left[category] = section
+ if (total_variables_left > total_variables_right) next_section = "right"
+ }
+ else // if (next_section === "right")
+ {
+ total_variables_right+=variable_amount
+ var_sections_right[category] = section
+ if (total_variables_right > total_variables_left) next_section = "left"
+ }
+ }
+ } catch(e) {
+ console.log("error sorting: ")
+ console.log(e)
+ }
+
+ return [var_sections_left, var_sections_right]
}
const renderInputAccordions = () => {
try {
if (Object.keys(displayData).length > 0) {
let var_sections = organizeVariables(displayData.exports)
- return Object.entries(var_sections).map(([key, value]) => {
- let _key;
- if (key === undefined || key === null) {
- _key = key + Math.floor(Math.random() * 100001);
- } else {
- _key = key + value.display_name + value.output_variables;
- }
- if (Object.keys(value.input_variables).length > 0) {
- return (
-
- )
- }
- })
+ let var_sections_left = var_sections[0]
+ let var_sections_right = var_sections[1]
+
+ return
+
+ {Object.entries(var_sections_left).map(([key, value]) => {
+ let _key;
+ if (key === undefined || key === null) {
+ _key = key + Math.floor(Math.random() * 100001);
+ } else {
+ _key = key + value.display_name + value.output_variables;
+ }
+ if (Object.keys(value.input_variables).length > 0) {
+ return (
+ )
+ }
+ })}
+
+
+
+
+ {Object.entries(var_sections_right).map(([key, value]) => {
+ let _key;
+ if (key === undefined || key === null) {
+ _key = key + Math.floor(Math.random() * 100001);
+ } else {
+ _key = key + value.display_name + value.output_variables;
+ }
+ if (Object.keys(value.input_variables).length > 0) {
+ return (
+ )
+ }
+ })}
+
+
+
}
} catch (e) {
// version of data is likely wrong
@@ -431,13 +500,11 @@ const RunButton = forwardRef(({...props}, ref) => {
checkDisableRun
}));
-
return (
diff --git a/electron/ui/src/views/FlowsheetConfig/FlowsheetConfig.js b/electron/ui/src/views/FlowsheetConfig/FlowsheetConfig.js
index f4481775..2e2a5ec0 100644
--- a/electron/ui/src/views/FlowsheetConfig/FlowsheetConfig.js
+++ b/electron/ui/src/views/FlowsheetConfig/FlowsheetConfig.js
@@ -87,12 +87,10 @@ export default function FlowsheetConfig(props) {
const [isBuilt, setIsBuilt] = useState(false)
const [showBuildOptions, setShowBuildOptions] = useState(false)
const theme = props.theme;
- console.log("flowsheet config theme=", theme);
const [inputsChanged, setInputsChanged] = useState(false);
useEffect(() => {
- console.log("params.id", params.id);
if (!params.hasOwnProperty("id") || !params.id)
return;
// gotta find a way to figure out whether to build or not
@@ -125,10 +123,8 @@ export default function FlowsheetConfig(props) {
}, [params.id]);
useEffect(() => {
- console.info("Check/set whether flowsheet is built");
const inputs = getInputs(flowsheetData);
if (!emptyOrNullObj(inputs)) {
- console.log('flowsheet is indeed built');
setIsBuilt(true);
}
}, [flowsheetData])
@@ -307,8 +303,6 @@ export default function FlowsheetConfig(props) {
}
}
- console.log("Returning container for FlowsheetConfig. build_options=", flowsheetData.inputData.build_options, "isBuilt=", isBuilt,
- "loadingFlowsheetData=", loadingFlowsheetData);
return (
{(loadingFlowsheetData) ?
@@ -380,10 +374,7 @@ export default function FlowsheetConfig(props) {
-