From cfb1454ae0469fa2b251f5f9d2f54b0d54aef6dd Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Fri, 23 Apr 2021 10:27:54 +0200 Subject: [PATCH 01/25] Add editable table cells to data points --- components/data-points.tsx | 196 +++++++++++++++++++++++----- components/editable-table-cell.tsx | 33 +++++ pages/experiment/[experimentid].tsx | 9 +- reducers/reducers.ts | 13 ++ tests/reducers/reducers.test.ts | 31 ++++- types/common.ts | 9 +- 6 files changed, 252 insertions(+), 39 deletions(-) create mode 100644 components/editable-table-cell.tsx diff --git a/components/data-points.tsx b/components/data-points.tsx index 1d6f2114..7e93b4c5 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,35 +1,73 @@ -import { Button, Card, CardContent, Table, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@material-ui/core"; +import { Button, Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@material-ui/core"; import { ChangeEvent, useEffect, useState } from "react"; -import { ExperimentType, DataPointType } from "../types/common"; +import { ExperimentType, DataPointType, VariableType, DataPointTypeValue } from "../types/common"; +import EditIcon from "@material-ui/icons/Edit"; +import CheckCircleIcon from "@material-ui/icons/CheckCircle"; +import CancelIcon from "@material-ui/icons/Cancel"; +import { EditableTableCell } from "./editable-table-cell"; type DataPointProps = { - experiment: ExperimentType, - onAddDataPoints: (dataPoints: DataPointType[]) => void, + experiment: ExperimentType + onUpdateDataPoints: (dataPoints: DataPointType[][]) => void + onAddDataPoints: (dataPoints: DataPointType[]) => void } export default function DataPoints(props: DataPointProps) { const SCORE = "score" - const { experiment: { valueVariables, categoricalVariables, dataPoints } } = props - const variableNames: string[] = valueVariables.map(item => item.name) + const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints, onAddDataPoints } = props + + const variables: VariableType[] = valueVariables.map(item => item as VariableType).concat(categoricalVariables.map(item => item as VariableType)) + + type DataPointEditableRow = { + dataPointEditables: DataPointEditable[] + isEditMode: Boolean + } + + type DataPointEditable = { + dataPoint: DataPointType + variable: VariableType + } + + const dataPointEditableRows: DataPointEditableRow[] = dataPoints.map(item => { + return { + dataPointEditables: item.map((item, index) => { + return { + dataPoint: item, + variable: variables[index], + } + }), + isEditMode: false, + } + } + ) + + /*const variableNames: string[] = valueVariables.map(item => item.name) .concat(categoricalVariables.map(item => item.name)) - .concat(SCORE) - const [newDataPoints, setNewDataPoints] = useState(createInitialNewPoints()) + .concat(SCORE)*/ + + const [rows, setRows] = useState(dataPointEditableRows) + + const [newDataPoints, setNewDataPoints] = useState(createInitialNewPoints()) - function createInitialNewPoints(): DataPointType[] { - return variableNames.map(name => { + function createInitialNewPoints(): DataPointEditable[] { + return dataPointEditableRows.map(name => { return { - name, - value: "" + dataPoint: { + name: "", + value: "" + }, + variable: undefined, + isEditMode: false, } }) } function onAdd() { - props.onAddDataPoints(newDataPoints) + //onAddDataPoints(newDataPoints) } function onNewPointChange(name: string, pointIndex: number, value: string) { - const newPoints = newDataPoints.map((point, index) => { + /*const newPoints = newDataPoints.map((point, index) => { if (index !== pointIndex) { return point } else { @@ -40,7 +78,69 @@ export default function DataPoints(props: DataPointProps) { } } }) - setNewDataPoints(newPoints) + setNewDataPoints(newPoints)*/ + } + + function onToggleEditMode(rowIndex: number) { + setRows( + rows.map((row, index) => { + if (index !== rowIndex) { + return row + } else { + return { + ...row, + isEditMode: !row.isEditMode + } + } + }) + ) + } + + function onEdit(value: string, rowIndex: number, itemIndex: number) { + console.log('edit', rowIndex, itemIndex, value) + setRows( + rows.map((row, i) => { + if (i !== rowIndex) { + return row + } else { + return { + ...row, + dataPointEditables: row.dataPointEditables.map((point, k) => { + if (k !== itemIndex) { + return point + } else { + return { + ...point, + dataPoint: { + ...point.dataPoint, + value + } + } + } + }) + } + } + }) + ) + } + + function onEditConfirm(rowIndex: number) { + console.log('confirm', rows) + onUpdateDataPoints(rows + .map(row => row.dataPointEditables + .map(point => { + return { + name: point.dataPoint.name, + value: (point.dataPoint.name === SCORE ? [point.dataPoint.value] : point.dataPoint.value) as DataPointTypeValue + } + }) + ) + ) + onToggleEditMode(rowIndex) + } + + function onUpdate() { + } return ( @@ -50,44 +150,74 @@ export default function DataPoints(props: DataPointProps) { Data points - - {variableNames.length > 1 && - <> + - {variableNames.map((name, index) => - {name} + {rows[0].dataPointEditables.map((item, index) => + {item.dataPoint.name} )} + - {dataPoints.map((points, pointsIndex) => - - {points.map((point, pointIndex) => { - if (point.name === SCORE) { - return {point.value[0]} - } else { - return {point.value} - } - })} + {rows.map((row, rowIndex) => + + {row.dataPointEditables.map((item, itemIndex) => + <> + {/*{item.dataPoint.name === SCORE ? item.dataPoint.value[0] : item.dataPoint.value} + onDataPointEdit(point, (e.target as HTMLInputElement).value)} + />*/} + + onEdit(value, rowIndex, itemIndex) }/> + + )} + + {row.isEditMode ? + <> + onEditConfirm(rowIndex)}> + + + onToggleEditMode(rowIndex)}> + + + : + onToggleEditMode(rowIndex)}> + + + } + )} - + {/* {variableNames.map((name, index) => onNewPointChange(name, index, (e.target as HTMLInputElement).value)} /> )} - + */}

- - } + ) diff --git a/components/editable-table-cell.tsx b/components/editable-table-cell.tsx new file mode 100644 index 00000000..77869d36 --- /dev/null +++ b/components/editable-table-cell.tsx @@ -0,0 +1,33 @@ +import { TableCell, TextField } from "@material-ui/core" +import { ChangeEvent, useState } from "react" +import { DataPointType } from "../types/common" + +type EditableTableCellProps = { + key: any + dataPoint: DataPointType + isEditMode: Boolean + onChange: (value: string) => void +} + +export function EditableTableCell(props: EditableTableCellProps) { + const { key, dataPoint, isEditMode, onChange } = props + const [value, setValue] = useState(dataPoint.name === "score" ? dataPoint.value[0] : dataPoint.value) + + return ( + <> + {isEditMode ? + + { + const value = (e.target as HTMLInputElement).value + setValue(value) + onChange(value) + }}/> + + : + {value} + } + + ) +} \ No newline at end of file diff --git a/pages/experiment/[experimentid].tsx b/pages/experiment/[experimentid].tsx index 5cc6113b..e8483dee 100644 --- a/pages/experiment/[experimentid].tsx +++ b/pages/experiment/[experimentid].tsx @@ -6,7 +6,7 @@ import { useStyles } from '../../styles/experiment.style'; import OptimizerModel from '../../components/optimizer-model'; import OptimizerConfigurator from '../../components/optimizer-configurator'; import { useEffect, useReducer, useState } from 'react'; -import { VALUE_VARIABLE_ADDED, EXPERIMENT_DESCRIPTION_UPDATED, EXPERIMENT_NAME_UPDATED, EXPERIMENT_UPDATED, rootReducer, VALUE_VARIABLE_DELETED, CATEGORICAL_VARIABLE_ADDED, CATEGORICAL_VARIABLE_DELETED, CONFIGURATION_UPDATED, RESULT_REGISTERED, DATA_POINTS_ADDED } from '../../reducers/reducers'; +import { VALUE_VARIABLE_ADDED, EXPERIMENT_DESCRIPTION_UPDATED, EXPERIMENT_NAME_UPDATED, EXPERIMENT_UPDATED, rootReducer, VALUE_VARIABLE_DELETED, CATEGORICAL_VARIABLE_ADDED, CATEGORICAL_VARIABLE_DELETED, CONFIGURATION_UPDATED, RESULT_REGISTERED, DATA_POINTS_ADDED, DATA_POINTS_UPDATED } from '../../reducers/reducers'; import { ValueVariableType, ExperimentType, CategoricalVariableType, OptimizerConfig, ExperimentResultType, DataPointType } from '../../types/common'; import { initialState } from '../../store'; import { Alert } from '@material-ui/lab'; @@ -109,6 +109,10 @@ export default function Experiment() { dispatch({ type: DATA_POINTS_ADDED, payload: dataPoints}) } + function updateDataPoints(dataPoints: DataPointType[][]) { + dispatch({ type: DATA_POINTS_UPDATED, payload: dataPoints}) + } + if (error) return
Failed to load experiment
; if (!state.experiment.id) return
Loading...
; @@ -165,7 +169,8 @@ export default function Experiment() {
addDataPoints(dataPoints)} /> + onAddDataPoints={(dataPoints: DataPointType[]) => addDataPoints(dataPoints)} + onUpdateDataPoints={(dataPoints: DataPointType[][]) => updateDataPoints(dataPoints)}/>
{state.experiment.results.plots.length > 0 && diff --git a/reducers/reducers.ts b/reducers/reducers.ts index fa05f42f..9e83fac8 100644 --- a/reducers/reducers.ts +++ b/reducers/reducers.ts @@ -11,6 +11,7 @@ export const CATEGORICAL_VARIABLE_DELETED = 'CATEGORICAL_VARIABLE_DELETED' export const CONFIGURATION_UPDATED = 'CONFIGURATION_UPDATED' export const RESULT_REGISTERED = 'RESULT_REGISTERED' export const DATA_POINTS_ADDED = 'DATA_POINTS_ADDED' +export const DATA_POINTS_UPDATED = 'DATA_POINTS_EDITED' export type ResultRegisteredAction = { type: typeof RESULT_REGISTERED @@ -62,6 +63,11 @@ export type DataPointsAddedAction = { payload: DataPointType[] } +export type DataPointsUpdatedAction = { + type: typeof DATA_POINTS_UPDATED + payload: DataPointType[][] +} + export type Action = ExperimentAction type ExperimentAction = CategoricalVariableAddedAction @@ -74,6 +80,7 @@ type ExperimentAction = | ConfigurationUpdatedAction | ResultRegisteredAction | DataPointsAddedAction + | DataPointsUpdatedAction export const rootReducer = (state: State, action: Action) => { switch (action.type) { @@ -87,6 +94,7 @@ export const rootReducer = (state: State, action: Action) => { case CONFIGURATION_UPDATED: case RESULT_REGISTERED: case DATA_POINTS_ADDED: + case DATA_POINTS_UPDATED: return { ...state, experiment: experimentReducer(state.experiment, action) @@ -165,5 +173,10 @@ const experimentReducer = (experimentState: ExperimentType, action: ExperimentAc ...experimentState, dataPoints: pointsAfterAdd } + case DATA_POINTS_UPDATED: + return { + ...experimentState, + dataPoints: action.payload + } } } \ No newline at end of file diff --git a/tests/reducers/reducers.test.ts b/tests/reducers/reducers.test.ts index 65433076..ee815c81 100644 --- a/tests/reducers/reducers.test.ts +++ b/tests/reducers/reducers.test.ts @@ -1,4 +1,4 @@ -import { CategoricalVariableAddedAction, CategoricalVariableDeletedAction, CATEGORICAL_VARIABLE_ADDED, CATEGORICAL_VARIABLE_DELETED, ConfigurationUpdatedAction, CONFIGURATION_UPDATED, DataPointsAddedAction, DATA_POINTS_ADDED, ExperimentDescriptionUpdatedAction, ExperimentNameUpdatedAction, ExperimentUpdatedAction, EXPERIMENT_DESCRIPTION_UPDATED, EXPERIMENT_NAME_UPDATED, EXPERIMENT_UPDATED, ResultRegisteredAction, RESULT_REGISTERED, rootReducer, ValueVariableAddedAction, ValueVariableDeletedAction, VALUE_VARIABLE_ADDED, VALUE_VARIABLE_DELETED } from "../../reducers/reducers"; +import { CategoricalVariableAddedAction, CategoricalVariableDeletedAction, CATEGORICAL_VARIABLE_ADDED, CATEGORICAL_VARIABLE_DELETED, ConfigurationUpdatedAction, CONFIGURATION_UPDATED, DataPointsAddedAction, DataPointsUpdatedAction, DATA_POINTS_ADDED, DATA_POINTS_UPDATED, ExperimentDescriptionUpdatedAction, ExperimentNameUpdatedAction, ExperimentUpdatedAction, EXPERIMENT_DESCRIPTION_UPDATED, EXPERIMENT_NAME_UPDATED, EXPERIMENT_UPDATED, ResultRegisteredAction, RESULT_REGISTERED, rootReducer, ValueVariableAddedAction, ValueVariableDeletedAction, VALUE_VARIABLE_ADDED, VALUE_VARIABLE_DELETED } from "../../reducers/reducers"; import { State } from "../../store"; import { CategoricalVariableType, DataPointType, ExperimentResultType, ExperimentType, OptimizerConfig, ValueVariableType } from "../../types/common"; @@ -275,4 +275,33 @@ describe("experiment reducer", () => { }) }) }) + + describe("DataPointsEditedAction", () => { + + it("should edit data points", async () => { + const payload: DataPointType[][] = [ + [ + { + name: "New point 1", + value: 1 + }, + { + name: "score", + value: [2] + } + ] + ] + + const action: DataPointsUpdatedAction = { + type: DATA_POINTS_UPDATED, + payload: payload + } + + expect(rootReducer(initState, action)).toEqual({ + experiment:{...initState.experiment, + dataPoints: payload + } + }) + }) + }) }) diff --git a/types/common.ts b/types/common.ts index 3852d492..6586beef 100644 --- a/types/common.ts +++ b/types/common.ts @@ -35,6 +35,8 @@ export type CategoricalVariableType = { order?: string } +export type VariableType = ValueVariableType | CategoricalVariableType + export type OptimizerConfig = { baseEstimator: string acqFunc: string @@ -44,18 +46,19 @@ export type OptimizerConfig = { } export type DataPointType = CategorialDataPointType | ValueDataPointType | ScoreDataPointType +export type DataPointTypeValue = string | number | number[] export type CategorialDataPointType = { name: string - value: string + value: DataPointTypeValue } export type ValueDataPointType = { name: string - value: number + value: DataPointTypeValue } export type ScoreDataPointType = { name: string - value: number[] + value: DataPointTypeValue } export type SpaceType = {type: string, name:string, from?: number, to?: number, categories?: string[]}[] \ No newline at end of file From 412d1e72e2eb4569b2378c96856144b0a6152f2a Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Fri, 23 Apr 2021 10:53:26 +0200 Subject: [PATCH 02/25] Refactoring --- components/data-points.tsx | 173 +++++++++++++++-------------- components/editable-table-cell.tsx | 11 +- types/common.ts | 2 + 3 files changed, 94 insertions(+), 92 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index 7e93b4c5..ebdb66c0 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,6 +1,6 @@ import { Button, Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@material-ui/core"; import { ChangeEvent, useEffect, useState } from "react"; -import { ExperimentType, DataPointType, VariableType, DataPointTypeValue } from "../types/common"; +import { ExperimentType, DataPointType, VariableType, DataPointTypeValue, SCORE } from "../types/common"; import EditIcon from "@material-ui/icons/Edit"; import CheckCircleIcon from "@material-ui/icons/CheckCircle"; import CancelIcon from "@material-ui/icons/Cancel"; @@ -12,28 +12,27 @@ type DataPointProps = { onAddDataPoints: (dataPoints: DataPointType[]) => void } +type DataPointEditableRow = { + dataPointEditables: DataPointEditable[] + isEditMode: Boolean +} + +type DataPointEditable = { + dataPoint: DataPointType + variable: VariableType +} + export default function DataPoints(props: DataPointProps) { - const SCORE = "score" const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints, onAddDataPoints } = props - const variables: VariableType[] = valueVariables.map(item => item as VariableType).concat(categoricalVariables.map(item => item as VariableType)) - - type DataPointEditableRow = { - dataPointEditables: DataPointEditable[] - isEditMode: Boolean - } - - type DataPointEditable = { - dataPoint: DataPointType - variable: VariableType - } + const combinedVariables: VariableType[] = valueVariables.map(item => item as VariableType).concat(categoricalVariables.map(item => item as VariableType)) const dataPointEditableRows: DataPointEditableRow[] = dataPoints.map(item => { return { dataPointEditables: item.map((item, index) => { return { dataPoint: item, - variable: variables[index], + variable: combinedVariables[index], } }), isEditMode: false, @@ -41,15 +40,15 @@ export default function DataPoints(props: DataPointProps) { } ) + const [rows, setRows] = useState(dataPointEditableRows) + /*const variableNames: string[] = valueVariables.map(item => item.name) .concat(categoricalVariables.map(item => item.name)) .concat(SCORE)*/ - const [rows, setRows] = useState(dataPointEditableRows) - - const [newDataPoints, setNewDataPoints] = useState(createInitialNewPoints()) + //const [newDataPoints, setNewDataPoints] = useState(createInitialNewPoints()) - function createInitialNewPoints(): DataPointEditable[] { + /*function createInitialNewPoints(): DataPointEditable[] { return dataPointEditableRows.map(name => { return { dataPoint: { @@ -60,7 +59,7 @@ export default function DataPoints(props: DataPointProps) { isEditMode: false, } }) - } + }*/ function onAdd() { //onAddDataPoints(newDataPoints) @@ -97,7 +96,6 @@ export default function DataPoints(props: DataPointProps) { } function onEdit(value: string, rowIndex: number, itemIndex: number) { - console.log('edit', rowIndex, itemIndex, value) setRows( rows.map((row, i) => { if (i !== rowIndex) { @@ -125,7 +123,6 @@ export default function DataPoints(props: DataPointProps) { } function onEditConfirm(rowIndex: number) { - console.log('confirm', rows) onUpdateDataPoints(rows .map(row => row.dataPointEditables .map(point => { @@ -139,6 +136,11 @@ export default function DataPoints(props: DataPointProps) { onToggleEditMode(rowIndex) } + function onEditCancel(rowIndex: number) { + //setRows(dataPointEditableRows) + onToggleEditMode(rowIndex) + } + function onUpdate() { } @@ -151,72 +153,71 @@ export default function DataPoints(props: DataPointProps) { Data points - - - - {rows[0].dataPointEditables.map((item, index) => - {item.dataPoint.name} - )} - - - - - - {rows.map((row, rowIndex) => - - {row.dataPointEditables.map((item, itemIndex) => - <> - {/*{item.dataPoint.name === SCORE ? item.dataPoint.value[0] : item.dataPoint.value} - onDataPointEdit(point, (e.target as HTMLInputElement).value)} - />*/} - - onEdit(value, rowIndex, itemIndex) }/> - - )} - - {row.isEditMode ? - <> - onEditConfirm(rowIndex)}> - - - onToggleEditMode(rowIndex)}> - - - : - onToggleEditMode(rowIndex)}> - - - } - - +
+ + + {rows[0].dataPointEditables.map((item, index) => + {item.dataPoint.name} + )} + + + + + + {rows.map((row, rowIndex) => + + {row.dataPointEditables.map((item, itemIndex) => + <> + {/*{item.dataPoint.name === SCORE ? item.dataPoint.value[0] : item.dataPoint.value} + onDataPointEdit(point, (e.target as HTMLInputElement).value)} + />*/} + + onEdit(value, rowIndex, itemIndex) }/> + )} - {/* - {variableNames.map((name, index) => - - onNewPointChange(name, index, (e.target as HTMLInputElement).value)} /> - - )} - */} - - -
-
- + + {row.isEditMode ? + <> + onEditConfirm(rowIndex)}> + + + onEditCancel(rowIndex)}> + + + : + onToggleEditMode(rowIndex)}> + + + } + + + )} + {/* + {variableNames.map((name, index) => + + onNewPointChange(name, index, (e.target as HTMLInputElement).value)} /> + + )} + */} + + + +
+
diff --git a/components/editable-table-cell.tsx b/components/editable-table-cell.tsx index 77869d36..312642b3 100644 --- a/components/editable-table-cell.tsx +++ b/components/editable-table-cell.tsx @@ -1,22 +1,21 @@ import { TableCell, TextField } from "@material-ui/core" import { ChangeEvent, useState } from "react" -import { DataPointType } from "../types/common" +import { DataPointType, SCORE } from "../types/common" type EditableTableCellProps = { - key: any dataPoint: DataPointType isEditMode: Boolean onChange: (value: string) => void } export function EditableTableCell(props: EditableTableCellProps) { - const { key, dataPoint, isEditMode, onChange } = props - const [value, setValue] = useState(dataPoint.name === "score" ? dataPoint.value[0] : dataPoint.value) + const { dataPoint, isEditMode, onChange } = props + const [value, setValue] = useState(dataPoint.name === SCORE ? dataPoint.value[0] : dataPoint.value) return ( <> {isEditMode ? - + { @@ -26,7 +25,7 @@ export function EditableTableCell(props: EditableTableCellProps) { }}/> : - {value} + {value} } ) diff --git a/types/common.ts b/types/common.ts index 6586beef..fb70ec81 100644 --- a/types/common.ts +++ b/types/common.ts @@ -61,4 +61,6 @@ export type ScoreDataPointType = { value: DataPointTypeValue } +export const SCORE = "score" + export type SpaceType = {type: string, name:string, from?: number, to?: number, categories?: string[]}[] \ No newline at end of file From d4809d00909ec112e93add9f93f56de22d9e9f29 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Fri, 23 Apr 2021 11:14:19 +0200 Subject: [PATCH 03/25] Remove inner state from EditableTableCell --- components/data-points.tsx | 14 +++++++++----- components/editable-table-cell.tsx | 14 ++++---------- types/common.ts | 2 -- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index ebdb66c0..344a7fc9 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,6 +1,6 @@ -import { Button, Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@material-ui/core"; -import { ChangeEvent, useEffect, useState } from "react"; -import { ExperimentType, DataPointType, VariableType, DataPointTypeValue, SCORE } from "../types/common"; +import { Button, Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography } from "@material-ui/core"; +import { useState } from "react"; +import { ExperimentType, DataPointType, VariableType, DataPointTypeValue } from "../types/common"; import EditIcon from "@material-ui/icons/Edit"; import CheckCircleIcon from "@material-ui/icons/CheckCircle"; import CancelIcon from "@material-ui/icons/Cancel"; @@ -22,6 +22,8 @@ type DataPointEditable = { variable: VariableType } +const SCORE = "score" + export default function DataPoints(props: DataPointProps) { const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints, onAddDataPoints } = props @@ -95,7 +97,8 @@ export default function DataPoints(props: DataPointProps) { ) } - function onEdit(value: string, rowIndex: number, itemIndex: number) { + function onEdit(editValue: string, rowIndex: number, itemIndex: number) { + console.log('edit', editValue) setRows( rows.map((row, i) => { if (i !== rowIndex) { @@ -111,7 +114,7 @@ export default function DataPoints(props: DataPointProps) { ...point, dataPoint: { ...point.dataPoint, - value + value: (point.dataPoint.name === SCORE ? [editValue] : editValue) as DataPointTypeValue } } } @@ -175,6 +178,7 @@ export default function DataPoints(props: DataPointProps) { />*/} onEdit(value, rowIndex, itemIndex) }/> diff --git a/components/editable-table-cell.tsx b/components/editable-table-cell.tsx index 312642b3..ce7eb5d4 100644 --- a/components/editable-table-cell.tsx +++ b/components/editable-table-cell.tsx @@ -1,16 +1,14 @@ import { TableCell, TextField } from "@material-ui/core" -import { ChangeEvent, useState } from "react" -import { DataPointType, SCORE } from "../types/common" +import { ChangeEvent } from "react" type EditableTableCellProps = { - dataPoint: DataPointType + value: string isEditMode: Boolean onChange: (value: string) => void } export function EditableTableCell(props: EditableTableCellProps) { - const { dataPoint, isEditMode, onChange } = props - const [value, setValue] = useState(dataPoint.name === SCORE ? dataPoint.value[0] : dataPoint.value) + const { value, isEditMode, onChange } = props return ( <> @@ -18,11 +16,7 @@ export function EditableTableCell(props: EditableTableCellProps) { { - const value = (e.target as HTMLInputElement).value - setValue(value) - onChange(value) - }}/> + onChange={(e: ChangeEvent) => onChange((e.target as HTMLInputElement).value)}/> : {value} diff --git a/types/common.ts b/types/common.ts index fb70ec81..6586beef 100644 --- a/types/common.ts +++ b/types/common.ts @@ -61,6 +61,4 @@ export type ScoreDataPointType = { value: DataPointTypeValue } -export const SCORE = "score" - export type SpaceType = {type: string, name:string, from?: number, to?: number, categories?: string[]}[] \ No newline at end of file From 3c44e3ff7939b6467e3954c147f9850d13db5d12 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Fri, 23 Apr 2021 11:17:08 +0200 Subject: [PATCH 04/25] Remove wrong prop --- components/data-points.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index 344a7fc9..0f351bb1 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -179,7 +179,6 @@ export default function DataPoints(props: DataPointProps) { onEdit(value, rowIndex, itemIndex) }/> From 68de82d9cbf4e0a1cb180008bc3b73ea532cfbf3 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Fri, 23 Apr 2021 11:25:07 +0200 Subject: [PATCH 05/25] Add ability to cancel an edit --- components/data-points.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index 0f351bb1..b4f85fcc 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -98,7 +98,6 @@ export default function DataPoints(props: DataPointProps) { } function onEdit(editValue: string, rowIndex: number, itemIndex: number) { - console.log('edit', editValue) setRows( rows.map((row, i) => { if (i !== rowIndex) { @@ -140,8 +139,18 @@ export default function DataPoints(props: DataPointProps) { } function onEditCancel(rowIndex: number) { - //setRows(dataPointEditableRows) - onToggleEditMode(rowIndex) + setRows(rows + .map((row, i) => { + if (i !== rowIndex) { + return row + } else { + return { + ...dataPointEditableRows[rowIndex] + } + } + } + ) + ) } function onUpdate() { From bc5e68a10c3e727e5fa225a4bd1f70e5dafb531a Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:49:18 +0200 Subject: [PATCH 06/25] Refactor adding new data points --- components/categorical-variable-options.tsx | 2 +- components/data-points.tsx | 229 ++++++++------------ 2 files changed, 94 insertions(+), 137 deletions(-) diff --git a/components/categorical-variable-options.tsx b/components/categorical-variable-options.tsx index af894aa5..2de23aa7 100644 --- a/components/categorical-variable-options.tsx +++ b/components/categorical-variable-options.tsx @@ -31,7 +31,7 @@ export default function CategoricalVariableOptions(props: CategoricalVariableOpt onOptionAdded()}> - + diff --git a/components/data-points.tsx b/components/data-points.tsx index b4f85fcc..6654a0e3 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,6 +1,6 @@ -import { Button, Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography } from "@material-ui/core"; +import { Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography } from "@material-ui/core"; import { useState } from "react"; -import { ExperimentType, DataPointType, VariableType, DataPointTypeValue } from "../types/common"; +import { ExperimentType, VariableType, DataPointTypeValue, DataPointType } from "../types/common"; import EditIcon from "@material-ui/icons/Edit"; import CheckCircleIcon from "@material-ui/icons/CheckCircle"; import CancelIcon from "@material-ui/icons/Cancel"; @@ -12,75 +12,42 @@ type DataPointProps = { onAddDataPoints: (dataPoints: DataPointType[]) => void } -type DataPointEditableRow = { - dataPointEditables: DataPointEditable[] - isEditMode: Boolean -} - -type DataPointEditable = { - dataPoint: DataPointType - variable: VariableType +type DataPointRow = { + dataPoints: DataPointType[] + isEditMode: boolean + isNew: boolean } const SCORE = "score" export default function DataPoints(props: DataPointProps) { const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints, onAddDataPoints } = props - const combinedVariables: VariableType[] = valueVariables.map(item => item as VariableType).concat(categoricalVariables.map(item => item as VariableType)) - - const dataPointEditableRows: DataPointEditableRow[] = dataPoints.map(item => { + + const newRow: DataPointRow = { + dataPoints: combinedVariables.map((variable, i) => { return { - dataPointEditables: item.map((item, index) => { - return { - dataPoint: item, - variable: combinedVariables[index], - } - }), - isEditMode: false, + name: variable.name, + value: "" } - } - ) - - const [rows, setRows] = useState(dataPointEditableRows) - - /*const variableNames: string[] = valueVariables.map(item => item.name) - .concat(categoricalVariables.map(item => item.name)) - .concat(SCORE)*/ - - //const [newDataPoints, setNewDataPoints] = useState(createInitialNewPoints()) - - /*function createInitialNewPoints(): DataPointEditable[] { - return dataPointEditableRows.map(name => { + }).concat({ + name: SCORE, + value: "0", + }), + isEditMode: true, + isNew: true, + } + + const dataPointEditableRows: DataPointRow[] = dataPoints.map(item => { return { - dataPoint: { - name: "", - value: "" - }, - variable: undefined, + dataPoints: item, isEditMode: false, + isNew: false, } - }) - }*/ - - function onAdd() { - //onAddDataPoints(newDataPoints) - } + } + ).concat(newRow) - function onNewPointChange(name: string, pointIndex: number, value: string) { - /*const newPoints = newDataPoints.map((point, index) => { - if (index !== pointIndex) { - return point - } else { - const newValue: any = point.name === SCORE ? [parseFloat(value)] as number[]: value as string - return { - name, - value: newValue - } - } - }) - setNewDataPoints(newPoints)*/ - } + const [rows, setRows] = useState(dataPointEditableRows) function onToggleEditMode(rowIndex: number) { setRows( @@ -105,16 +72,13 @@ export default function DataPoints(props: DataPointProps) { } else { return { ...row, - dataPointEditables: row.dataPointEditables.map((point, k) => { + dataPoints: row.dataPoints.map((point, k) => { if (k !== itemIndex) { return point } else { return { ...point, - dataPoint: { - ...point.dataPoint, - value: (point.dataPoint.name === SCORE ? [editValue] : editValue) as DataPointTypeValue - } + value: (point.name === SCORE ? [editValue] : editValue) as DataPointTypeValue } } }) @@ -124,18 +88,31 @@ export default function DataPoints(props: DataPointProps) { ) } - function onEditConfirm(rowIndex: number) { + function onEditConfirm(row: DataPointRow, rowIndex: number) { onUpdateDataPoints(rows - .map(row => row.dataPointEditables - .map(point => { - return { - name: point.dataPoint.name, - value: (point.dataPoint.name === SCORE ? [point.dataPoint.value] : point.dataPoint.value) as DataPointTypeValue - } - }) - ) + .filter(item => row.isNew || !item.isNew) + .map((item, i) => { + return item.dataPoints + }) ) - onToggleEditMode(rowIndex) + + if (row.isNew) { + let newRows = rows.slice().map((item, i) => { + if (rowIndex !== i) { + return item + } else { + return { + ...item, + isEditMode: false, + isNew: false, + } + } + }) + newRows.splice(rows.length, 0, newRow) + setRows(newRows) + } else { + onToggleEditMode(rowIndex) + } } function onEditCancel(rowIndex: number) { @@ -148,15 +125,10 @@ export default function DataPoints(props: DataPointProps) { ...dataPointEditableRows[rowIndex] } } - } - ) + }) ) } - function onUpdate() { - - } - return ( @@ -165,71 +137,56 @@ export default function DataPoints(props: DataPointProps) { Data points - - - - {rows[0].dataPointEditables.map((item, index) => - {item.dataPoint.name} - )} - - - - - - {rows.map((row, rowIndex) => - - {row.dataPointEditables.map((item, itemIndex) => - <> - {/*{item.dataPoint.name === SCORE ? item.dataPoint.value[0] : item.dataPoint.value} - onDataPointEdit(point, (e.target as HTMLInputElement).value)} - />*/} - + {combinedVariables.length > 0 && +
+ + + {rows[0].dataPoints.map((item, index) => + {item.name} + )} + + + + + + {rows.map((row, rowIndex) => + + {row.dataPoints.map((item, itemIndex) => onEdit(value, rowIndex, itemIndex) }/> - - )} - - {row.isEditMode ? - <> + )} + + {row.isEditMode ? + <> + onEditConfirm(row, rowIndex)}> + + + onEditCancel(rowIndex)}> + + + : onEditConfirm(rowIndex)}> - + aria-label="toggle edit" + onClick={() => onToggleEditMode(rowIndex)}> + - onEditCancel(rowIndex)}> - - - : - onToggleEditMode(rowIndex)}> - - - } - - - )} - {/* - {variableNames.map((name, index) => - - onNewPointChange(name, index, (e.target as HTMLInputElement).value)} /> - + } + + )} - */} - -
-
- + + + }
From a3fd76648a0f54fcf1c05f30fe1cee914e76cbd1 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 12:58:37 +0200 Subject: [PATCH 07/25] Remove unused reducer action --- components/data-points.tsx | 7 +++---- pages/experiment/[experimentid].tsx | 5 ----- reducers/reducers.ts | 7 ------- tests/reducers/reducers.test.ts | 32 ++--------------------------- 4 files changed, 5 insertions(+), 46 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index 6654a0e3..4df442f7 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -9,7 +9,6 @@ import { EditableTableCell } from "./editable-table-cell"; type DataPointProps = { experiment: ExperimentType onUpdateDataPoints: (dataPoints: DataPointType[][]) => void - onAddDataPoints: (dataPoints: DataPointType[]) => void } type DataPointRow = { @@ -21,7 +20,7 @@ type DataPointRow = { const SCORE = "score" export default function DataPoints(props: DataPointProps) { - const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints, onAddDataPoints } = props + const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints } = props const combinedVariables: VariableType[] = valueVariables.map(item => item as VariableType).concat(categoricalVariables.map(item => item as VariableType)) const newRow: DataPointRow = { @@ -47,6 +46,7 @@ export default function DataPoints(props: DataPointProps) { } ).concat(newRow) + //TODO: Reducer? const [rows, setRows] = useState(dataPointEditableRows) function onToggleEditMode(rowIndex: number) { @@ -57,7 +57,7 @@ export default function DataPoints(props: DataPointProps) { } else { return { ...row, - isEditMode: !row.isEditMode + isEditMode: !row.isEditMode } } }) @@ -95,7 +95,6 @@ export default function DataPoints(props: DataPointProps) { return item.dataPoints }) ) - if (row.isNew) { let newRows = rows.slice().map((item, i) => { if (rowIndex !== i) { diff --git a/pages/experiment/[experimentid].tsx b/pages/experiment/[experimentid].tsx index e8483dee..cac2d15b 100644 --- a/pages/experiment/[experimentid].tsx +++ b/pages/experiment/[experimentid].tsx @@ -105,10 +105,6 @@ export default function Experiment() { dispatch({ type: CONFIGURATION_UPDATED, payload: config}) } - function addDataPoints(dataPoints: DataPointType[]) { - dispatch({ type: DATA_POINTS_ADDED, payload: dataPoints}) - } - function updateDataPoints(dataPoints: DataPointType[][]) { dispatch({ type: DATA_POINTS_UPDATED, payload: dataPoints}) } @@ -169,7 +165,6 @@ export default function Experiment() {
addDataPoints(dataPoints)} onUpdateDataPoints={(dataPoints: DataPointType[][]) => updateDataPoints(dataPoints)}/>
{state.experiment.results.plots.length > 0 && diff --git a/reducers/reducers.ts b/reducers/reducers.ts index 9e83fac8..dff32b08 100644 --- a/reducers/reducers.ts +++ b/reducers/reducers.ts @@ -166,13 +166,6 @@ const experimentReducer = (experimentState: ExperimentType, action: ExperimentAc ...experimentState, results: action.payload } - case DATA_POINTS_ADDED: - let pointsAfterAdd = experimentState.dataPoints.slice() - pointsAfterAdd.splice(experimentState.dataPoints.length, 0, action.payload) - return { - ...experimentState, - dataPoints: pointsAfterAdd - } case DATA_POINTS_UPDATED: return { ...experimentState, diff --git a/tests/reducers/reducers.test.ts b/tests/reducers/reducers.test.ts index ee815c81..c5760c75 100644 --- a/tests/reducers/reducers.test.ts +++ b/tests/reducers/reducers.test.ts @@ -225,34 +225,6 @@ describe("experiment reducer", () => { }) }) - it("should add data points", async () => { - const payload: DataPointType[] = [ - { - name: "Milk", - value: "200" - }, - { - name: "Flour", - value: "250" - }, - { - name: "score", - value: [1] - } - ] - - const action: DataPointsAddedAction = { - type: DATA_POINTS_ADDED, - payload - } - - expect(rootReducer(initState, action)).toEqual({ - experiment:{...initState.experiment, - dataPoints: [payload] - } - }) - }) - describe("ResultRegisteredAction", () => { it("should update result", async () => { @@ -276,9 +248,9 @@ describe("experiment reducer", () => { }) }) - describe("DataPointsEditedAction", () => { + describe("DataPointsUpdatedAction", () => { - it("should edit data points", async () => { + it("should update data points", async () => { const payload: DataPointType[][] = [ [ { From c3a3c5b35246eca97fb3440c048e495536162f2c Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 13:13:14 +0200 Subject: [PATCH 08/25] Add missing keys --- components/data-points.tsx | 11 ++++++----- components/editable-table-cell.tsx | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index 4df442f7..d1f6dcb5 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -37,7 +37,7 @@ export default function DataPoints(props: DataPointProps) { isNew: true, } - const dataPointEditableRows: DataPointRow[] = dataPoints.map(item => { + const dataPointRows: DataPointRow[] = dataPoints.map(item => { return { dataPoints: item, isEditMode: false, @@ -46,8 +46,8 @@ export default function DataPoints(props: DataPointProps) { } ).concat(newRow) - //TODO: Reducer? - const [rows, setRows] = useState(dataPointEditableRows) + //TODO: Use reducer? + const [rows, setRows] = useState(dataPointRows) function onToggleEditMode(rowIndex: number) { setRows( @@ -121,7 +121,7 @@ export default function DataPoints(props: DataPointProps) { return row } else { return { - ...dataPointEditableRows[rowIndex] + ...dataPointRows[rowIndex] } } }) @@ -152,11 +152,12 @@ export default function DataPoints(props: DataPointProps) { {row.dataPoints.map((item, itemIndex) => onEdit(value, rowIndex, itemIndex) }/> )} - + {row.isEditMode ? <> {isEditMode ? - onChange((e.target as HTMLInputElement).value)}/> From da0108e1c730217b26e7d0c3715d9e711cba7450 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 13:43:37 +0200 Subject: [PATCH 09/25] Update data point rows on experiment change --- components/data-points.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index d1f6dcb5..ecd476c5 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,5 +1,5 @@ import { Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography } from "@material-ui/core"; -import { useState } from "react"; +import { useLayoutEffect, useState } from "react"; import { ExperimentType, VariableType, DataPointTypeValue, DataPointType } from "../types/common"; import EditIcon from "@material-ui/icons/Edit"; import CheckCircleIcon from "@material-ui/icons/CheckCircle"; @@ -49,6 +49,10 @@ export default function DataPoints(props: DataPointProps) { //TODO: Use reducer? const [rows, setRows] = useState(dataPointRows) + useLayoutEffect(() => { + setRows(dataPointRows) + }, [props.experiment]) + function onToggleEditMode(rowIndex: number) { setRows( rows.map((row, index) => { From 88e8a698454cb062ca3fd0c0c61e8dbec3b8f77a Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 13:45:01 +0200 Subject: [PATCH 10/25] Change useLayoutEffect to useEffect --- components/data-points.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index ecd476c5..b2e69e91 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,5 +1,5 @@ import { Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography } from "@material-ui/core"; -import { useLayoutEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { ExperimentType, VariableType, DataPointTypeValue, DataPointType } from "../types/common"; import EditIcon from "@material-ui/icons/Edit"; import CheckCircleIcon from "@material-ui/icons/CheckCircle"; @@ -49,7 +49,7 @@ export default function DataPoints(props: DataPointProps) { //TODO: Use reducer? const [rows, setRows] = useState(dataPointRows) - useLayoutEffect(() => { + useEffect(() => { setRows(dataPointRows) }, [props.experiment]) From e757965f3b72b8b1d600540b4944ea6c76aa7cd2 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 14:12:55 +0200 Subject: [PATCH 11/25] Create component for editable table --- components/data-points.tsx | 82 ++++++----------------------------- components/editable-table.tsx | 68 +++++++++++++++++++++++++++++ types/common.ts | 17 +++++++- 3 files changed, 98 insertions(+), 69 deletions(-) create mode 100644 components/editable-table.tsx diff --git a/components/data-points.tsx b/components/data-points.tsx index b2e69e91..4c8f23ae 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,29 +1,18 @@ -import { Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography } from "@material-ui/core"; +import { Card, CardContent, Typography } from "@material-ui/core"; import { useEffect, useState } from "react"; -import { ExperimentType, VariableType, DataPointTypeValue, DataPointType } from "../types/common"; -import EditIcon from "@material-ui/icons/Edit"; -import CheckCircleIcon from "@material-ui/icons/CheckCircle"; -import CancelIcon from "@material-ui/icons/Cancel"; -import { EditableTableCell } from "./editable-table-cell"; +import { ExperimentType, VariableType, DataPointTypeValue, DataPointType, TableDataRow, SCORE } from "../types/common"; +import { EditableTable } from "./editable-table"; type DataPointProps = { experiment: ExperimentType onUpdateDataPoints: (dataPoints: DataPointType[][]) => void } -type DataPointRow = { - dataPoints: DataPointType[] - isEditMode: boolean - isNew: boolean -} - -const SCORE = "score" - export default function DataPoints(props: DataPointProps) { const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints } = props const combinedVariables: VariableType[] = valueVariables.map(item => item as VariableType).concat(categoricalVariables.map(item => item as VariableType)) - const newRow: DataPointRow = { + const newRow: TableDataRow = { dataPoints: combinedVariables.map((variable, i) => { return { name: variable.name, @@ -37,7 +26,7 @@ export default function DataPoints(props: DataPointProps) { isNew: true, } - const dataPointRows: DataPointRow[] = dataPoints.map(item => { + const dataPointRows: TableDataRow[] = dataPoints.map(item => { return { dataPoints: item, isEditMode: false, @@ -47,7 +36,7 @@ export default function DataPoints(props: DataPointProps) { ).concat(newRow) //TODO: Use reducer? - const [rows, setRows] = useState(dataPointRows) + const [rows, setRows] = useState(dataPointRows) useEffect(() => { setRows(dataPointRows) @@ -92,11 +81,11 @@ export default function DataPoints(props: DataPointProps) { ) } - function onEditConfirm(row: DataPointRow, rowIndex: number) { + function onEditConfirm(row: TableDataRow, rowIndex: number) { onUpdateDataPoints(rows .filter(item => row.isNew || !item.isNew) .map((item, i) => { - return item.dataPoints + return item.dataPoints.map(item => item as DataPointType) }) ) if (row.isNew) { @@ -141,55 +130,12 @@ export default function DataPoints(props: DataPointProps) { {combinedVariables.length > 0 && - - - - {rows[0].dataPoints.map((item, index) => - {item.name} - )} - - - - - - {rows.map((row, rowIndex) => - - {row.dataPoints.map((item, itemIndex) => - onEdit(value, rowIndex, itemIndex) }/> - )} - - {row.isEditMode ? - <> - onEditConfirm(row, rowIndex)}> - - - onEditCancel(rowIndex)}> - - - : - onToggleEditMode(rowIndex)}> - - - } - - - )} - - -
+ onEdit(editValue, rowIndex, itemIndex)} + onEditConfirm={(row: TableDataRow, rowIndex: number) => onEditConfirm(row, rowIndex)} + onEditCancel={(rowIndex: number) => onEditCancel(rowIndex)} + onToggleEditMode={(rowIndex: number) => onToggleEditMode(rowIndex)} /> } diff --git a/components/editable-table.tsx b/components/editable-table.tsx new file mode 100644 index 00000000..b1d2ee67 --- /dev/null +++ b/components/editable-table.tsx @@ -0,0 +1,68 @@ +import { IconButton, Table, TableBody, TableCell, TableHead, TableRow } from "@material-ui/core" +import { EditableTableCell } from "./editable-table-cell" +import EditIcon from "@material-ui/icons/Edit" +import CheckCircleIcon from "@material-ui/icons/CheckCircle" +import CancelIcon from "@material-ui/icons/Cancel" +import { SCORE, TableDataRow } from "../types/common"; + +type EditableTableProps = { + rows: TableDataRow[] + onEdit: (editValue: string, rowIndex: number, itemIndex: number) => void + onEditConfirm: (row: TableDataRow, rowIndex: number) => void + onEditCancel: (rowIndex: number) => void + onToggleEditMode: (rowIndex: number) => void +} + +export function EditableTable(props: EditableTableProps) { + const { rows, onEdit, onEditConfirm, onEditCancel, onToggleEditMode } = props + + return ( + + + + {rows[0].dataPoints.map((item, index) => + {item.name} + )} + + + + + {rows.map((row, rowIndex) => + + {row.dataPoints.map((item, itemIndex) => + onEdit(value, rowIndex, itemIndex) }/> + )} + + {row.isEditMode ? + <> + onEditConfirm(row, rowIndex)}> + + + onEditCancel(rowIndex)}> + + + : + onToggleEditMode(rowIndex)}> + + + } + + + )} + +
+ ) +} \ No newline at end of file diff --git a/types/common.ts b/types/common.ts index 6586beef..8eae13a6 100644 --- a/types/common.ts +++ b/types/common.ts @@ -61,4 +61,19 @@ export type ScoreDataPointType = { value: DataPointTypeValue } -export type SpaceType = {type: string, name:string, from?: number, to?: number, categories?: string[]}[] \ No newline at end of file +export type SpaceType = {type: string, name:string, from?: number, to?: number, categories?: string[]}[] + +export type TableDataPointValue = string | number | number[] + +export type TableDataPoint = { + name: string + value: TableDataPointValue +} + +export type TableDataRow = { + dataPoints: TableDataPoint[] + isEditMode: boolean + isNew: boolean +} + +export const SCORE = "score" \ No newline at end of file From c273458248739a9a27fa7a1ab707cbeecc2947c5 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 14:16:14 +0200 Subject: [PATCH 12/25] Use correct type for table data points --- components/data-points.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index 4c8f23ae..1d73260a 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,6 +1,6 @@ import { Card, CardContent, Typography } from "@material-ui/core"; import { useEffect, useState } from "react"; -import { ExperimentType, VariableType, DataPointTypeValue, DataPointType, TableDataRow, SCORE } from "../types/common"; +import { ExperimentType, VariableType, DataPointType, TableDataRow, SCORE, TableDataPointValue } from "../types/common"; import { EditableTable } from "./editable-table"; type DataPointProps = { @@ -71,7 +71,7 @@ export default function DataPoints(props: DataPointProps) { } else { return { ...point, - value: (point.name === SCORE ? [editValue] : editValue) as DataPointTypeValue + value: (point.name === SCORE ? [editValue] : editValue) as TableDataPointValue } } }) From bf605e0260956c14cb1b1b31c8bc60d601be3788 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 15:42:21 +0200 Subject: [PATCH 13/25] Refactor variable types, add dropdown options when editing data points --- components/categorical-variable.tsx | 8 ++++---- components/data-points.tsx | 29 ++++++++++++++++++++++------- components/editable-table-cell.tsx | 25 +++++++++++++++++++------ components/editable-table.tsx | 8 +++++--- components/optimizer-model.tsx | 14 +++++++------- components/value-variable.tsx | 8 ++++---- pages/experiment/[experimentid].tsx | 18 +++++++++--------- reducers/reducers.ts | 10 +++++----- tests/reducers/reducers.test.ts | 10 +++++----- types/common.ts | 25 ++++++++----------------- 10 files changed, 88 insertions(+), 67 deletions(-) diff --git a/components/categorical-variable.tsx b/components/categorical-variable.tsx index d438a54c..d5eb4caa 100644 --- a/components/categorical-variable.tsx +++ b/components/categorical-variable.tsx @@ -2,12 +2,12 @@ import { Button, IconButton, TextField, Typography } from '@material-ui/core'; import DeleteIcon from "@material-ui/icons/Delete"; import { useState } from 'react'; import { useForm } from 'react-hook-form'; -import { CategoricalVariableType } from '../types/common'; +import { VariableType } from '../types/common'; import CategoricalVariableOptions from './categorical-variable-options'; import { useStyles } from '../styles/categorical-variable.style'; type CategoricalVariableProps = { - onAdded: (data: CategoricalVariableType) => void + onAdded: (data: VariableType) => void } export default function CategoricalVariable(props: CategoricalVariableProps) { @@ -15,8 +15,8 @@ export default function CategoricalVariable(props: CategoricalVariableProps) { //TODO: Avoid handling options separately? const [options, setOptions] = useState([]) - const { register, handleSubmit, reset, watch, errors } = useForm(); - const onSubmit = async (data: CategoricalVariableType) => { + const { register, handleSubmit, reset, watch, errors } = useForm(); + const onSubmit = async (data: VariableType) => { props.onAdded({...data, options}) setOptions([]) reset() diff --git a/components/data-points.tsx b/components/data-points.tsx index 1d73260a..bbf1ad28 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,6 +1,6 @@ import { Card, CardContent, Typography } from "@material-ui/core"; import { useEffect, useState } from "react"; -import { ExperimentType, VariableType, DataPointType, TableDataRow, SCORE, TableDataPointValue } from "../types/common"; +import { ExperimentType, VariableType, DataPointType, TableDataPoint, TableDataPointValue, TableDataRow } from "../types/common"; import { EditableTable } from "./editable-table"; type DataPointProps = { @@ -8,32 +8,41 @@ type DataPointProps = { onUpdateDataPoints: (dataPoints: DataPointType[][]) => void } +const SCORE = "score" + export default function DataPoints(props: DataPointProps) { const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints } = props - const combinedVariables: VariableType[] = valueVariables.map(item => item as VariableType).concat(categoricalVariables.map(item => item as VariableType)) + const combinedVariables: VariableType[] = valueVariables.concat(categoricalVariables) const newRow: TableDataRow = { dataPoints: combinedVariables.map((variable, i) => { return { name: variable.name, - value: "" + value: variable.options ? variable.options[0] : "", + options: variable.options, } }).concat({ name: SCORE, value: "0", + options: undefined, }), isEditMode: true, isNew: true, } - const dataPointRows: TableDataRow[] = dataPoints.map(item => { + const dataPointRows: TableDataRow[] = dataPoints.map((item, i) => { return { - dataPoints: item, + dataPoints: item.map((point: TableDataPoint, k) => { + return { + ...point, + options: combinedVariables[k] ? combinedVariables[k].options : undefined, + } + }), isEditMode: false, isNew: false, } } - ).concat(newRow) + ).concat(newRow as any) //TODO: Use reducer? const [rows, setRows] = useState(dataPointRows) @@ -85,7 +94,12 @@ export default function DataPoints(props: DataPointProps) { onUpdateDataPoints(rows .filter(item => row.isNew || !item.isNew) .map((item, i) => { - return item.dataPoints.map(item => item as DataPointType) + return item.dataPoints.map(item => { + return { + name: item.name, + value: item.value, + } as DataPointType + }) }) ) if (row.isNew) { @@ -132,6 +146,7 @@ export default function DataPoints(props: DataPointProps) { {combinedVariables.length > 0 && onEdit(editValue, rowIndex, itemIndex)} onEditConfirm={(row: TableDataRow, rowIndex: number) => onEditConfirm(row, rowIndex)} onEditCancel={(rowIndex: number) => onEditCancel(rowIndex)} diff --git a/components/editable-table-cell.tsx b/components/editable-table-cell.tsx index dcf75c6d..91d26605 100644 --- a/components/editable-table-cell.tsx +++ b/components/editable-table-cell.tsx @@ -1,22 +1,35 @@ -import { TableCell, TextField } from "@material-ui/core" +import { FormControl, MenuItem, Select, TableCell, TextField } from "@material-ui/core" import { ChangeEvent } from "react" type EditableTableCellProps = { value: string - isEditMode: Boolean + isEditMode: boolean + options?: string[] onChange: (value: string) => void } export function EditableTableCell(props: EditableTableCellProps) { - const { value, isEditMode, onChange } = props + const { value, isEditMode, options, onChange } = props return ( <> {isEditMode ? - onChange((e.target as HTMLInputElement).value)}/> + {options && options.length > 0 ? + + + + : + onChange((e.target as HTMLInputElement).value)}/> + } : {value} diff --git a/components/editable-table.tsx b/components/editable-table.tsx index b1d2ee67..da7193a9 100644 --- a/components/editable-table.tsx +++ b/components/editable-table.tsx @@ -3,10 +3,11 @@ import { EditableTableCell } from "./editable-table-cell" import EditIcon from "@material-ui/icons/Edit" import CheckCircleIcon from "@material-ui/icons/CheckCircle" import CancelIcon from "@material-ui/icons/Cancel" -import { SCORE, TableDataRow } from "../types/common"; +import { TableDataRow } from "../types/common"; type EditableTableProps = { rows: TableDataRow[] + useArrayForValue: string onEdit: (editValue: string, rowIndex: number, itemIndex: number) => void onEditConfirm: (row: TableDataRow, rowIndex: number) => void onEditCancel: (rowIndex: number) => void @@ -14,7 +15,7 @@ type EditableTableProps = { } export function EditableTable(props: EditableTableProps) { - const { rows, onEdit, onEditConfirm, onEditCancel, onToggleEditMode } = props + const { rows, useArrayForValue, onEdit, onEditConfirm, onEditCancel, onToggleEditMode } = props return ( @@ -32,8 +33,9 @@ export function EditableTable(props: EditableTableProps) { {row.dataPoints.map((item, itemIndex) => onEdit(value, rowIndex, itemIndex) }/> )} diff --git a/components/optimizer-model.tsx b/components/optimizer-model.tsx index 3925f58e..f497a7bc 100644 --- a/components/optimizer-model.tsx +++ b/components/optimizer-model.tsx @@ -1,15 +1,15 @@ import { Button, Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@material-ui/core' -import { CategoricalVariableType, ExperimentType, ValueVariableType } from '../types/common' +import { ExperimentType, VariableType } from '../types/common' import DeleteIcon from '@material-ui/icons/Delete' import { ReactNode, useState } from 'react' import VariableEditor from './variable-editor' type OptimizerModelProps = { experiment: ExperimentType - onDeleteValueVariable: (valueVariable: ValueVariableType) => void - onDeleteCategoricalVariable: (categoricalVariable: CategoricalVariableType) => void - addValueVariable: (valueVariable: ValueVariableType) => void - addCategoricalVariable: (categoricalVariable: CategoricalVariableType) => void + onDeleteValueVariable: (valueVariable: VariableType) => void + onDeleteCategoricalVariable: (categoricalVariable: VariableType) => void + addValueVariable: (valueVariable: VariableType) => void + addCategoricalVariable: (categoricalVariable: VariableType) => void } export default function OptimizerModel(props: OptimizerModelProps) { @@ -103,8 +103,8 @@ export default function OptimizerModel(props: OptimizerModelProps) { } {isAddOpen && props.addCategoricalVariable(categoricalVariable)} - addValueVariable={(valueVariable: ValueVariableType) => props.addValueVariable(valueVariable)} + addCategoricalVariable={(categoricalVariable: VariableType) => props.addCategoricalVariable(categoricalVariable)} + addValueVariable={(valueVariable: VariableType) => props.addValueVariable(valueVariable)} close={() => setAddOpen(false)} /> } diff --git a/components/value-variable.tsx b/components/value-variable.tsx index 675aa16e..19380ba2 100644 --- a/components/value-variable.tsx +++ b/components/value-variable.tsx @@ -1,15 +1,15 @@ import { Button, TextField } from '@material-ui/core' import { useForm } from 'react-hook-form'; -import { ValueVariableType } from '../types/common'; +import { VariableType } from '../types/common'; type ValueVariableProps = { - onAdded: (data: ValueVariableType) => void + onAdded: (data: VariableType) => void } export default function ValueVariable(props: ValueVariableProps) { - const { register, handleSubmit, reset, watch, errors } = useForm(); - const onSubmit = async (data: ValueVariableType) => { + const { register, handleSubmit, reset, watch, errors } = useForm(); + const onSubmit = async (data: VariableType) => { props.onAdded(data) reset() } diff --git a/pages/experiment/[experimentid].tsx b/pages/experiment/[experimentid].tsx index cac2d15b..fad5c4ba 100644 --- a/pages/experiment/[experimentid].tsx +++ b/pages/experiment/[experimentid].tsx @@ -7,7 +7,7 @@ import OptimizerModel from '../../components/optimizer-model'; import OptimizerConfigurator from '../../components/optimizer-configurator'; import { useEffect, useReducer, useState } from 'react'; import { VALUE_VARIABLE_ADDED, EXPERIMENT_DESCRIPTION_UPDATED, EXPERIMENT_NAME_UPDATED, EXPERIMENT_UPDATED, rootReducer, VALUE_VARIABLE_DELETED, CATEGORICAL_VARIABLE_ADDED, CATEGORICAL_VARIABLE_DELETED, CONFIGURATION_UPDATED, RESULT_REGISTERED, DATA_POINTS_ADDED, DATA_POINTS_UPDATED } from '../../reducers/reducers'; -import { ValueVariableType, ExperimentType, CategoricalVariableType, OptimizerConfig, ExperimentResultType, DataPointType } from '../../types/common'; +import { VariableType, ExperimentType, OptimizerConfig, ExperimentResultType, DataPointType } from '../../types/common'; import { initialState } from '../../store'; import { Alert } from '@material-ui/lab'; import ModelEditor from '../../components/model-editor'; @@ -73,19 +73,19 @@ export default function Experiment() { dispatch({ type: RESULT_REGISTERED, payload: result }) } - function addValueVariable(valueVariable: ValueVariableType) { + function addValueVariable(valueVariable: VariableType) { dispatch({ type: VALUE_VARIABLE_ADDED, payload: valueVariable }) } - function deleteValueVariable(valueVariable: ValueVariableType) { + function deleteValueVariable(valueVariable: VariableType) { dispatch({ type: VALUE_VARIABLE_DELETED, payload: valueVariable }) } - function addCategoricalVariable(categoricalVariable: CategoricalVariableType) { + function addCategoricalVariable(categoricalVariable: VariableType) { dispatch({ type: CATEGORICAL_VARIABLE_ADDED, payload: categoricalVariable}) } - function deleteCategoricalVariable(categoricalVariable: CategoricalVariableType) { + function deleteCategoricalVariable(categoricalVariable: VariableType) { dispatch({ type: CATEGORICAL_VARIABLE_DELETED, payload: categoricalVariable }) } @@ -158,10 +158,10 @@ export default function Experiment() { {deleteValueVariable(valueVariable)}} - onDeleteCategoricalVariable={(categoricalVariable: CategoricalVariableType) => {deleteCategoricalVariable(categoricalVariable)}} - addValueVariable={(valueVariable: ValueVariableType) => addValueVariable(valueVariable)} - addCategoricalVariable={(categoricalVariable: CategoricalVariableType) => addCategoricalVariable(categoricalVariable)}/> + onDeleteValueVariable={(valueVariable: VariableType) => {deleteValueVariable(valueVariable)}} + onDeleteCategoricalVariable={(categoricalVariable: VariableType) => {deleteCategoricalVariable(categoricalVariable)}} + addValueVariable={(valueVariable: VariableType) => addValueVariable(valueVariable)} + addCategoricalVariable={(categoricalVariable: VariableType) => addCategoricalVariable(categoricalVariable)}/>
{ const initState: State = { @@ -116,7 +116,7 @@ describe("experiment reducer", () => { }) it("should add value variable", async () => { - const payload: ValueVariableType = { + const payload: VariableType = { name: "Flour", description: "Wet", minVal: 300, @@ -142,7 +142,7 @@ describe("experiment reducer", () => { }) it("should delete value variable", async () => { - const payload: ValueVariableType = { + const payload: VariableType = { name: "Water", description: "Wet", minVal: 100, @@ -162,7 +162,7 @@ describe("experiment reducer", () => { }) it("should add categorial variable", async () => { - const payload: CategoricalVariableType = { + const payload: VariableType = { name: "Fat", description: "Fatty", options: [], @@ -186,7 +186,7 @@ describe("experiment reducer", () => { }) it("should delete categorical variable", async () => { - const payload: CategoricalVariableType = { + const payload: VariableType = { name: "Icing", description: "Sugary", options: [], diff --git a/types/common.ts b/types/common.ts index 8eae13a6..6e6df968 100644 --- a/types/common.ts +++ b/types/common.ts @@ -1,8 +1,8 @@ export type ExperimentType = { id: string info: Info - categoricalVariables: CategoricalVariableType[] - valueVariables: ValueVariableType[] + categoricalVariables: VariableType[] + valueVariables: VariableType[] optimizerConfig: OptimizerConfig results: ExperimentResultType dataPoints: DataPointType[][] @@ -20,23 +20,15 @@ export type Info = { description: string } -export type ValueVariableType = { +export type VariableType = { name: string description: string - minVal: number - maxVal: number - order?: number -} - -export type CategoricalVariableType = { - name: string - description: string - options: string[] + minVal?: number + maxVal?: number + options?: string[] order?: string } -export type VariableType = ValueVariableType | CategoricalVariableType - export type OptimizerConfig = { baseEstimator: string acqFunc: string @@ -68,12 +60,11 @@ export type TableDataPointValue = string | number | number[] export type TableDataPoint = { name: string value: TableDataPointValue + options?: string[] | undefined } export type TableDataRow = { dataPoints: TableDataPoint[] isEditMode: boolean isNew: boolean -} - -export const SCORE = "score" \ No newline at end of file +} \ No newline at end of file From 109a9eb63cc2653480e674092ef16d62da07c45d Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:13:51 +0200 Subject: [PATCH 14/25] Add delete function to data points, add missing types --- components/data-points.tsx | 18 ++++++++++++++---- components/editable-table.tsx | 24 +++++++++++++++++------- reducers/reducers.ts | 8 ++++---- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index bbf1ad28..cf309003 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -90,9 +90,8 @@ export default function DataPoints(props: DataPointProps) { ) } - function onEditConfirm(row: TableDataRow, rowIndex: number) { - onUpdateDataPoints(rows - .filter(item => row.isNew || !item.isNew) + function updateDataPoints(dataRows: TableDataRow[]) { + onUpdateDataPoints(dataRows .map((item, i) => { return item.dataPoints.map(item => { return { @@ -102,6 +101,10 @@ export default function DataPoints(props: DataPointProps) { }) }) ) + } + + function onEditConfirm(row: TableDataRow, rowIndex: number) { + updateDataPoints(rows.filter(item => row.isNew || !item.isNew)) if (row.isNew) { let newRows = rows.slice().map((item, i) => { if (rowIndex !== i) { @@ -135,6 +138,12 @@ export default function DataPoints(props: DataPointProps) { ) } + function onDelete(rowIndex: number) { + let rowsAfterDelete: TableDataRow[] = rows.slice() + rowsAfterDelete.splice(rowIndex, 1) + updateDataPoints(rowsAfterDelete.filter(row => !row.isNew)) + } + return ( @@ -150,7 +159,8 @@ export default function DataPoints(props: DataPointProps) { onEdit={(editValue: string, rowIndex: number, itemIndex: number) => onEdit(editValue, rowIndex, itemIndex)} onEditConfirm={(row: TableDataRow, rowIndex: number) => onEditConfirm(row, rowIndex)} onEditCancel={(rowIndex: number) => onEditCancel(rowIndex)} - onToggleEditMode={(rowIndex: number) => onToggleEditMode(rowIndex)} /> + onToggleEditMode={(rowIndex: number) => onToggleEditMode(rowIndex)} + onDelete={(rowIndex: number) => onDelete(rowIndex)} /> } diff --git a/components/editable-table.tsx b/components/editable-table.tsx index da7193a9..1e8d3aca 100644 --- a/components/editable-table.tsx +++ b/components/editable-table.tsx @@ -3,6 +3,7 @@ import { EditableTableCell } from "./editable-table-cell" import EditIcon from "@material-ui/icons/Edit" import CheckCircleIcon from "@material-ui/icons/CheckCircle" import CancelIcon from "@material-ui/icons/Cancel" +import DeleteIcon from "@material-ui/icons/Delete"; import { TableDataRow } from "../types/common"; type EditableTableProps = { @@ -12,10 +13,11 @@ type EditableTableProps = { onEditConfirm: (row: TableDataRow, rowIndex: number) => void onEditCancel: (rowIndex: number) => void onToggleEditMode: (rowIndex: number) => void + onDelete: (rowIndex: number) => void } export function EditableTable(props: EditableTableProps) { - const { rows, useArrayForValue, onEdit, onEditConfirm, onEditCancel, onToggleEditMode } = props + const { rows, useArrayForValue, onEdit, onEditConfirm, onEditCancel, onToggleEditMode, onDelete } = props return (
@@ -54,12 +56,20 @@ export function EditableTable(props: EditableTableProps) { : - onToggleEditMode(rowIndex)}> - - + <> + onToggleEditMode(rowIndex)}> + + + onDelete(rowIndex)}> + + + } diff --git a/reducers/reducers.ts b/reducers/reducers.ts index 040aac8f..df018a69 100644 --- a/reducers/reducers.ts +++ b/reducers/reducers.ts @@ -127,14 +127,14 @@ const experimentReducer = (experimentState: ExperimentType, action: ExperimentAc } } case VALUE_VARIABLE_ADDED: - let varsAfterAdd = experimentState.valueVariables.slice() + let varsAfterAdd: VariableType[] = experimentState.valueVariables.slice() varsAfterAdd.splice(experimentState.valueVariables.length, 0, action.payload) return { ...experimentState, valueVariables: varsAfterAdd } case VALUE_VARIABLE_DELETED: - let varsAfterDelete = experimentState.valueVariables.slice() + let varsAfterDelete: VariableType[] = experimentState.valueVariables.slice() let indexOfDelete = experimentState.valueVariables.indexOf(action.payload) varsAfterDelete.splice(indexOfDelete, 1) return { @@ -142,14 +142,14 @@ const experimentReducer = (experimentState: ExperimentType, action: ExperimentAc valueVariables: varsAfterDelete } case CATEGORICAL_VARIABLE_ADDED: - let catVarsAfterAdd = experimentState.categoricalVariables.slice() + let catVarsAfterAdd: VariableType[] = experimentState.categoricalVariables.slice() catVarsAfterAdd.splice(experimentState.categoricalVariables.length, 0, action.payload) return { ...experimentState, categoricalVariables: catVarsAfterAdd } case CATEGORICAL_VARIABLE_DELETED: - let catVarsAfterDelete = experimentState.categoricalVariables.slice() + let catVarsAfterDelete: VariableType[] = experimentState.categoricalVariables.slice() let indexOfCatDelete = experimentState.categoricalVariables.indexOf(action.payload) catVarsAfterDelete.splice(indexOfCatDelete, 1) return { From 18ad44524ad3ef8e1be266ee93c2ea77f8363b3b Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:35:05 +0200 Subject: [PATCH 15/25] Add key to select items, styling --- components/editable-table-cell.tsx | 2 +- components/editable-table.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/editable-table-cell.tsx b/components/editable-table-cell.tsx index 91d26605..be260e2a 100644 --- a/components/editable-table-cell.tsx +++ b/components/editable-table-cell.tsx @@ -22,7 +22,7 @@ export function EditableTableCell(props: EditableTableCellProps) { onChange={(e: ChangeEvent) => onChange(e.target.value as string)} displayEmpty inputProps={{ 'aria-label': 'select value' }}> - {options.map(item => {item})} + {options.map((item, i) => {item})} : diff --git a/components/editable-table.tsx b/components/editable-table.tsx index 1e8d3aca..d208c58a 100644 --- a/components/editable-table.tsx +++ b/components/editable-table.tsx @@ -47,13 +47,13 @@ export function EditableTable(props: EditableTableProps) { size="small" aria-label="confirm edit" onClick={() => onEditConfirm(row, rowIndex)}> - + onEditCancel(rowIndex)}> - + : <> From 5990bb888c61ff03fd5bccfd5873ab1222bd533f Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:37:51 +0200 Subject: [PATCH 16/25] Update variable types --- components/variable-editor.tsx | 10 +++++----- utility/converters.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/variable-editor.tsx b/components/variable-editor.tsx index 4c37853d..a5fc1e9a 100644 --- a/components/variable-editor.tsx +++ b/components/variable-editor.tsx @@ -3,12 +3,12 @@ import ValueVariable from './value-variable'; import { Card, CardContent, Grid, IconButton, Radio, Typography } from "@material-ui/core" import CloseIcon from "@material-ui/icons/Close"; import { useState } from "react" -import { CategoricalVariableType, ValueVariableType } from '../types/common'; +import { VariableType } from '../types/common'; import useStyles from '../styles/variable-editor.style'; type VariableEditorProps = { - addValueVariable: (valueVariable: ValueVariableType) => void - addCategoricalVariable: (categoricalVariable: CategoricalVariableType) => void + addValueVariable: (valueVariable: VariableType) => void + addCategoricalVariable: (categoricalVariable: VariableType) => void close: () => void } @@ -53,10 +53,10 @@ export default function VariableEditor(props: VariableEditorProps) {

{radioIndex === 0 && - props.addValueVariable(valueVariable)} /> + props.addValueVariable(valueVariable)} /> } {radioIndex === 1 && - props.addCategoricalVariable(categoricalVariable)} /> + props.addCategoricalVariable(categoricalVariable)} /> } diff --git a/utility/converters.ts b/utility/converters.ts index 0c649855..88813d9b 100644 --- a/utility/converters.ts +++ b/utility/converters.ts @@ -1,5 +1,5 @@ import { ExperimentData } from "../openapi" -import { CategoricalVariableType, DataPointType, ExperimentType, ScoreDataPointType, SpaceType, ValueVariableType } from "../types/common" +import { VariableType, DataPointType, ExperimentType, ScoreDataPointType, SpaceType } from "../types/common" export const calculateSpace = (experiment: ExperimentType): SpaceType => { const numeric: SpaceType = experiment.valueVariables.map(v => { return {type: "numeric", name: v.name, from: Number(v.minVal), to: Number(v.maxVal)}}) @@ -7,6 +7,6 @@ export const calculateSpace = (experiment: ExperimentType): SpaceType => { return numeric.concat(categorial) } const numPat = / [0-9] + / -export const calculateData = (categorialValues: CategoricalVariableType[], numericValues: ValueVariableType[], dataPoints: DataPointType[][]): ExperimentData[] => { +export const calculateData = (categorialValues: VariableType[], numericValues: VariableType[], dataPoints: DataPointType[][]): ExperimentData[] => { return dataPoints.map((run):ExperimentData => ({xi: run.filter(it => it.name !== "score").map(it => numericValues.find(p => p.name === it.name) ? Number(it.value) : it.value), yi: Number((run.filter(it => it.name === "score")[0] as ScoreDataPointType).value[0])})) } \ No newline at end of file From 8c6fe7895b243c21337ac1bf9326b5740a845761 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:59:47 +0200 Subject: [PATCH 17/25] Move data point updates to a reducer --- components/data-points.tsx | 121 +++---- components/editable-table-cell.tsx | 2 +- components/editable-table.tsx | 6 +- reducers/data-points-reducer.ts | 119 +++++++ reducers/reducers.ts | 2 +- tests/reducers/data-points-reducer.test.ts | 360 +++++++++++++++++++++ tests/reducers/reducers.test.ts | 8 +- 7 files changed, 526 insertions(+), 92 deletions(-) create mode 100644 reducers/data-points-reducer.ts create mode 100644 tests/reducers/data-points-reducer.test.ts diff --git a/components/data-points.tsx b/components/data-points.tsx index cf309003..143b424a 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,6 +1,7 @@ import { Card, CardContent, Typography } from "@material-ui/core"; -import { useEffect, useState } from "react"; -import { ExperimentType, VariableType, DataPointType, TableDataPoint, TableDataPointValue, TableDataRow } from "../types/common"; +import { useEffect, useReducer, useState } from "react"; +import { dataPointsReducer, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, DATA_POINTS_TABLE_UPDATED } from "../reducers/data-points-reducer"; +import { ExperimentType, VariableType, DataPointType, TableDataPoint, TableDataRow } from "../types/common"; import { EditableTable } from "./editable-table"; type DataPointProps = { @@ -14,7 +15,7 @@ export default function DataPoints(props: DataPointProps) { const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints } = props const combinedVariables: VariableType[] = valueVariables.concat(categoricalVariables) - const newRow: TableDataRow = { + const emptyRow: TableDataRow = { dataPoints: combinedVariables.map((variable, i) => { return { name: variable.name, @@ -42,52 +43,38 @@ export default function DataPoints(props: DataPointProps) { isNew: false, } } - ).concat(newRow as any) + ).concat(emptyRow as any) - //TODO: Use reducer? - const [rows, setRows] = useState(dataPointRows) + const [rows, dispatch] = useReducer(dataPointsReducer, dataPointRows) + const [initialRows, setInitialRows] = useState(dataPointRows) useEffect(() => { - setRows(dataPointRows) - }, [props.experiment]) + updateDataPoints(rows.filter(item => !item.isNew) as TableDataRow[]) + }, [rows]) - function onToggleEditMode(rowIndex: number) { - setRows( - rows.map((row, index) => { - if (index !== rowIndex) { - return row - } else { - return { - ...row, - isEditMode: !row.isEditMode - } - } - }) - ) + function toggleEditMode(rowIndex: number) { + dispatch({ type: DATA_POINTS_TABLE_EDIT_TOGGLED, payload: rowIndex }) } - function onEdit(editValue: string, rowIndex: number, itemIndex: number) { - setRows( - rows.map((row, i) => { - if (i !== rowIndex) { - return row - } else { - return { - ...row, - dataPoints: row.dataPoints.map((point, k) => { - if (k !== itemIndex) { - return point - } else { - return { - ...point, - value: (point.name === SCORE ? [editValue] : editValue) as TableDataPointValue - } - } - }) - } - } - }) - ) + function cancelEdit(initialRows: TableDataRow[], rowIndex: number) { + dispatch({ type: DATA_POINTS_TABLE_EDIT_CANCELLED, payload: { initialRows, rowIndex } }) + } + + function edit(editValue: string, rowIndex: number, itemIndex: number) { + dispatch({ type: DATA_POINTS_TABLE_EDITED, payload: { + itemIndex, + rowIndex, + useArrayForValue: SCORE, + value: editValue + }}) + } + + function deleteRow(rowIndex: number) { + dispatch({ type: DATA_POINTS_TABLE_ROW_DELETED, payload: rowIndex }) + } + + function addRow(emptyRow: TableDataRow) { + dispatch({ type: DATA_POINTS_TABLE_ROW_ADDED, payload: emptyRow }) } function updateDataPoints(dataRows: TableDataRow[]) { @@ -104,46 +91,13 @@ export default function DataPoints(props: DataPointProps) { } function onEditConfirm(row: TableDataRow, rowIndex: number) { - updateDataPoints(rows.filter(item => row.isNew || !item.isNew)) if (row.isNew) { - let newRows = rows.slice().map((item, i) => { - if (rowIndex !== i) { - return item - } else { - return { - ...item, - isEditMode: false, - isNew: false, - } - } - }) - newRows.splice(rows.length, 0, newRow) - setRows(newRows) + addRow(emptyRow) } else { - onToggleEditMode(rowIndex) + toggleEditMode(rowIndex) } } - function onEditCancel(rowIndex: number) { - setRows(rows - .map((row, i) => { - if (i !== rowIndex) { - return row - } else { - return { - ...dataPointRows[rowIndex] - } - } - }) - ) - } - - function onDelete(rowIndex: number) { - let rowsAfterDelete: TableDataRow[] = rows.slice() - rowsAfterDelete.splice(rowIndex, 1) - updateDataPoints(rowsAfterDelete.filter(row => !row.isNew)) - } - return ( @@ -154,15 +108,14 @@ export default function DataPoints(props: DataPointProps) { {combinedVariables.length > 0 && onEdit(editValue, rowIndex, itemIndex)} + onEdit={(editValue: string, rowIndex: number, itemIndex: number) => edit(editValue, rowIndex, itemIndex)} onEditConfirm={(row: TableDataRow, rowIndex: number) => onEditConfirm(row, rowIndex)} - onEditCancel={(rowIndex: number) => onEditCancel(rowIndex)} - onToggleEditMode={(rowIndex: number) => onToggleEditMode(rowIndex)} - onDelete={(rowIndex: number) => onDelete(rowIndex)} /> + onEditCancel={(rowIndex: number) => cancelEdit(initialRows, rowIndex)} + onToggleEditMode={(rowIndex: number) => toggleEditMode(rowIndex)} + onDelete={(rowIndex: number) => deleteRow(rowIndex)} /> } - ) diff --git a/components/editable-table-cell.tsx b/components/editable-table-cell.tsx index be260e2a..3f0a8481 100644 --- a/components/editable-table-cell.tsx +++ b/components/editable-table-cell.tsx @@ -28,7 +28,7 @@ export function EditableTableCell(props: EditableTableCellProps) { : onChange((e.target as HTMLInputElement).value)}/> + onChange={(e: ChangeEvent) => onChange("" + (e.target as HTMLInputElement).value)}/> } : diff --git a/components/editable-table.tsx b/components/editable-table.tsx index d208c58a..19e81ced 100644 --- a/components/editable-table.tsx +++ b/components/editable-table.tsx @@ -3,6 +3,7 @@ import { EditableTableCell } from "./editable-table-cell" import EditIcon from "@material-ui/icons/Edit" import CheckCircleIcon from "@material-ui/icons/CheckCircle" import CancelIcon from "@material-ui/icons/Cancel" +import AddIcon from "@material-ui/icons/Add" import DeleteIcon from "@material-ui/icons/Delete"; import { TableDataRow } from "../types/common"; @@ -47,13 +48,14 @@ export function EditableTable(props: EditableTableProps) { size="small" aria-label="confirm edit" onClick={() => onEditConfirm(row, rowIndex)}> - + {row.isNew ? : + } onEditCancel(rowIndex)}> - + : <> diff --git a/reducers/data-points-reducer.ts b/reducers/data-points-reducer.ts new file mode 100644 index 00000000..d669798a --- /dev/null +++ b/reducers/data-points-reducer.ts @@ -0,0 +1,119 @@ +import { TableDataRow } from "../types/common" + +export type DataPointsTableAction = + DataPointsTableEditToggledAction + | DataPointsTableEditCancelledAction + | DataPointsTableEditedAction + | DataPointsTableUpdatedAction + | DataPointsTableRowDeletedAction + | DataPointsTableRowAddedAction + +export const DATA_POINTS_TABLE_EDIT_TOGGLED = 'DATA_POINTS_TABLE_EDIT_TOGGLED' +export const DATA_POINTS_TABLE_EDIT_CANCELLED = 'DATA_POINTS_TABLE_EDIT_CANCELLED' +export const DATA_POINTS_TABLE_EDITED = 'DATA_POINTS_TABLE_EDITED' +export const DATA_POINTS_TABLE_UPDATED = 'DATA_POINTS_TABLE_UPDATED' +export const DATA_POINTS_TABLE_ROW_DELETED = 'DATA_POINTS_TABLE_ROW_DELETED' +export const DATA_POINTS_TABLE_ROW_ADDED = 'DATA_POINTS_TABLE_ROW_ADDED' + +export type DataPointsTableEditToggledAction = { + type: typeof DATA_POINTS_TABLE_EDIT_TOGGLED + payload: number +} + +export type DataPointsTableEditCancelledAction = { + type: typeof DATA_POINTS_TABLE_EDIT_CANCELLED + payload: { + initialRows: TableDataRow[] + rowIndex: number + } +} + +export type DataPointsTableEditedAction = { + type: typeof DATA_POINTS_TABLE_EDITED + payload: { + value: string + rowIndex: number + itemIndex: number + useArrayForValue: string + } +} + +export type DataPointsTableUpdatedAction = { + type: typeof DATA_POINTS_TABLE_UPDATED + payload: TableDataRow[] +} + +export type DataPointsTableRowDeletedAction = { + type: typeof DATA_POINTS_TABLE_ROW_DELETED + payload: number +} + +export type DataPointsTableRowAddedAction = { + type: typeof DATA_POINTS_TABLE_ROW_ADDED + payload: TableDataRow +} + +export const dataPointsReducer = (dataRows: TableDataRow[], action: DataPointsTableAction) => { + switch (action.type) { + case DATA_POINTS_TABLE_EDIT_TOGGLED: + return dataRows.map((row, index) => { + if (index !== action.payload) { + return row + } else { + return { + ...row, + isEditMode: !row.isEditMode + } + } + }) + case DATA_POINTS_TABLE_EDIT_CANCELLED: + const rowIndexEditCancelled = action.payload.rowIndex + return dataRows.map((row, i) => { + if (i !== rowIndexEditCancelled) { + return row + } else { + return action.payload.initialRows[rowIndexEditCancelled] + } + }) + case DATA_POINTS_TABLE_EDITED: + return dataRows.map((row, i) => { + if (i !== action.payload.rowIndex) { + return row + } else { + return { + ...row, + dataPoints: row.dataPoints.map((point, k) => { + if (k !== action.payload.itemIndex) { + return point + } else { + return { + ...point, + value: (point.name === action.payload.useArrayForValue ? [action.payload.value] : action.payload.value) + } + } + }) + } + } + }) + case DATA_POINTS_TABLE_UPDATED: + return action.payload + case DATA_POINTS_TABLE_ROW_DELETED: + let rowsAfterDelete: TableDataRow[] = dataRows.slice() + rowsAfterDelete.splice(action.payload, 1) + return rowsAfterDelete + case DATA_POINTS_TABLE_ROW_ADDED: + const rowsAfterAdded: TableDataRow[] = dataRows.slice().map((item, i) => { + if (dataRows.length - 1 !== i) { + return item + } else { + return { + ...item, + isEditMode: false, + isNew: false, + } + } + }) + rowsAfterAdded.splice(dataRows.length, 0, action.payload) + return rowsAfterAdded + } +} diff --git a/reducers/reducers.ts b/reducers/reducers.ts index df018a69..47887f1e 100644 --- a/reducers/reducers.ts +++ b/reducers/reducers.ts @@ -1,5 +1,5 @@ import { State } from "../store" -import { ExperimentType, OptimizerConfig, ExperimentResultType, DataPointType, VariableType } from "../types/common" +import { ExperimentType, OptimizerConfig, ExperimentResultType, DataPointType, VariableType, TableDataRow } from "../types/common" export const EXPERIMENT_UPDATED = 'EXPERIMENT_SAVED' export const EXPERIMENT_NAME_UPDATED = 'EXPERIMENT_NAME_UPDATED' diff --git a/tests/reducers/data-points-reducer.test.ts b/tests/reducers/data-points-reducer.test.ts new file mode 100644 index 00000000..f6ef3273 --- /dev/null +++ b/tests/reducers/data-points-reducer.test.ts @@ -0,0 +1,360 @@ +import { dataPointsReducer, DataPointsTableEditCancelledAction, DataPointsTableEditedAction, DataPointsTableEditToggledAction, DataPointsTableRowAddedAction, DataPointsTableRowDeletedAction, DataPointsTableUpdatedAction, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, DATA_POINTS_TABLE_UPDATED } from "../../reducers/data-points-reducer" +import { TableDataRow } from "../../types/common" + +//TODO: Simplify by reusing state + +describe("data points reducer", () => { + describe("DataPointsTableEditToggledAction", () => { + it("should toggle edit mode", async () => { + const payload = 1 + + const action: DataPointsTableEditToggledAction = { + type: DATA_POINTS_TABLE_EDIT_TOGGLED, + payload + } + + const initState: TableDataRow[] = [ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [], + isEditMode: false, + isNew: false, + } + ] + + expect(dataPointsReducer(initState, action)).toEqual([ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [], + isEditMode: true, + isNew: false, + } + ]) + }) + }) + + describe("DataPointsTableEditCancelledAction", () => { + it("should cancel edit", async () => { + const payload = { + initialRows: [ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "50" + } + ], + isEditMode: false, + isNew: false, + } + ], + rowIndex: 1 + } + + const action: DataPointsTableEditCancelledAction = { + type: DATA_POINTS_TABLE_EDIT_CANCELLED, + payload + } + + const initState: TableDataRow[] = [ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + } + ], + isEditMode: true, + isNew: false, + } + ] + + expect(dataPointsReducer(initState, action)).toEqual(payload.initialRows) + }) + }) + + describe("DataPointsTableEditedAction", () => { + it("should edit table cell - non-array value", async () => { + const payload = { + value: "300", + rowIndex: 1, + itemIndex: 1, + useArrayForValue: "score" + } + + const action: DataPointsTableEditedAction = { + type: DATA_POINTS_TABLE_EDITED, + payload + } + + const initState: TableDataRow[] = [ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "Milk", + value: "200" + }, + { + name: "score", + value: [0.5] + } + ], + isEditMode: true, + isNew: false, + } + ] + + expect(dataPointsReducer(initState, action)).toEqual([ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "Milk", + value: "300" + }, + { + name: "score", + value: [0.5] + } + ], + isEditMode: true, + isNew: false, + } + ]) + }) + + it("should edit table cell - array value", async () => { + const payload = { + value: "0.2", + rowIndex: 1, + itemIndex: 1, + useArrayForValue: "score" + } + + const action: DataPointsTableEditedAction = { + type: DATA_POINTS_TABLE_EDITED, + payload + } + + const initState: TableDataRow[] = [ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: true, + isNew: false, + } + ] + + expect(dataPointsReducer(initState, action)).toEqual([ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: ["0.2"] + } + ], + isEditMode: true, + isNew: false, + } + ]) + }) + }) + + describe("DataPointsTableUpdatedAction", () => { + it("should update table", async () => { + const payload = [ + { + dataPoints: [ + { + name: "Milk", + value: "200" + }, + { + name: "score", + value: [0.3] + } + ], + isEditMode: false, + isNew: false, + } + ] + + const action: DataPointsTableUpdatedAction = { + type: DATA_POINTS_TABLE_UPDATED, + payload + } + + const initState: TableDataRow[] = [ + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: false, + isNew: false, + } + ] + + expect(dataPointsReducer(initState, action)).toEqual(payload) + }) + }) + + describe("DataPointsTableDeletedAction", () => { + it("should delete row", async () => { + const payload = 1 + + const action: DataPointsTableRowDeletedAction = { + type: DATA_POINTS_TABLE_ROW_DELETED, + payload + } + + const initState: TableDataRow[] = [ + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "200" + }, + { + name: "score", + value: [0.2] + } + ], + isEditMode: false, + isNew: false, + } + ] + + expect(dataPointsReducer(initState, action)).toEqual(initState.slice(0, 1)) + }) + }) + + describe("DataPointsTableAddedAction", () => { + it("should add row", async () => { + const payload = { + dataPoints: [ + { + name: "Milk", + value: "" + }, + { + name: "score", + value: "0" + } + ], + isEditMode: true, + isNew: true, + } + + const action: DataPointsTableRowAddedAction = { + type: DATA_POINTS_TABLE_ROW_ADDED, + payload + } + + const initState: TableDataRow[] = [ + { + dataPoints: [ + { + name: "Milk", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: true, + isNew: true, + }, + ] + + expect(dataPointsReducer(initState, action)).toEqual( + [ + { + ...initState[0], + isEditMode: false, + isNew: false + }, + payload + ], + ) + }) + }) +}) \ No newline at end of file diff --git a/tests/reducers/reducers.test.ts b/tests/reducers/reducers.test.ts index df9aebbb..629dfbd6 100644 --- a/tests/reducers/reducers.test.ts +++ b/tests/reducers/reducers.test.ts @@ -1,6 +1,6 @@ -import { CategoricalVariableAddedAction, CategoricalVariableDeletedAction, CATEGORICAL_VARIABLE_ADDED, CATEGORICAL_VARIABLE_DELETED, ConfigurationUpdatedAction, CONFIGURATION_UPDATED, DataPointsAddedAction, DataPointsUpdatedAction, DATA_POINTS_ADDED, DATA_POINTS_UPDATED, ExperimentDescriptionUpdatedAction, ExperimentNameUpdatedAction, ExperimentUpdatedAction, EXPERIMENT_DESCRIPTION_UPDATED, EXPERIMENT_NAME_UPDATED, EXPERIMENT_UPDATED, ResultRegisteredAction, RESULT_REGISTERED, rootReducer, ValueVariableAddedAction, ValueVariableDeletedAction, VALUE_VARIABLE_ADDED, VALUE_VARIABLE_DELETED } from "../../reducers/reducers"; +import { CategoricalVariableAddedAction, CategoricalVariableDeletedAction, CATEGORICAL_VARIABLE_ADDED, CATEGORICAL_VARIABLE_DELETED, ConfigurationUpdatedAction, CONFIGURATION_UPDATED, DataPointsAddedAction, DataPointsTableEditToggledAction, DataPointsUpdatedAction, DATA_POINTS_ADDED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_UPDATED, ExperimentDescriptionUpdatedAction, ExperimentNameUpdatedAction, ExperimentUpdatedAction, EXPERIMENT_DESCRIPTION_UPDATED, EXPERIMENT_NAME_UPDATED, EXPERIMENT_UPDATED, ResultRegisteredAction, RESULT_REGISTERED, rootReducer, ValueVariableAddedAction, ValueVariableDeletedAction, VALUE_VARIABLE_ADDED, VALUE_VARIABLE_DELETED } from "../../reducers/reducers"; import { State } from "../../store"; -import { DataPointType, ExperimentResultType, ExperimentType, OptimizerConfig, VariableType } from "../../types/common"; +import { DataPointType, ExperimentResultType, ExperimentType, OptimizerConfig, TableDataPointValue, TableDataRow, VariableType } from "../../types/common"; describe("experiment reducer", () => { const initState: State = { @@ -249,7 +249,6 @@ describe("experiment reducer", () => { }) describe("DataPointsUpdatedAction", () => { - it("should update data points", async () => { const payload: DataPointType[][] = [ [ @@ -266,7 +265,7 @@ describe("experiment reducer", () => { const action: DataPointsUpdatedAction = { type: DATA_POINTS_UPDATED, - payload: payload + payload } expect(rootReducer(initState, action)).toEqual({ @@ -277,3 +276,4 @@ describe("experiment reducer", () => { }) }) }) + From e0ef12c1f64877d71edf9e13dd5b127bd037ac7f Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Mon, 26 Apr 2021 21:04:33 +0200 Subject: [PATCH 18/25] Added todo --- components/data-points.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/data-points.tsx b/components/data-points.tsx index 143b424a..dcba32f6 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -46,6 +46,7 @@ export default function DataPoints(props: DataPointProps) { ).concat(emptyRow as any) const [rows, dispatch] = useReducer(dataPointsReducer, dataPointRows) + //TODO: Using this as undo only works once (not after editing the same row multiple times) const [initialRows, setInitialRows] = useState(dataPointRows) useEffect(() => { From d520b00a529fe5b8b0c05392f451d0aedf45b5d3 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Tue, 27 Apr 2021 13:09:15 +0200 Subject: [PATCH 19/25] Add previous rows to data points state --- components/data-points.tsx | 36 +- reducers/data-points-reducer.ts | 108 +++-- tests/reducers/data-points-reducer.test.ts | 454 +++++++++++---------- types/common.ts | 1 + 4 files changed, 341 insertions(+), 258 deletions(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index dcba32f6..7cf78d12 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,6 +1,6 @@ import { Card, CardContent, Typography } from "@material-ui/core"; -import { useEffect, useReducer, useState } from "react"; -import { dataPointsReducer, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, DATA_POINTS_TABLE_UPDATED } from "../reducers/data-points-reducer"; +import { useEffect, useReducer } from "react"; +import { dataPointsReducer, DataPointsState, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, testReducer } from "../reducers/data-points-reducer"; import { ExperimentType, VariableType, DataPointType, TableDataPoint, TableDataRow } from "../types/common"; import { EditableTable } from "./editable-table"; @@ -45,20 +45,34 @@ export default function DataPoints(props: DataPointProps) { } ).concat(emptyRow as any) - const [rows, dispatch] = useReducer(dataPointsReducer, dataPointRows) - //TODO: Using this as undo only works once (not after editing the same row multiple times) - const [initialRows, setInitialRows] = useState(dataPointRows) + const initialState: DataPointsState = { + rows: dataPointRows, + prevRows: [] + } + + const [state, dispatch] = useReducer(dataPointsReducer, initialState) useEffect(() => { - updateDataPoints(rows.filter(item => !item.isNew) as TableDataRow[]) - }, [rows]) + updateDataPoints(state.rows.filter(item => !item.isNew) as TableDataRow[]) + }, [state.rows]) function toggleEditMode(rowIndex: number) { dispatch({ type: DATA_POINTS_TABLE_EDIT_TOGGLED, payload: rowIndex }) + + /*let prevRowsEdited: TableDataRow[] = prevRows.slice() + prevRowsEdited.map((item, i) => { + if (i !== rowIndex) { + return item + } else { + rows[rowIndex] + } + }) + setPrevRows(prevRowsEdited) + console.log('prevRows', prevRows)*/ } - function cancelEdit(initialRows: TableDataRow[], rowIndex: number) { - dispatch({ type: DATA_POINTS_TABLE_EDIT_CANCELLED, payload: { initialRows, rowIndex } }) + function cancelEdit(prevRows: TableDataRow[], rowIndex: number) { + dispatch({ type: DATA_POINTS_TABLE_EDIT_CANCELLED, payload: { prevRows, rowIndex } }) } function edit(editValue: string, rowIndex: number, itemIndex: number) { @@ -109,11 +123,11 @@ export default function DataPoints(props: DataPointProps) { {combinedVariables.length > 0 && edit(editValue, rowIndex, itemIndex)} onEditConfirm={(row: TableDataRow, rowIndex: number) => onEditConfirm(row, rowIndex)} - onEditCancel={(rowIndex: number) => cancelEdit(initialRows, rowIndex)} + onEditCancel={(rowIndex: number) => cancelEdit(state.prevRows, rowIndex)} onToggleEditMode={(rowIndex: number) => toggleEditMode(rowIndex)} onDelete={(rowIndex: number) => deleteRow(rowIndex)} /> } diff --git a/reducers/data-points-reducer.ts b/reducers/data-points-reducer.ts index d669798a..d47513a0 100644 --- a/reducers/data-points-reducer.ts +++ b/reducers/data-points-reducer.ts @@ -1,5 +1,10 @@ import { TableDataRow } from "../types/common" +export type DataPointsState = { + rows: TableDataRow[] + prevRows: TableDataRow[] +} + export type DataPointsTableAction = DataPointsTableEditToggledAction | DataPointsTableEditCancelledAction @@ -23,7 +28,7 @@ export type DataPointsTableEditToggledAction = { export type DataPointsTableEditCancelledAction = { type: typeof DATA_POINTS_TABLE_EDIT_CANCELLED payload: { - initialRows: TableDataRow[] + prevRows: TableDataRow[] rowIndex: number } } @@ -53,57 +58,73 @@ export type DataPointsTableRowAddedAction = { payload: TableDataRow } -export const dataPointsReducer = (dataRows: TableDataRow[], action: DataPointsTableAction) => { +export const dataPointsReducer = (state: DataPointsState, action: DataPointsTableAction) => { switch (action.type) { case DATA_POINTS_TABLE_EDIT_TOGGLED: - return dataRows.map((row, index) => { - if (index !== action.payload) { - return row - } else { - return { - ...row, - isEditMode: !row.isEditMode + //TODO: save current row in state.prevRows + return { + ...state, + rows: state.rows.map((row, index) => { + if (index !== action.payload) { + return row + } else { + return { + ...row, + isEditMode: !row.isEditMode + } } - } - }) + }) + } case DATA_POINTS_TABLE_EDIT_CANCELLED: const rowIndexEditCancelled = action.payload.rowIndex - return dataRows.map((row, i) => { - if (i !== rowIndexEditCancelled) { - return row - } else { - return action.payload.initialRows[rowIndexEditCancelled] - } - }) + return { + ...state, + rows: state.rows.map((row, i) => { + if (i !== rowIndexEditCancelled) { + return row + } else { + return action.payload.prevRows[rowIndexEditCancelled] + } + }) + } case DATA_POINTS_TABLE_EDITED: - return dataRows.map((row, i) => { - if (i !== action.payload.rowIndex) { - return row - } else { - return { - ...row, - dataPoints: row.dataPoints.map((point, k) => { - if (k !== action.payload.itemIndex) { - return point - } else { - return { - ...point, - value: (point.name === action.payload.useArrayForValue ? [action.payload.value] : action.payload.value) + return { + ...state, + rows: state.rows.map((row, i) => { + if (i !== action.payload.rowIndex) { + return row + } else { + return { + ...row, + dataPoints: row.dataPoints.map((point, k) => { + if (k !== action.payload.itemIndex) { + return point + } else { + return { + ...point, + value: (point.name === action.payload.useArrayForValue ? [action.payload.value] : action.payload.value) + } } - } - }) + }) + } } - } - }) + }) + } case DATA_POINTS_TABLE_UPDATED: - return action.payload + return { + ...state, + rows: action.payload + } case DATA_POINTS_TABLE_ROW_DELETED: - let rowsAfterDelete: TableDataRow[] = dataRows.slice() + let rowsAfterDelete: TableDataRow[] = state.rows.slice() rowsAfterDelete.splice(action.payload, 1) - return rowsAfterDelete + return { + ...state, + rows: rowsAfterDelete + } case DATA_POINTS_TABLE_ROW_ADDED: - const rowsAfterAdded: TableDataRow[] = dataRows.slice().map((item, i) => { - if (dataRows.length - 1 !== i) { + const rowsAfterAdded: TableDataRow[] = state.rows.slice().map((item, i) => { + if (state.rows.length - 1 !== i) { return item } else { return { @@ -113,7 +134,10 @@ export const dataPointsReducer = (dataRows: TableDataRow[], action: DataPointsTa } } }) - rowsAfterAdded.splice(dataRows.length, 0, action.payload) - return rowsAfterAdded + rowsAfterAdded.splice(state.rows.length, 0, action.payload) + return { + ...state, + rows: rowsAfterAdded + } } } diff --git a/tests/reducers/data-points-reducer.test.ts b/tests/reducers/data-points-reducer.test.ts index f6ef3273..a568741d 100644 --- a/tests/reducers/data-points-reducer.test.ts +++ b/tests/reducers/data-points-reducer.test.ts @@ -1,4 +1,4 @@ -import { dataPointsReducer, DataPointsTableEditCancelledAction, DataPointsTableEditedAction, DataPointsTableEditToggledAction, DataPointsTableRowAddedAction, DataPointsTableRowDeletedAction, DataPointsTableUpdatedAction, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, DATA_POINTS_TABLE_UPDATED } from "../../reducers/data-points-reducer" +import { dataPointsReducer, DataPointsState, DataPointsTableEditCancelledAction, DataPointsTableEditedAction, DataPointsTableEditToggledAction, DataPointsTableRowAddedAction, DataPointsTableRowDeletedAction, DataPointsTableUpdatedAction, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, DATA_POINTS_TABLE_UPDATED } from "../../reducers/data-points-reducer" import { TableDataRow } from "../../types/common" //TODO: Simplify by reusing state @@ -13,38 +13,45 @@ describe("data points reducer", () => { payload } - const initState: TableDataRow[] = [ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [], - isEditMode: false, - isNew: false, - } - ] + const initState: DataPointsState = { + prevRows: [], + rows: [ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [], + isEditMode: false, + isNew: false, + } + ] + } - expect(dataPointsReducer(initState, action)).toEqual([ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, + expect(dataPointsReducer(initState, action)).toEqual( { - dataPoints: [], - isEditMode: true, - isNew: false, - } - ]) + prevRows: [], + rows:[ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [], + isEditMode: true, + isNew: false, + } + ]} + ) }) }) describe("DataPointsTableEditCancelledAction", () => { - it("should cancel edit", async () => { + /*it("should cancel edit", async () => { const payload = { - initialRows: [ + prevRows: [ { dataPoints: [], isEditMode: false, @@ -69,26 +76,30 @@ describe("data points reducer", () => { payload } - const initState: TableDataRow[] = [ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [ - { - name: "Water", - value: "100" - } - ], - isEditMode: true, - isNew: false, - } - ] + const initState: DataPointsState = { + prevRows: [], + rows: + [ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + } + ], + isEditMode: true, + isNew: false, + } + ] + } - expect(dataPointsReducer(initState, action)).toEqual(payload.initialRows) - }) + expect(dataPointsReducer(initState, action)).toEqual(payload.prevRows) + })*/ }) describe("DataPointsTableEditedAction", () => { @@ -105,57 +116,64 @@ describe("data points reducer", () => { payload } - const initState: TableDataRow[] = [ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [ - { - name: "Water", - value: "100" - }, - { - name: "Milk", - value: "200" - }, - { - name: "score", - value: [0.5] - } - ], - isEditMode: true, - isNew: false, - } - ] + const initState: DataPointsState = { + prevRows: [], + rows:[ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "Milk", + value: "200" + }, + { + name: "score", + value: [0.5] + } + ], + isEditMode: true, + isNew: false, + } + ] + } - expect(dataPointsReducer(initState, action)).toEqual([ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, + expect(dataPointsReducer(initState, action)).toEqual( { - dataPoints: [ - { - name: "Water", - value: "100" - }, + prevRows: [], + rows:[ { - name: "Milk", - value: "300" + dataPoints: [], + isEditMode: false, + isNew: false, }, { - name: "score", - value: [0.5] + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "Milk", + value: "300" + }, + { + name: "score", + value: [0.5] + } + ], + isEditMode: true, + isNew: false, } - ], - isEditMode: true, - isNew: false, - } - ]) + ] + }) }) it("should edit table cell - array value", async () => { @@ -171,49 +189,55 @@ describe("data points reducer", () => { payload } - const initState: TableDataRow[] = [ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [ - { - name: "Water", - value: "100" - }, - { - name: "score", - value: [0.1] - } - ], - isEditMode: true, - isNew: false, - } - ] + const initState: DataPointsState = { + prevRows: [], + rows:[ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: true, + isNew: false, + } + ] + } - expect(dataPointsReducer(initState, action)).toEqual([ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [ - { - name: "Water", - value: "100" - }, - { - name: "score", - value: ["0.2"] - } - ], - isEditMode: true, - isNew: false, - } - ]) + expect(dataPointsReducer(initState, action)).toEqual({ + prevRows: [], + rows:[ + { + dataPoints: [], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: ["0.2"] + } + ], + isEditMode: true, + isNew: false, + } + ]} + ) }) }) @@ -241,24 +265,30 @@ describe("data points reducer", () => { payload } - const initState: TableDataRow[] = [ - { - dataPoints: [ - { - name: "Water", - value: "100" - }, - { - name: "score", - value: [0.1] - } - ], - isEditMode: false, - isNew: false, - } - ] + const initState: DataPointsState = { + prevRows: [], + rows:[ + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: false, + isNew: false, + } + ]} - expect(dataPointsReducer(initState, action)).toEqual(payload) + expect(dataPointsReducer(initState, action)).toEqual({ + prevRows: [], + rows: payload + } + ) }) }) @@ -271,42 +301,50 @@ describe("data points reducer", () => { payload } - const initState: TableDataRow[] = [ - { - dataPoints: [ - { - name: "Water", - value: "100" - }, - { - name: "score", - value: [0.1] - } - ], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [ - { - name: "Water", - value: "200" - }, - { - name: "score", - value: [0.2] - } - ], - isEditMode: false, - isNew: false, - } - ] + const initState: DataPointsState = { + prevRows: [], + rows:[ + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "200" + }, + { + name: "score", + value: [0.2] + } + ], + isEditMode: false, + isNew: false, + } + ] + } - expect(dataPointsReducer(initState, action)).toEqual(initState.slice(0, 1)) + expect(dataPointsReducer(initState, action)).toEqual({ + prevRows: [], + rows: initState.rows.slice(0, 1) + } + ) + }) }) - describe("DataPointsTableAddedAction", () => { + describe("DataPointsTableRowAddedAction", () => { it("should add row", async () => { const payload = { dataPoints: [ @@ -328,32 +366,38 @@ describe("data points reducer", () => { payload } - const initState: TableDataRow[] = [ - { - dataPoints: [ - { - name: "Milk", - value: "100" - }, - { - name: "score", - value: [0.1] - } - ], - isEditMode: true, - isNew: true, - }, - ] - - expect(dataPointsReducer(initState, action)).toEqual( - [ + const initState: DataPointsState = { + prevRows: [], + rows: [ { - ...initState[0], - isEditMode: false, - isNew: false + dataPoints: [ + { + name: "Milk", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: true, + isNew: true, }, - payload - ], + ] + } + + expect(dataPointsReducer(initState, action)).toEqual({ + prevRows: [], + rows: + [ + { + ...initState.rows[0], + isEditMode: false, + isNew: false + }, + payload + ] + } ) }) }) diff --git a/types/common.ts b/types/common.ts index 6e6df968..9c8e8e5c 100644 --- a/types/common.ts +++ b/types/common.ts @@ -20,6 +20,7 @@ export type Info = { description: string } +//TODO split export type VariableType = { name: string description: string From 0a09d6a1cd9522dfdc6094447d06ae981a5cfca9 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Tue, 27 Apr 2021 14:07:39 +0200 Subject: [PATCH 20/25] Add cancel functionality to table row edits --- components/categorical-variable.tsx | 1 - components/data-points.tsx | 19 +- reducers/data-points-reducer.ts | 39 +++- tests/reducers/data-points-reducer.test.ts | 243 +++++++++++---------- 4 files changed, 165 insertions(+), 137 deletions(-) diff --git a/components/categorical-variable.tsx b/components/categorical-variable.tsx index d5eb4caa..424fd025 100644 --- a/components/categorical-variable.tsx +++ b/components/categorical-variable.tsx @@ -12,7 +12,6 @@ type CategoricalVariableProps = { export default function CategoricalVariable(props: CategoricalVariableProps) { const classes = useStyles() - //TODO: Avoid handling options separately? const [options, setOptions] = useState([]) const { register, handleSubmit, reset, watch, errors } = useForm(); diff --git a/components/data-points.tsx b/components/data-points.tsx index 7cf78d12..0f159548 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -47,7 +47,7 @@ export default function DataPoints(props: DataPointProps) { const initialState: DataPointsState = { rows: dataPointRows, - prevRows: [] + prevRows: dataPointRows } const [state, dispatch] = useReducer(dataPointsReducer, initialState) @@ -58,21 +58,10 @@ export default function DataPoints(props: DataPointProps) { function toggleEditMode(rowIndex: number) { dispatch({ type: DATA_POINTS_TABLE_EDIT_TOGGLED, payload: rowIndex }) - - /*let prevRowsEdited: TableDataRow[] = prevRows.slice() - prevRowsEdited.map((item, i) => { - if (i !== rowIndex) { - return item - } else { - rows[rowIndex] - } - }) - setPrevRows(prevRowsEdited) - console.log('prevRows', prevRows)*/ } - function cancelEdit(prevRows: TableDataRow[], rowIndex: number) { - dispatch({ type: DATA_POINTS_TABLE_EDIT_CANCELLED, payload: { prevRows, rowIndex } }) + function cancelEdit(rowIndex: number) { + dispatch({ type: DATA_POINTS_TABLE_EDIT_CANCELLED, payload: rowIndex }) } function edit(editValue: string, rowIndex: number, itemIndex: number) { @@ -127,7 +116,7 @@ export default function DataPoints(props: DataPointProps) { useArrayForValue={SCORE} onEdit={(editValue: string, rowIndex: number, itemIndex: number) => edit(editValue, rowIndex, itemIndex)} onEditConfirm={(row: TableDataRow, rowIndex: number) => onEditConfirm(row, rowIndex)} - onEditCancel={(rowIndex: number) => cancelEdit(state.prevRows, rowIndex)} + onEditCancel={(rowIndex: number) => cancelEdit(rowIndex)} onToggleEditMode={(rowIndex: number) => toggleEditMode(rowIndex)} onDelete={(rowIndex: number) => deleteRow(rowIndex)} /> } diff --git a/reducers/data-points-reducer.ts b/reducers/data-points-reducer.ts index d47513a0..a0f9752d 100644 --- a/reducers/data-points-reducer.ts +++ b/reducers/data-points-reducer.ts @@ -27,10 +27,7 @@ export type DataPointsTableEditToggledAction = { export type DataPointsTableEditCancelledAction = { type: typeof DATA_POINTS_TABLE_EDIT_CANCELLED - payload: { - prevRows: TableDataRow[] - rowIndex: number - } + payload: number } export type DataPointsTableEditedAction = { @@ -61,9 +58,16 @@ export type DataPointsTableRowAddedAction = { export const dataPointsReducer = (state: DataPointsState, action: DataPointsTableAction) => { switch (action.type) { case DATA_POINTS_TABLE_EDIT_TOGGLED: - //TODO: save current row in state.prevRows + const rowIndexEditToggled = action.payload return { ...state, + prevRows: state.prevRows.map((row, i) => { + if (i !== rowIndexEditToggled) { + return row + } else { + return state.rows[rowIndexEditToggled] + } + }), rows: state.rows.map((row, index) => { if (index !== action.payload) { return row @@ -76,14 +80,17 @@ export const dataPointsReducer = (state: DataPointsState, action: DataPointsTabl }) } case DATA_POINTS_TABLE_EDIT_CANCELLED: - const rowIndexEditCancelled = action.payload.rowIndex + const rowIndexEditCancelled = action.payload return { ...state, rows: state.rows.map((row, i) => { if (i !== rowIndexEditCancelled) { return row } else { - return action.payload.prevRows[rowIndexEditCancelled] + return { + ...state.prevRows[rowIndexEditCancelled], + isEditMode: false + } } }) } @@ -118,8 +125,12 @@ export const dataPointsReducer = (state: DataPointsState, action: DataPointsTabl case DATA_POINTS_TABLE_ROW_DELETED: let rowsAfterDelete: TableDataRow[] = state.rows.slice() rowsAfterDelete.splice(action.payload, 1) + + let preRowsAfterDelete: TableDataRow[] = state.prevRows.slice() + preRowsAfterDelete.splice(action.payload, 1) return { ...state, + prevRows: preRowsAfterDelete, rows: rowsAfterDelete } case DATA_POINTS_TABLE_ROW_ADDED: @@ -135,8 +146,22 @@ export const dataPointsReducer = (state: DataPointsState, action: DataPointsTabl } }) rowsAfterAdded.splice(state.rows.length, 0, action.payload) + + const preRowsAfterAdded: TableDataRow[] = state.prevRows.slice().map((item, i) => { + if (state.prevRows.length - 1 !== i) { + return item + } else { + return { + ...item, + isEditMode: false, + isNew: false, + } + } + }) + preRowsAfterAdded.splice(state.rows.length, 0, action.payload) return { ...state, + prevRows: preRowsAfterAdded, rows: rowsAfterAdded } } diff --git a/tests/reducers/data-points-reducer.test.ts b/tests/reducers/data-points-reducer.test.ts index a568741d..8edc3c2c 100644 --- a/tests/reducers/data-points-reducer.test.ts +++ b/tests/reducers/data-points-reducer.test.ts @@ -1,95 +1,97 @@ import { dataPointsReducer, DataPointsState, DataPointsTableEditCancelledAction, DataPointsTableEditedAction, DataPointsTableEditToggledAction, DataPointsTableRowAddedAction, DataPointsTableRowDeletedAction, DataPointsTableUpdatedAction, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, DATA_POINTS_TABLE_UPDATED } from "../../reducers/data-points-reducer" import { TableDataRow } from "../../types/common" -//TODO: Simplify by reusing state - describe("data points reducer", () => { describe("DataPointsTableEditToggledAction", () => { - it("should toggle edit mode", async () => { - const payload = 1 + it("should toggle edit mode and save previous row", async () => { + const payload = 0 const action: DataPointsTableEditToggledAction = { type: DATA_POINTS_TABLE_EDIT_TOGGLED, payload } + const initPrevRows: TableDataRow[] = [ + { + dataPoints: [ + { + name: "Milk", + value: "100" + }, + { + name: "score", + value: [1] + } + ], + isEditMode: false, + isNew: false, + } + ] + + const initRows: TableDataRow[] = [ + { + dataPoints: [ + { + name: "Milk", + value: "200" + }, + { + name: "score", + value: [1] + } + ], + isEditMode: false, + isNew: false, + } + ] + const initState: DataPointsState = { - prevRows: [], - rows: [ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [], - isEditMode: false, - isNew: false, - } - ] + prevRows: initPrevRows, + rows: initRows } expect(dataPointsReducer(initState, action)).toEqual( { - prevRows: [], + prevRows: initRows, rows:[ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [], - isEditMode: true, - isNew: false, - } + { + ...initRows[0], + isEditMode: true, + isNew: false, + } ]} ) }) }) describe("DataPointsTableEditCancelledAction", () => { - /*it("should cancel edit", async () => { - const payload = { + it("should toggle edit mode and set row to prevRow", async () => { + const payload = 0 + + const action: DataPointsTableEditCancelledAction = { + type: DATA_POINTS_TABLE_EDIT_CANCELLED, + payload + } + + const initState: DataPointsState = { prevRows: [ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, { dataPoints: [ { name: "Water", - value: "50" + value: "100" } ], isEditMode: false, isNew: false, } ], - rowIndex: 1 - } - - const action: DataPointsTableEditCancelledAction = { - type: DATA_POINTS_TABLE_EDIT_CANCELLED, - payload - } - - const initState: DataPointsState = { - prevRows: [], - rows: - [ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, + rows: [ { dataPoints: [ { name: "Water", - value: "100" + value: "200" } ], isEditMode: true, @@ -98,8 +100,11 @@ describe("data points reducer", () => { ] } - expect(dataPointsReducer(initState, action)).toEqual(payload.prevRows) - })*/ + expect(dataPointsReducer(initState, action)).toEqual({ + prevRows: initState.prevRows, + rows: initState.prevRows + }) + }) }) describe("DataPointsTableEditedAction", () => { @@ -301,42 +306,44 @@ describe("data points reducer", () => { payload } + const initRows: TableDataRow[] = [ + { + dataPoints: [ + { + name: "Water", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: false, + isNew: false, + }, + { + dataPoints: [ + { + name: "Water", + value: "200" + }, + { + name: "score", + value: [0.2] + } + ], + isEditMode: false, + isNew: false, + } + ] + const initState: DataPointsState = { - prevRows: [], - rows:[ - { - dataPoints: [ - { - name: "Water", - value: "100" - }, - { - name: "score", - value: [0.1] - } - ], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [ - { - name: "Water", - value: "200" - }, - { - name: "score", - value: [0.2] - } - ], - isEditMode: false, - isNew: false, - } - ] + prevRows: initRows, + rows: initRows } expect(dataPointsReducer(initState, action)).toEqual({ - prevRows: [], + prevRows: initState.prevRows.slice(0, 1), rows: initState.rows.slice(0, 1) } ) @@ -366,37 +373,45 @@ describe("data points reducer", () => { payload } + const initRows: TableDataRow[] = [ + { + dataPoints: [ + { + name: "Milk", + value: "100" + }, + { + name: "score", + value: [0.1] + } + ], + isEditMode: true, + isNew: true, + }, + ] + const initState: DataPointsState = { - prevRows: [], + prevRows: initRows, + rows: initRows + } + + expect(dataPointsReducer(initState, action)).toEqual({ + prevRows: [ + { + ...initState.rows[0], + isEditMode: false, + isNew: false + }, + payload + ], rows: [ { - dataPoints: [ - { - name: "Milk", - value: "100" - }, - { - name: "score", - value: [0.1] - } - ], - isEditMode: true, - isNew: true, + ...initState.rows[0], + isEditMode: false, + isNew: false }, + payload ] - } - - expect(dataPointsReducer(initState, action)).toEqual({ - prevRows: [], - rows: - [ - { - ...initState.rows[0], - isEditMode: false, - isNew: false - }, - payload - ] } ) }) From 544d0d41652897d8d9e606c075e42b00f5effc91 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Tue, 27 Apr 2021 14:23:05 +0200 Subject: [PATCH 21/25] Revert variables to use separate types --- components/categorical-variable.tsx | 8 ++++---- components/data-points.tsx | 4 ++-- components/optimizer-model.tsx | 14 +++++++------- components/value-variable.tsx | 8 ++++---- components/variable-editor.tsx | 10 +++++----- pages/experiment/[experimentid].tsx | 18 +++++++++--------- reducers/reducers.ts | 18 +++++++++--------- tests/reducers/reducers.test.ts | 10 +++++----- types/common.ts | 29 +++++++++++++++++++++-------- utility/converters.ts | 4 ++-- 10 files changed, 68 insertions(+), 55 deletions(-) diff --git a/components/categorical-variable.tsx b/components/categorical-variable.tsx index 424fd025..3a42a68f 100644 --- a/components/categorical-variable.tsx +++ b/components/categorical-variable.tsx @@ -2,20 +2,20 @@ import { Button, IconButton, TextField, Typography } from '@material-ui/core'; import DeleteIcon from "@material-ui/icons/Delete"; import { useState } from 'react'; import { useForm } from 'react-hook-form'; -import { VariableType } from '../types/common'; import CategoricalVariableOptions from './categorical-variable-options'; import { useStyles } from '../styles/categorical-variable.style'; +import { CategoricalVariableType } from '../types/common'; type CategoricalVariableProps = { - onAdded: (data: VariableType) => void + onAdded: (data: CategoricalVariableType) => void } export default function CategoricalVariable(props: CategoricalVariableProps) { const classes = useStyles() const [options, setOptions] = useState([]) - const { register, handleSubmit, reset, watch, errors } = useForm(); - const onSubmit = async (data: VariableType) => { + const { register, handleSubmit, reset, watch, errors } = useForm(); + const onSubmit = async (data: CategoricalVariableType) => { props.onAdded({...data, options}) setOptions([]) reset() diff --git a/components/data-points.tsx b/components/data-points.tsx index 0f159548..add828ea 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,7 +1,7 @@ import { Card, CardContent, Typography } from "@material-ui/core"; import { useEffect, useReducer } from "react"; import { dataPointsReducer, DataPointsState, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, testReducer } from "../reducers/data-points-reducer"; -import { ExperimentType, VariableType, DataPointType, TableDataPoint, TableDataRow } from "../types/common"; +import { ExperimentType, DataPointType, TableDataPoint, TableDataRow, CombinedVariableType } from "../types/common"; import { EditableTable } from "./editable-table"; type DataPointProps = { @@ -13,7 +13,7 @@ const SCORE = "score" export default function DataPoints(props: DataPointProps) { const { experiment: { valueVariables, categoricalVariables, dataPoints }, onUpdateDataPoints } = props - const combinedVariables: VariableType[] = valueVariables.concat(categoricalVariables) + const combinedVariables: CombinedVariableType[] = (valueVariables as CombinedVariableType[]).concat(categoricalVariables as CombinedVariableType[]) const emptyRow: TableDataRow = { dataPoints: combinedVariables.map((variable, i) => { diff --git a/components/optimizer-model.tsx b/components/optimizer-model.tsx index f497a7bc..3925f58e 100644 --- a/components/optimizer-model.tsx +++ b/components/optimizer-model.tsx @@ -1,15 +1,15 @@ import { Button, Card, CardContent, IconButton, Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@material-ui/core' -import { ExperimentType, VariableType } from '../types/common' +import { CategoricalVariableType, ExperimentType, ValueVariableType } from '../types/common' import DeleteIcon from '@material-ui/icons/Delete' import { ReactNode, useState } from 'react' import VariableEditor from './variable-editor' type OptimizerModelProps = { experiment: ExperimentType - onDeleteValueVariable: (valueVariable: VariableType) => void - onDeleteCategoricalVariable: (categoricalVariable: VariableType) => void - addValueVariable: (valueVariable: VariableType) => void - addCategoricalVariable: (categoricalVariable: VariableType) => void + onDeleteValueVariable: (valueVariable: ValueVariableType) => void + onDeleteCategoricalVariable: (categoricalVariable: CategoricalVariableType) => void + addValueVariable: (valueVariable: ValueVariableType) => void + addCategoricalVariable: (categoricalVariable: CategoricalVariableType) => void } export default function OptimizerModel(props: OptimizerModelProps) { @@ -103,8 +103,8 @@ export default function OptimizerModel(props: OptimizerModelProps) { } {isAddOpen && props.addCategoricalVariable(categoricalVariable)} - addValueVariable={(valueVariable: VariableType) => props.addValueVariable(valueVariable)} + addCategoricalVariable={(categoricalVariable: CategoricalVariableType) => props.addCategoricalVariable(categoricalVariable)} + addValueVariable={(valueVariable: ValueVariableType) => props.addValueVariable(valueVariable)} close={() => setAddOpen(false)} /> } diff --git a/components/value-variable.tsx b/components/value-variable.tsx index 19380ba2..675aa16e 100644 --- a/components/value-variable.tsx +++ b/components/value-variable.tsx @@ -1,15 +1,15 @@ import { Button, TextField } from '@material-ui/core' import { useForm } from 'react-hook-form'; -import { VariableType } from '../types/common'; +import { ValueVariableType } from '../types/common'; type ValueVariableProps = { - onAdded: (data: VariableType) => void + onAdded: (data: ValueVariableType) => void } export default function ValueVariable(props: ValueVariableProps) { - const { register, handleSubmit, reset, watch, errors } = useForm(); - const onSubmit = async (data: VariableType) => { + const { register, handleSubmit, reset, watch, errors } = useForm(); + const onSubmit = async (data: ValueVariableType) => { props.onAdded(data) reset() } diff --git a/components/variable-editor.tsx b/components/variable-editor.tsx index a5fc1e9a..dca13b8b 100644 --- a/components/variable-editor.tsx +++ b/components/variable-editor.tsx @@ -3,12 +3,12 @@ import ValueVariable from './value-variable'; import { Card, CardContent, Grid, IconButton, Radio, Typography } from "@material-ui/core" import CloseIcon from "@material-ui/icons/Close"; import { useState } from "react" -import { VariableType } from '../types/common'; import useStyles from '../styles/variable-editor.style'; +import { CategoricalVariableType, ValueVariableType } from '../types/common'; type VariableEditorProps = { - addValueVariable: (valueVariable: VariableType) => void - addCategoricalVariable: (categoricalVariable: VariableType) => void + addValueVariable: (valueVariable: ValueVariableType) => void + addCategoricalVariable: (categoricalVariable: CategoricalVariableType) => void close: () => void } @@ -53,10 +53,10 @@ export default function VariableEditor(props: VariableEditorProps) {

{radioIndex === 0 && - props.addValueVariable(valueVariable)} /> + props.addValueVariable(valueVariable)} /> } {radioIndex === 1 && - props.addCategoricalVariable(categoricalVariable)} /> + props.addCategoricalVariable(categoricalVariable)} /> } diff --git a/pages/experiment/[experimentid].tsx b/pages/experiment/[experimentid].tsx index fad5c4ba..65eb8871 100644 --- a/pages/experiment/[experimentid].tsx +++ b/pages/experiment/[experimentid].tsx @@ -7,7 +7,7 @@ import OptimizerModel from '../../components/optimizer-model'; import OptimizerConfigurator from '../../components/optimizer-configurator'; import { useEffect, useReducer, useState } from 'react'; import { VALUE_VARIABLE_ADDED, EXPERIMENT_DESCRIPTION_UPDATED, EXPERIMENT_NAME_UPDATED, EXPERIMENT_UPDATED, rootReducer, VALUE_VARIABLE_DELETED, CATEGORICAL_VARIABLE_ADDED, CATEGORICAL_VARIABLE_DELETED, CONFIGURATION_UPDATED, RESULT_REGISTERED, DATA_POINTS_ADDED, DATA_POINTS_UPDATED } from '../../reducers/reducers'; -import { VariableType, ExperimentType, OptimizerConfig, ExperimentResultType, DataPointType } from '../../types/common'; +import { ExperimentType, OptimizerConfig, ExperimentResultType, DataPointType, ValueVariableType, CategoricalVariableType } from '../../types/common'; import { initialState } from '../../store'; import { Alert } from '@material-ui/lab'; import ModelEditor from '../../components/model-editor'; @@ -73,19 +73,19 @@ export default function Experiment() { dispatch({ type: RESULT_REGISTERED, payload: result }) } - function addValueVariable(valueVariable: VariableType) { + function addValueVariable(valueVariable: ValueVariableType) { dispatch({ type: VALUE_VARIABLE_ADDED, payload: valueVariable }) } - function deleteValueVariable(valueVariable: VariableType) { + function deleteValueVariable(valueVariable: ValueVariableType) { dispatch({ type: VALUE_VARIABLE_DELETED, payload: valueVariable }) } - function addCategoricalVariable(categoricalVariable: VariableType) { + function addCategoricalVariable(categoricalVariable: CategoricalVariableType) { dispatch({ type: CATEGORICAL_VARIABLE_ADDED, payload: categoricalVariable}) } - function deleteCategoricalVariable(categoricalVariable: VariableType) { + function deleteCategoricalVariable(categoricalVariable: CategoricalVariableType) { dispatch({ type: CATEGORICAL_VARIABLE_DELETED, payload: categoricalVariable }) } @@ -158,10 +158,10 @@ export default function Experiment() { {deleteValueVariable(valueVariable)}} - onDeleteCategoricalVariable={(categoricalVariable: VariableType) => {deleteCategoricalVariable(categoricalVariable)}} - addValueVariable={(valueVariable: VariableType) => addValueVariable(valueVariable)} - addCategoricalVariable={(categoricalVariable: VariableType) => addCategoricalVariable(categoricalVariable)}/> + onDeleteValueVariable={(valueVariable: ValueVariableType) => {deleteValueVariable(valueVariable)}} + onDeleteCategoricalVariable={(categoricalVariable: CategoricalVariableType) => {deleteCategoricalVariable(categoricalVariable)}} + addValueVariable={(valueVariable: ValueVariableType) => addValueVariable(valueVariable)} + addCategoricalVariable={(categoricalVariable: CategoricalVariableType) => addCategoricalVariable(categoricalVariable)}/>
{ const initState: State = { @@ -116,7 +116,7 @@ describe("experiment reducer", () => { }) it("should add value variable", async () => { - const payload: VariableType = { + const payload: ValueVariableType = { name: "Flour", description: "Wet", minVal: 300, @@ -142,7 +142,7 @@ describe("experiment reducer", () => { }) it("should delete value variable", async () => { - const payload: VariableType = { + const payload: ValueVariableType = { name: "Water", description: "Wet", minVal: 100, @@ -162,7 +162,7 @@ describe("experiment reducer", () => { }) it("should add categorial variable", async () => { - const payload: VariableType = { + const payload: CategoricalVariableType = { name: "Fat", description: "Fatty", options: [], @@ -186,7 +186,7 @@ describe("experiment reducer", () => { }) it("should delete categorical variable", async () => { - const payload: VariableType = { + const payload: CategoricalVariableType = { name: "Icing", description: "Sugary", options: [], diff --git a/types/common.ts b/types/common.ts index 9c8e8e5c..75a29692 100644 --- a/types/common.ts +++ b/types/common.ts @@ -1,8 +1,8 @@ export type ExperimentType = { id: string info: Info - categoricalVariables: VariableType[] - valueVariables: VariableType[] + categoricalVariables: CategoricalVariableType[] + valueVariables:  ValueVariableType[] optimizerConfig: OptimizerConfig results: ExperimentResultType dataPoints: DataPointType[][] @@ -20,16 +20,21 @@ export type Info = { description: string } -//TODO split -export type VariableType = { +export type CategoricalVariableType = { name: string description: string - minVal?: number - maxVal?: number - options?: string[] - order?: string + options: string[] +} + +export type ValueVariableType = { + name: string + description: string + minVal: number + maxVal: number } +export type VariableType = CategoricalVariableType | ValueVariableType + export type OptimizerConfig = { baseEstimator: string acqFunc: string @@ -68,4 +73,12 @@ export type TableDataRow = { dataPoints: TableDataPoint[] isEditMode: boolean isNew: boolean +} + +export type CombinedVariableType = { + name: string + description: string + minVal?: number + maxVal?: number + options?: string[] } \ No newline at end of file diff --git a/utility/converters.ts b/utility/converters.ts index 88813d9b..0c649855 100644 --- a/utility/converters.ts +++ b/utility/converters.ts @@ -1,5 +1,5 @@ import { ExperimentData } from "../openapi" -import { VariableType, DataPointType, ExperimentType, ScoreDataPointType, SpaceType } from "../types/common" +import { CategoricalVariableType, DataPointType, ExperimentType, ScoreDataPointType, SpaceType, ValueVariableType } from "../types/common" export const calculateSpace = (experiment: ExperimentType): SpaceType => { const numeric: SpaceType = experiment.valueVariables.map(v => { return {type: "numeric", name: v.name, from: Number(v.minVal), to: Number(v.maxVal)}}) @@ -7,6 +7,6 @@ export const calculateSpace = (experiment: ExperimentType): SpaceType => { return numeric.concat(categorial) } const numPat = / [0-9] + / -export const calculateData = (categorialValues: VariableType[], numericValues: VariableType[], dataPoints: DataPointType[][]): ExperimentData[] => { +export const calculateData = (categorialValues: CategoricalVariableType[], numericValues: ValueVariableType[], dataPoints: DataPointType[][]): ExperimentData[] => { return dataPoints.map((run):ExperimentData => ({xi: run.filter(it => it.name !== "score").map(it => numericValues.find(p => p.name === it.name) ? Number(it.value) : it.value), yi: Number((run.filter(it => it.name === "score")[0] as ScoreDataPointType).value[0])})) } \ No newline at end of file From 4ed1674b89236f23f6322d592b7c51ad3dfa6cf5 Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Tue, 27 Apr 2021 14:32:35 +0200 Subject: [PATCH 22/25] Small test adjustment, formatting --- tests/reducers/reducers.test.ts | 2 +- types/common.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/reducers/reducers.test.ts b/tests/reducers/reducers.test.ts index 9b407838..7bcfc328 100644 --- a/tests/reducers/reducers.test.ts +++ b/tests/reducers/reducers.test.ts @@ -254,7 +254,7 @@ describe("experiment reducer", () => { [ { name: "New point 1", - value: 1 + value: "1" }, { name: "score", diff --git a/types/common.ts b/types/common.ts index 75a29692..c7e6a7a4 100644 --- a/types/common.ts +++ b/types/common.ts @@ -2,7 +2,7 @@ export type ExperimentType = { id: string info: Info categoricalVariables: CategoricalVariableType[] - valueVariables:  ValueVariableType[] + valueVariables: ValueVariableType[] optimizerConfig: OptimizerConfig results: ExperimentResultType dataPoints: DataPointType[][] From e56b8a0130dd7e941d8910a81f00c8812585ab6b Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Tue, 27 Apr 2021 15:49:20 +0200 Subject: [PATCH 23/25] Refactor data points reducer tests --- tests/reducers/data-points-reducer.test.ts | 82 ++++++---------------- 1 file changed, 22 insertions(+), 60 deletions(-) diff --git a/tests/reducers/data-points-reducer.test.ts b/tests/reducers/data-points-reducer.test.ts index 8edc3c2c..9cb0b87e 100644 --- a/tests/reducers/data-points-reducer.test.ts +++ b/tests/reducers/data-points-reducer.test.ts @@ -2,6 +2,7 @@ import { dataPointsReducer, DataPointsState, DataPointsTableEditCancelledAction, import { TableDataRow } from "../../types/common" describe("data points reducer", () => { + describe("DataPointsTableEditToggledAction", () => { it("should toggle edit mode and save previous row", async () => { const payload = 0 @@ -111,7 +112,7 @@ describe("data points reducer", () => { it("should edit table cell - non-array value", async () => { const payload = { value: "300", - rowIndex: 1, + rowIndex: 0, itemIndex: 1, useArrayForValue: "score" } @@ -124,11 +125,6 @@ describe("data points reducer", () => { const initState: DataPointsState = { prevRows: [], rows:[ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, { dataPoints: [ { @@ -150,41 +146,24 @@ describe("data points reducer", () => { ] } - expect(dataPointsReducer(initState, action)).toEqual( - { - prevRows: [], - rows:[ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { - dataPoints: [ - { - name: "Water", - value: "100" - }, - { - name: "Milk", - value: "300" - }, - { - name: "score", - value: [0.5] - } - ], - isEditMode: true, - isNew: false, - } - ] - }) + expect(dataPointsReducer(initState, action)).toEqual({ + ...initState, + rows: [{ + ...initState.rows[0], + dataPoints: [ + {...initState.rows[0].dataPoints[0]}, + {...initState.rows[0].dataPoints[1], + value: payload.value + }, + {...initState.rows[0].dataPoints[2]} + ]} + ]}) }) it("should edit table cell - array value", async () => { const payload = { value: "0.2", - rowIndex: 1, + rowIndex: 0, itemIndex: 1, useArrayForValue: "score" } @@ -197,11 +176,6 @@ describe("data points reducer", () => { const initState: DataPointsState = { prevRows: [], rows:[ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, { dataPoints: [ { @@ -220,27 +194,15 @@ describe("data points reducer", () => { } expect(dataPointsReducer(initState, action)).toEqual({ - prevRows: [], - rows:[ - { - dataPoints: [], - isEditMode: false, - isNew: false, - }, - { + ...initState, + rows: [{ + ...initState.rows[0], dataPoints: [ - { - name: "Water", - value: "100" - }, - { - name: "score", - value: ["0.2"] + {...initState.rows[0].dataPoints[0]}, + {...initState.rows[0].dataPoints[1], + value: [payload.value] } - ], - isEditMode: true, - isNew: false, - } + ]} ]} ) }) From 642e4d6d1d3501e2d9b8dd1520b222cabb5651e9 Mon Sep 17 00:00:00 2001 From: Jakob Langdal Date: Tue, 27 Apr 2021 21:19:11 +0200 Subject: [PATCH 24/25] Remove unused variable --- components/data-points.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/data-points.tsx b/components/data-points.tsx index add828ea..b98e4cec 100644 --- a/components/data-points.tsx +++ b/components/data-points.tsx @@ -1,6 +1,6 @@ import { Card, CardContent, Typography } from "@material-ui/core"; import { useEffect, useReducer } from "react"; -import { dataPointsReducer, DataPointsState, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED, testReducer } from "../reducers/data-points-reducer"; +import { dataPointsReducer, DataPointsState, DATA_POINTS_TABLE_EDITED, DATA_POINTS_TABLE_EDIT_CANCELLED, DATA_POINTS_TABLE_EDIT_TOGGLED, DATA_POINTS_TABLE_ROW_ADDED, DATA_POINTS_TABLE_ROW_DELETED } from "../reducers/data-points-reducer"; import { ExperimentType, DataPointType, TableDataPoint, TableDataRow, CombinedVariableType } from "../types/common"; import { EditableTable } from "./editable-table"; From b791df1493a8a5aa22b6ac5da13543d5b34f339a Mon Sep 17 00:00:00 2001 From: Jack Ord <36296721+j-or@users.noreply.github.com> Date: Wed, 28 Apr 2021 09:41:36 +0200 Subject: [PATCH 25/25] Update value to match variable name, fix disappearing table row --- reducers/data-points-reducer.ts | 2 +- reducers/reducers.ts | 2 +- tests/reducers/data-points-reducer.test.ts | 41 ++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/reducers/data-points-reducer.ts b/reducers/data-points-reducer.ts index a0f9752d..67eaa020 100644 --- a/reducers/data-points-reducer.ts +++ b/reducers/data-points-reducer.ts @@ -89,7 +89,7 @@ export const dataPointsReducer = (state: DataPointsState, action: DataPointsTabl } else { return { ...state.prevRows[rowIndexEditCancelled], - isEditMode: false + isEditMode: row.isNew } } }) diff --git a/reducers/reducers.ts b/reducers/reducers.ts index eec16a7e..4711cef7 100644 --- a/reducers/reducers.ts +++ b/reducers/reducers.ts @@ -11,7 +11,7 @@ export const CATEGORICAL_VARIABLE_DELETED = 'CATEGORICAL_VARIABLE_DELETED' export const CONFIGURATION_UPDATED = 'CONFIGURATION_UPDATED' export const RESULT_REGISTERED = 'RESULT_REGISTERED' export const DATA_POINTS_ADDED = 'DATA_POINTS_ADDED' -export const DATA_POINTS_UPDATED = 'DATA_POINTS_EDITED' +export const DATA_POINTS_UPDATED = 'DATA_POINTS_UPDATED' export type ResultRegisteredAction = { type: typeof RESULT_REGISTERED diff --git a/tests/reducers/data-points-reducer.test.ts b/tests/reducers/data-points-reducer.test.ts index 9cb0b87e..af03b386 100644 --- a/tests/reducers/data-points-reducer.test.ts +++ b/tests/reducers/data-points-reducer.test.ts @@ -106,6 +106,47 @@ describe("data points reducer", () => { rows: initState.prevRows }) }) + + it("should set row to prevRow but not toggle edit mode for new row", async () => { + const payload = 0 + + const action: DataPointsTableEditCancelledAction = { + type: DATA_POINTS_TABLE_EDIT_CANCELLED, + payload + } + + const initState: DataPointsState = { + prevRows: [ + { + dataPoints: [ + { + name: "Water", + value: "" + } + ], + isEditMode: true, + isNew: true, + } + ], + rows: [ + { + dataPoints: [ + { + name: "Water", + value: "100" + } + ], + isEditMode: true, + isNew: true, + } + ] + } + expect(dataPointsReducer(initState, action)).toEqual({ + prevRows: initState.prevRows, + rows: initState.prevRows + }) + }) + }) describe("DataPointsTableEditedAction", () => {