diff --git a/app/api/entities/reaction_variation_entity.rb b/app/api/entities/reaction_variation_entity.rb
index 6909de9c66..cee722d5ec 100644
--- a/app/api/entities/reaction_variation_entity.rb
+++ b/app/api/entities/reaction_variation_entity.rb
@@ -4,9 +4,8 @@ module Entities
class ReactionVariationEntity < ApplicationEntity
expose(
:id,
- :notes,
:properties,
- :analyses,
+ :metadata,
:reactants,
:products,
:solvents,
@@ -14,12 +13,15 @@ class ReactionVariationEntity < ApplicationEntity
expose :starting_materials, as: :startingMaterials
def properties
- {}.tap do |properties|
- properties[:temperature] = ReactionVariationPropertyEntity.represent(object[:properties][:temperature])
- properties[:duration] = ReactionVariationPropertyEntity.represent(object[:properties][:duration])
+ object[:properties].slice(:duration, :temperature).transform_values do |value|
+ ReactionVariationPropertyEntity.represent(value)
end
end
+ def metadata
+ object[:metadata].slice(:notes, :analyses)
+ end
+
def materials(material_type, entity)
{}.tap do |materials|
object[material_type]&.each do |k, v|
diff --git a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariations.js b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariations.js
index 1293603f9e..4b3da7d69c 100644
--- a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariations.js
+++ b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariations.js
@@ -11,104 +11,42 @@ import { isEqual } from 'lodash';
import PropTypes from 'prop-types';
import Reaction from 'src/models/Reaction';
import {
- createVariationsRow, copyVariationsRow, updateVariationsRow, getCellDataType, getStandardUnits, materialTypes
+ createVariationsRow, copyVariationsRow, updateVariationsRow, getVariationsColumns, materialTypes,
+ addMissingColumnsToVariations, removeObsoleteColumnsFromVariations, getColumnDefinitions
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils';
import {
- AnalysesCellRenderer, AnalysesCellEditor, getReactionAnalyses, updateAnalyses, getAnalysesOverlay, AnalysisOverlay
+ getReactionAnalyses, updateAnalyses
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsAnalyses';
import {
- getMaterialColumnGroupChild, updateVariationsGasTypes,
+ updateVariationsGasTypes,
getReactionMaterials, getReactionMaterialsIDs, getReactionMaterialsGasTypes,
- removeObsoleteMaterialsFromVariations, addMissingMaterialsToVariations
+ removeObsoleteMaterialColumns
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials';
import {
PropertyFormatter, PropertyParser,
MaterialFormatter, MaterialParser,
EquivalentParser, GasParser, FeedstockParser,
- NoteCellRenderer, NoteCellEditor,
- RowToolsCellRenderer, MenuHeader
+ ColumnSelection
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents';
-import {
- columnDefinitionsReducer
-} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsReducers';
+import columnDefinitionsReducer
+ from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsReducers';
import GasPhaseReactionStore from 'src/stores/alt/stores/GasPhaseReactionStore';
export default function ReactionVariations({ reaction, onReactionChange, isActive }) {
const gridRef = useRef(null);
const reactionVariations = reaction.variations;
const reactionHasPolymers = reaction.hasPolymers();
+ const { dispValue: durationValue = null, dispUnit: durationUnit = 'None' } = reaction.durationDisplay ?? {};
+ const { userText: temperatureValue = null, valueUnit: temperatureUnit = 'None' } = reaction.temperature ?? {};
+ const vesselVolume = GasPhaseReactionStore.getState().reactionVesselSizeValue;
const [gasMode, setGasMode] = useState(reaction.gaseous);
const [allReactionAnalyses, setAllReactionAnalyses] = useState(getReactionAnalyses(reaction));
const [reactionMaterials, setReactionMaterials] = useState(getReactionMaterials(reaction));
- const [columnDefinitions, setColumnDefinitions] = useReducer(columnDefinitionsReducer, [
- {
- headerName: 'Tools',
- cellRenderer: RowToolsCellRenderer,
- lockPosition: 'left',
- sortable: false,
- maxWidth: 100,
- cellDataType: false,
- },
- {
- headerName: 'Notes',
- field: 'notes',
- cellRenderer: NoteCellRenderer,
- sortable: false,
- cellDataType: 'text',
- cellEditor: NoteCellEditor,
- },
- {
- headerName: 'Analyses',
- field: 'analyses',
- tooltipValueGetter: getAnalysesOverlay,
- tooltipComponent: AnalysisOverlay,
- cellRenderer: AnalysesCellRenderer,
- cellEditor: AnalysesCellEditor,
- cellDataType: false,
- sortable: false,
- },
- {
- headerName: 'Properties',
- groupId: 'properties',
- marryChildren: true,
- children: [
- {
- field: 'properties.temperature',
- cellDataType: getCellDataType('temperature'),
- entryDefs: {
- currentEntry: 'temperature',
- displayUnit: getStandardUnits('temperature')[0],
- availableEntries: ['temperature']
- },
- headerComponent: MenuHeader,
- headerComponentParams: {
- names: ['T'],
- }
- },
- {
- field: 'properties.duration',
- cellDataType: getCellDataType('duration'),
- editable: !gasMode,
- entryDefs: {
- currentEntry: 'duration',
- displayUnit: getStandardUnits('duration')[0],
- availableEntries: ['duration']
- },
- headerComponent: MenuHeader,
- headerComponentParams: {
- names: ['t'],
- }
- },
- ]
- },
- ].concat(
- Object.entries(reactionMaterials).map(([materialType, materials]) => ({
- headerName: materialTypes[materialType].label,
- groupId: materialType,
- marryChildren: true,
- children: materials.map((material) => getMaterialColumnGroupChild(material, materialType, MenuHeader, gasMode))
- }))
- ));
+ const [selectedColumns, setSelectedColumns] = useState(getVariationsColumns(reactionVariations));
+ const [columnDefinitions, setColumnDefinitions] = useReducer(
+ columnDefinitionsReducer,
+ getColumnDefinitions(selectedColumns, reactionMaterials, gasMode)
+ );
const dataTypeDefinitions = {
property: {
@@ -180,7 +118,7 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv
/*
Keep set of materials up-to-date.
Materials could have been added or removed in the "Scheme" tab.
- These changes need to be reflected in the variations.
+ We need to only *remove* obsolete materials, not *add* missing ones, since users add materials manually.
*/
if (
!isEqual(
@@ -188,21 +126,25 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv
getReactionMaterialsIDs(updatedReactionMaterials)
)
) {
- let updatedReactionVariations = removeObsoleteMaterialsFromVariations(reactionVariations, updatedReactionMaterials);
- updatedReactionVariations = addMissingMaterialsToVariations(
- updatedReactionVariations,
+ const updatedSelectedColumns = removeObsoleteMaterialColumns(
updatedReactionMaterials,
- updatedGasMode
+ selectedColumns
);
+ setSelectedColumns(updatedSelectedColumns);
+ const updatedReactionVariations = removeObsoleteColumnsFromVariations(
+ reactionVariations,
+ updatedSelectedColumns
+ );
setReactionVariations(updatedReactionVariations);
+
setColumnDefinitions(
{
- type: 'update_material_set',
- gasMode: updatedGasMode,
- reactionMaterials: updatedReactionMaterials
+ type: 'remove_obsolete_materials',
+ selectedColumns: updatedSelectedColumns
}
);
+
setReactionMaterials(updatedReactionMaterials);
}
@@ -210,11 +152,17 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv
Update gas mode according to "Scheme" tab.
*/
if (gasMode !== updatedGasMode) {
+ const updatedSelectedColumns = getVariationsColumns([]);
+ setSelectedColumns(updatedSelectedColumns);
setColumnDefinitions(
{
type: 'toggle_gas_mode',
- gasMode: updatedGasMode,
- reactionMaterials: updatedReactionMaterials
+ materials: Object.keys(materialTypes).reduce((materials, materialType) => {
+ materials[materialType] = [];
+ return materials;
+ }, {}),
+ selectedColumns: updatedSelectedColumns,
+ gasMode: updatedGasMode
}
);
setGasMode(updatedGasMode);
@@ -240,8 +188,9 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv
setColumnDefinitions(
{
type: 'update_gas_type',
+ selectedColumns,
+ materials: updatedReactionMaterials,
gasMode: updatedGasMode,
- reactionMaterials: updatedReactionMaterials
}
);
@@ -294,12 +243,27 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv
setAllReactionAnalyses(updatedAllReactionAnalyses);
}
- const addRow = useCallback(() => {
- const vesselVolume = GasPhaseReactionStore.getState().reactionVesselSizeValue;
+ const addRow = () => {
setReactionVariations(
- [...reactionVariations, createVariationsRow(reaction, reactionVariations, gasMode, vesselVolume)]
+ [
+ ...reactionVariations,
+ createVariationsRow(
+ {
+ materials: reactionMaterials,
+ selectedColumns,
+ variations: reactionVariations,
+ reactionHasPolymers,
+ durationValue,
+ durationUnit,
+ temperatureValue,
+ temperatureUnit,
+ gasMode,
+ vesselVolume
+ }
+ )
+ ]
);
- }, [reaction, reactionVariations, gasMode]);
+ };
const copyRow = useCallback((data) => {
const copiedRow = copyVariationsRow(data, reactionVariations);
@@ -320,9 +284,35 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv
);
}, [reactionVariations, reactionHasPolymers]);
- const fitColumnToContent = (event) => {
- const { column } = event;
- gridRef.current.api.autoSizeColumns([column], false);
+ const applyColumnSelection = (columns) => {
+ let updatedReactionVariations = addMissingColumnsToVariations({
+ materials: reactionMaterials,
+ selectedColumns: columns,
+ variations: reactionVariations,
+ reactionHasPolymers,
+ durationValue,
+ durationUnit,
+ temperatureValue,
+ temperatureUnit,
+ gasMode,
+ vesselVolume
+ });
+ updatedReactionVariations = removeObsoleteColumnsFromVariations(
+ updatedReactionVariations,
+ columns
+ );
+ setReactionVariations(updatedReactionVariations);
+
+ setColumnDefinitions(
+ {
+ type: 'apply_column_selection',
+ materials: reactionMaterials,
+ selectedColumns: columns,
+ gasMode
+ }
+ );
+
+ setSelectedColumns(columns);
};
const removeAllVariations = () => {
@@ -392,11 +382,25 @@ export default function ReactionVariations({ reaction, onReactionChange, isActiv
);
}
+ const fitColumnToContent = (event) => {
+ const { column } = event;
+ gridRef.current.api.autoSizeColumns([column], false);
+ };
+
return (
{addVariation()}
{removeAllVariations()}
+ {ColumnSelection(
+ selectedColumns,
+ {
+ ...getReactionMaterialsIDs(reactionMaterials),
+ properties: ['duration', 'temperature'],
+ metadata: ['notes', 'analyses']
+ },
+ applyColumnSelection,
+ )}
{
// eslint-disable-next-line no-param-reassign
- row.analyses = row.analyses.filter((id) => analysesIDs.includes(id));
+ row.metadata.analyses = row.metadata.analyses.filter((id) => analysesIDs.includes(id));
});
return updatedVariations;
}
-function getAnalysesOverlay({ data: variationsRow, context }) {
- const { analyses: analysesIDs } = variationsRow;
+function getAnalysesOverlay({ data: row, context }) {
+ const { analyses: analysesIDs } = row;
const { allReactionAnalyses } = context;
return allReactionAnalyses.filter((analysis) => analysesIDs.includes(analysis.id));
@@ -61,7 +61,7 @@ AnalysisOverlay.propTypes = {
function AnalysisVariationLink({ reaction, analysisID }) {
const { variations } = cloneDeep(reaction);
- const linkedVariations = variations.filter((row) => row.analyses.includes(analysisID)) ?? [];
+ const linkedVariations = variations.filter((row) => row.metadata.analyses.includes(analysisID)) ?? [];
if (linkedVariations.length === 0) {
return null;
@@ -94,7 +94,7 @@ AnalysesCellRenderer.propTypes = {
};
function AnalysesCellEditor({
- data: variationsRow,
+ data: row,
value: analysesIDs,
onValueChange,
stopEditing,
@@ -150,7 +150,7 @@ function AnalysesCellEditor({
const cellContent = (
- {`Link analyses to ${getVariationsRowName(reactionShortLabel, variationsRow.id)}`}
+ {`Link analyses to ${getVariationsRowName(reactionShortLabel, row.id)}`}
{analysesSelection}
diff --git a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents.js b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents.js
index 6b8002ec07..303289eeda 100644
--- a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents.js
+++ b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents.js
@@ -1,5 +1,6 @@
/* eslint-disable react/display-name */
import React, { useState, useEffect } from 'react';
+import Select from 'react-select';
import { AgGridReact } from 'ag-grid-react';
import {
Button, ButtonGroup, Modal, Form, OverlayTrigger, Tooltip
@@ -18,7 +19,7 @@ import {
} from 'src/utilities/UnitsConversion';
function RowToolsCellRenderer({
- data: variationsRow, context
+ data: row, context
}) {
const { reactionShortLabel, copyRow, removeRow } = context;
return (
@@ -26,14 +27,14 @@ function RowToolsCellRenderer({
{getVariationsRowName(reactionShortLabel, variationsRow.id)}}
+ overlay={{getVariationsRowName(reactionShortLabel, row.id)}}
>
-
+
-
@@ -52,13 +53,13 @@ RowToolsCellRenderer.propTypes = {
}).isRequired,
};
-function EquivalentParser({ data: variationsRow, oldValue: cellData, newValue }) {
+function EquivalentParser({ data: row, oldValue: cellData, newValue }) {
let equivalent = parseNumericString(newValue);
if (equivalent < 0) {
equivalent = 0;
}
// Adapt mass to updated equivalent.
- const referenceMaterial = getReferenceMaterial(variationsRow);
+ const referenceMaterial = getReferenceMaterial(row);
const referenceMol = getMolFromGram(referenceMaterial.mass.value, referenceMaterial);
const mass = getGramFromMol(referenceMol * equivalent, cellData);
@@ -106,7 +107,7 @@ function MaterialFormatter({ value: cellData, colDef }) {
}
function MaterialParser({
- data: variationsRow, oldValue: cellData, newValue, colDef, context
+ data: row, oldValue: cellData, newValue, colDef, context
}) {
const { currentEntry, displayUnit } = colDef.entryDefs;
let value = convertUnit(parseNumericString(newValue), displayUnit, cellData[currentEntry].unit);
@@ -129,7 +130,7 @@ function MaterialParser({
return updatedCellData;
}
- const referenceMaterial = getReferenceMaterial(variationsRow);
+ const referenceMaterial = getReferenceMaterial(row);
// Adapt equivalent to updated mass.
if ('equivalent' in updatedCellData) {
@@ -147,7 +148,7 @@ function MaterialParser({
}
function GasParser({
- data: variationsRow, oldValue: cellData, newValue, colDef
+ data: row, oldValue: cellData, newValue, colDef
}) {
const { currentEntry, displayUnit } = colDef.entryDefs;
let value = convertUnit(parseNumericString(newValue), displayUnit, cellData[currentEntry].unit);
@@ -170,11 +171,11 @@ function GasParser({
const amount = calculateGasMoles(vesselVolume, concentration, temperatureInKelvin);
const mass = getGramFromMol(amount, updatedCellData);
- const catalyst = getCatalystMaterial(variationsRow);
+ const catalyst = getCatalystMaterial(row);
const catalystAmount = catalyst?.amount.value ?? 0;
const turnoverNumber = calculateTON(amount, catalystAmount);
- const percentYield = computePercentYieldGas(amount, getFeedstockMaterial(variationsRow), vesselVolume);
+ const percentYield = computePercentYieldGas(amount, getFeedstockMaterial(row), vesselVolume);
updatedCellData = {
...updatedCellData,
@@ -204,7 +205,7 @@ function GasParser({
}
function FeedstockParser({
- data: variationsRow, oldValue: cellData, newValue, colDef
+ data: row, oldValue: cellData, newValue, colDef
}) {
const { currentEntry, displayUnit } = colDef.entryDefs;
let value = convertUnit(parseNumericString(newValue), displayUnit, cellData[currentEntry].unit);
@@ -253,7 +254,7 @@ function FeedstockParser({
return updatedCellData;
}
- const referenceMaterial = getReferenceMaterial(variationsRow);
+ const referenceMaterial = getReferenceMaterial(row);
const equivalent = computeEquivalent(updatedCellData, referenceMaterial);
return { ...updatedCellData, equivalent: { ...updatedCellData.equivalent, value: equivalent } };
@@ -275,7 +276,7 @@ function NoteCellRenderer(props) {
}
function NoteCellEditor({
- data: variationsRow,
+ data: row,
value,
onValueChange,
stopEditing,
@@ -296,7 +297,7 @@ function NoteCellEditor({
const cellContent = (
- {`Edit note for ${getVariationsRowName(reactionShortLabel, variationsRow.id)}`}
+ {`Edit note for ${getVariationsRowName(reactionShortLabel, row.id)}`}
{
+ onApply(currentColumns);
+ setShowModal(false);
+ };
+
+ const handleSelectChange = (key) => (selectedOptions) => {
+ const updatedCurrentColumns = { ...currentColumns };
+ updatedCurrentColumns[key] = selectedOptions ? selectedOptions.map((option) => option.value) : [];
+ setCurrentColumns(updatedCurrentColumns);
+ };
+
+ const splitCamelCase = (str) => str.replace(/([a-z])([A-Z])/g, '$1 $2');
+ const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1);
+
+ return (
+ <>
+ setShowModal(true)} className="mb-2">
+ Select Columns
+
+
+ setShowModal(false)}>
+
+ Column Selection
+
+
+ {Object.entries(availableColumns).map(([key, values]) => (
+
+
{toUpperCase(splitCamelCase(key))}
+
+ ))}
+
+
+
+ Apply
+
+
+
+ >
+ );
+}
+
export {
RowToolsCellRenderer,
EquivalentParser,
@@ -527,4 +579,5 @@ export {
NoteCellEditor,
MaterialOverlay,
MenuHeader,
+ ColumnSelection,
};
diff --git a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials.js b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials.js
index a6e8e26c17..29f0541112 100644
--- a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials.js
+++ b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials.js
@@ -28,21 +28,21 @@ function getGramFromMol(mol, material) {
return (mol / (material.aux.purity ?? 1.0)) * material.aux.molecularWeight;
}
-function getReferenceMaterial(variationsRow) {
- const variationsRowCopy = cloneDeep(variationsRow);
- const potentialReferenceMaterials = { ...variationsRowCopy.startingMaterials, ...variationsRowCopy.reactants };
+function getReferenceMaterial(row) {
+ const rowCopy = cloneDeep(row);
+ const potentialReferenceMaterials = { ...rowCopy.startingMaterials, ...rowCopy.reactants };
return Object.values(potentialReferenceMaterials).find((material) => material.aux?.isReference || false);
}
-function getCatalystMaterial(variationsRow) {
- const variationsRowCopy = cloneDeep(variationsRow);
- const potentialCatalystMaterials = { ...variationsRowCopy.startingMaterials, ...variationsRowCopy.reactants };
+function getCatalystMaterial(row) {
+ const rowCopy = cloneDeep(row);
+ const potentialCatalystMaterials = { ...rowCopy.startingMaterials, ...rowCopy.reactants };
return Object.values(potentialCatalystMaterials).find((material) => material.aux?.gasType === 'catalyst' || false);
}
-function getFeedstockMaterial(variationsRow) {
- const variationsRowCopy = cloneDeep(variationsRow);
- const potentialFeedstockMaterials = { ...variationsRowCopy.startingMaterials, ...variationsRowCopy.reactants };
+function getFeedstockMaterial(row) {
+ const rowCopy = cloneDeep(row);
+ const potentialFeedstockMaterials = { ...rowCopy.startingMaterials, ...rowCopy.reactants };
return Object.values(potentialFeedstockMaterials).find((material) => material.aux?.gasType === 'feedstock' || false);
}
@@ -74,19 +74,25 @@ function getReactionMaterials(reaction) {
}, {});
}
-function getReactionMaterialsIDs(reactionMaterials) {
- return Object.values(reactionMaterials).flat().map((material) => material.id);
+function getReactionMaterialsIDs(materials) {
+ return Object.fromEntries(
+ Object.entries(materials).map(([materialType, materialsOfType]) => [
+ materialType,
+ materialsOfType.map((material) => material.id.toString())
+ ])
+ );
}
-function getReactionMaterialsGasTypes(reactionMaterials) {
- return Object.values(reactionMaterials).flat().map((material) => material.gas_type);
+function getReactionMaterialsGasTypes(materials) {
+ return Object.values(materials).flat().map((material) => material.gas_type);
}
-function updateYields(variationsRow, reactionHasPolymers) {
- const updatedVariationsRow = cloneDeep(variationsRow);
- const referenceMaterial = getReferenceMaterial(updatedVariationsRow);
+function updateYields(row, reactionHasPolymers) {
+ const updatedRow = cloneDeep(row);
+ const referenceMaterial = getReferenceMaterial(updatedRow);
+ if (!referenceMaterial) { return updatedRow; }
- Object.values(updatedVariationsRow.products).forEach((productMaterial) => {
+ Object.values(updatedRow.products).forEach((productMaterial) => {
if (productMaterial.aux.gasType === 'gas') { return; }
productMaterial.yield.value = computePercentYield(
productMaterial,
@@ -95,21 +101,22 @@ function updateYields(variationsRow, reactionHasPolymers) {
);
});
- return updatedVariationsRow;
+ return updatedRow;
}
-function updateEquivalents(variationsRow) {
- const updatedVariationsRow = cloneDeep(variationsRow);
- const referenceMaterial = getReferenceMaterial(updatedVariationsRow);
+function updateEquivalents(row) {
+ const updatedRow = cloneDeep(row);
+ const referenceMaterial = getReferenceMaterial(updatedRow);
+ if (!referenceMaterial) { return updatedRow; }
['startingMaterials', 'reactants'].forEach((materialType) => {
- Object.values(updatedVariationsRow[materialType]).forEach((material) => {
+ Object.values(updatedRow[materialType]).forEach((material) => {
if (material.aux.isReference) { return; }
const updatedEquivalent = computeEquivalent(material, referenceMaterial);
material.equivalent.value = updatedEquivalent;
});
});
- return updatedVariationsRow;
+ return updatedRow;
}
function getMaterialEntries(materialType, gasType) {
@@ -190,7 +197,7 @@ function getMaterialData(material, materialType, gasMode = false, vesselVolume =
return materialData;
}
-function getMaterialColumnGroupChild(material, materialType, headerComponent, gasMode) {
+function getMaterialColumnGroupChild(material, materialType, gasMode) {
const materialCopy = cloneDeep(material);
let gasType = materialCopy.gas_type ?? 'off';
@@ -202,7 +209,7 @@ function getMaterialColumnGroupChild(material, materialType, headerComponent, ga
);
const entry = entries[0];
- let names = new Set([`ID: ${materialCopy.id.toString()}`]);
+ let names = new Set([`ID: ${materialCopy.id}`]);
['external_label', 'name', 'short_label', 'molecule_formula', 'molecule_iupac_name'].forEach((name) => {
if (materialCopy[name]) {
names.add(materialCopy[name]);
@@ -221,7 +228,7 @@ function getMaterialColumnGroupChild(material, materialType, headerComponent, ga
},
editable: (params) => cellIsEditable(params),
cellDataType: getCellDataType(entry, gasType),
- headerComponent,
+ headerComponent: MenuHeader,
headerComponentParams: {
names,
gasType,
@@ -229,40 +236,42 @@ function getMaterialColumnGroupChild(material, materialType, headerComponent, ga
};
}
-function removeObsoleteMaterialsFromVariations(variations, currentMaterials) {
- const updatedVariations = cloneDeep(variations);
- updatedVariations.forEach((row) => {
- Object.keys(materialTypes).forEach((materialType) => {
- Object.keys(row[materialType]).forEach((materialName) => {
- if (!currentMaterials[materialType].map((material) => material.id.toString()).includes(materialName)) {
- delete row[materialType][materialName];
- }
- });
- });
- });
- return updatedVariations;
+function resetColumnDefinitionsMaterials(columnDefinitions, materials, selectedColumns, gasMode) {
+ return Object.entries(materials).reduce((updatedDefinitions, [materialType, materialsOfType]) => {
+ const updatedMaterials = materialsOfType
+ .filter((material) => selectedColumns[materialType].includes(material.id.toString()))
+ .map((material) => getMaterialColumnGroupChild(material, materialType, gasMode));
+
+ return updateColumnDefinitions(
+ updatedDefinitions,
+ materialType,
+ 'children',
+ updatedMaterials
+ );
+ }, cloneDeep(columnDefinitions));
}
-function addMissingMaterialsToVariations(variations, currentMaterials, gasMode) {
- const updatedVariations = cloneDeep(variations);
- updatedVariations.forEach((row) => {
- Object.keys(materialTypes).forEach((materialType) => {
- currentMaterials[materialType].forEach((material) => {
- if (!(material.id in row[materialType])) {
- row[materialType][material.id] = getMaterialData(material, materialType, gasMode);
- }
- });
- });
+function removeObsoleteMaterialColumns(materials, columns) {
+ const updatedColumns = cloneDeep(columns);
+
+ Object.entries(materials).forEach(([materialType, materialsOfType]) => {
+ updatedColumns[materialType] = updatedColumns[materialType].filter(
+ (materialID) => materialsOfType.map((material) => material.id.toString()).includes(materialID.toString())
+ );
});
- return updatedVariations;
+
+ return updatedColumns;
}
-function updateVariationsGasTypes(variations, currentMaterials, gasMode) {
+function updateVariationsGasTypes(variations, materials, gasMode) {
const updatedVariations = cloneDeep(variations);
updatedVariations.forEach((row) => {
Object.keys(materialTypes).forEach((materialType) => {
- currentMaterials[materialType].forEach((material) => {
+ materials[materialType].forEach((material) => {
const currentGasType = material.gas_type ?? 'off';
+ if (!Object.prototype.hasOwnProperty.call(row[materialType], material.id.toString())) {
+ return;
+ }
if (currentGasType !== row[materialType][material.id].aux.gasType) {
row[materialType][material.id] = getMaterialData(material, materialType, gasMode);
}
@@ -272,54 +281,6 @@ function updateVariationsGasTypes(variations, currentMaterials, gasMode) {
return updatedVariations;
}
-function updateColumnDefinitionsMaterialTypes(columnDefinitions, currentMaterials, gasMode) {
- let updatedColumnDefinitions = cloneDeep(columnDefinitions);
-
- Object.entries(currentMaterials).forEach(([materialType, materials]) => {
- const updatedMaterials = materials.map(
- (material) => getMaterialColumnGroupChild(material, materialType, MenuHeader, gasMode)
- );
- updatedColumnDefinitions = updateColumnDefinitions(
- updatedColumnDefinitions,
- materialType,
- 'children',
- updatedMaterials
- );
- });
-
- return updatedColumnDefinitions;
-}
-
-function updateColumnDefinitionsMaterials(columnDefinitions, currentMaterials, headerComponent, gasMode) {
- const updatedColumnDefinitions = cloneDeep(columnDefinitions);
-
- Object.entries(currentMaterials).forEach(([materialType, materials]) => {
- const materialIDs = materials.map((material) => material.id.toString());
- const materialColumnGroup = updatedColumnDefinitions.find((columnGroup) => columnGroup.groupId === materialType);
-
- // Remove obsolete materials.
- materialColumnGroup.children = materialColumnGroup.children.filter((child) => {
- const childID = child.field.split('.').splice(1).join('.'); // Ensure that IDs that contain "." are handled correctly.
- return materialIDs.includes(childID);
- });
- // Add missing materials.
- materials.forEach((material) => {
- if (!materialColumnGroup.children.some((child) => child.field === `${materialType}.${material.id}`)) {
- materialColumnGroup.children.push(
- getMaterialColumnGroupChild(
- material,
- materialType,
- headerComponent,
- gasMode
- )
- );
- }
- });
- });
-
- return updatedColumnDefinitions;
-}
-
function updateVariationsRowOnReferenceMaterialChange(row, reactionHasPolymers) {
let updatedRow = cloneDeep(row);
updatedRow = updateEquivalents(updatedRow);
@@ -330,7 +291,7 @@ function updateVariationsRowOnReferenceMaterialChange(row, reactionHasPolymers)
function updateVariationsRowOnCatalystMaterialChange(row) {
const updatedRow = cloneDeep(row);
- const catalystMaterialAmount = getCatalystMaterial(updatedRow).amount.value;
+ const catalystMaterialAmount = getCatalystMaterial(updatedRow)?.amount.value;
Object.values(updatedRow.products).forEach((productMaterial) => {
if (productMaterial.aux.gasType === 'gas') {
@@ -369,19 +330,29 @@ function updateVariationsRowOnFeedstockMaterialChange(row) {
return updatedRow;
}
+function computeDerivedQuantitiesVariationsRow(row, reactionHasPolymers, gasMode) {
+ let updatedRow = row;
+ updatedRow = updateVariationsRowOnReferenceMaterialChange(row, reactionHasPolymers);
+ if (gasMode) {
+ updatedRow = updateVariationsRowOnCatalystMaterialChange(updatedRow);
+ updatedRow = updateVariationsRowOnFeedstockMaterialChange(updatedRow);
+ }
+
+ return updatedRow;
+}
+
export {
getMaterialColumnGroupChild,
getReactionMaterials,
getReactionMaterialsIDs,
getReactionMaterialsGasTypes,
getMaterialData,
- updateColumnDefinitionsMaterials,
- updateColumnDefinitionsMaterialTypes,
+ resetColumnDefinitionsMaterials,
updateVariationsRowOnReferenceMaterialChange,
updateVariationsRowOnCatalystMaterialChange,
updateVariationsRowOnFeedstockMaterialChange,
- removeObsoleteMaterialsFromVariations,
- addMissingMaterialsToVariations,
+ computeDerivedQuantitiesVariationsRow,
+ removeObsoleteMaterialColumns,
updateVariationsGasTypes,
getReferenceMaterial,
getCatalystMaterial,
diff --git a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsReducers.js b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsReducers.js
index da0d832f6c..2a01403f72 100644
--- a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsReducers.js
+++ b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsReducers.js
@@ -1,21 +1,31 @@
-import { MenuHeader } from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents';
import {
- updateColumnDefinitionsMaterials, updateColumnDefinitionsMaterialTypes
+ resetColumnDefinitionsMaterials
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials';
import {
- getCellDataType,
- updateColumnDefinitions
+ getCellDataType, updateColumnDefinitions, addMissingColumnDefinitions, removeObsoleteColumnDefinitions,
+ getColumnDefinitions
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils';
-function columnDefinitionsReducer(columnDefinitions, action) {
+export default function columnDefinitionsReducer(columnDefinitions, action) {
switch (action.type) {
- case 'update_material_set': {
- return updateColumnDefinitionsMaterials(
+ case 'remove_obsolete_materials': {
+ return removeObsoleteColumnDefinitions(
columnDefinitions,
- action.reactionMaterials,
- MenuHeader,
+ action.selectedColumns,
+ );
+ }
+ case 'apply_column_selection': {
+ let updatedColumnDefinitions = addMissingColumnDefinitions(
+ columnDefinitions,
+ action.selectedColumns,
+ action.materials,
action.gasMode
);
+ updatedColumnDefinitions = removeObsoleteColumnDefinitions(
+ updatedColumnDefinitions,
+ action.selectedColumns
+ );
+ return updatedColumnDefinitions;
}
case 'update_entry_defs': {
let updatedColumnDefinitions = updateColumnDefinitions(
@@ -33,24 +43,17 @@ function columnDefinitionsReducer(columnDefinitions, action) {
return updatedColumnDefinitions;
}
case 'toggle_gas_mode': {
- let updatedColumnDefinitions = updateColumnDefinitions(
- columnDefinitions,
- 'properties.duration',
- 'editable',
- !action.gasMode
- );
- updatedColumnDefinitions = updateColumnDefinitionsMaterialTypes(
- updatedColumnDefinitions,
- action.reactionMaterials,
+ return getColumnDefinitions(
+ action.selectedColumns,
+ action.materials,
action.gasMode
);
-
- return updatedColumnDefinitions;
}
case 'update_gas_type': {
- return updateColumnDefinitionsMaterialTypes(
+ return resetColumnDefinitionsMaterials(
columnDefinitions,
- action.reactionMaterials,
+ action.materials,
+ action.selectedColumns,
action.gasMode
);
}
@@ -59,7 +62,3 @@ function columnDefinitionsReducer(columnDefinitions, action) {
}
}
}
-
-export {
- columnDefinitionsReducer
-};
diff --git a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils.js b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils.js
index dae2aee38c..83ff40d9ed 100644
--- a/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils.js
+++ b/app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils.js
@@ -4,9 +4,14 @@ import { metPreConv as convertAmount } from 'src/utilities/metricPrefix';
import {
updateVariationsRowOnReferenceMaterialChange,
updateVariationsRowOnCatalystMaterialChange,
- updateVariationsRowOnFeedstockMaterialChange,
- getMaterialData
+ getMaterialData, getMaterialColumnGroupChild, computeDerivedQuantitiesVariationsRow
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials';
+import {
+ AnalysesCellRenderer, AnalysesCellEditor, getAnalysesOverlay, AnalysisOverlay
+} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsAnalyses';
+import {
+ NoteCellRenderer, NoteCellEditor, MenuHeader, RowToolsCellRenderer
+} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents';
const REACTION_VARIATIONS_TAB_KEY = 'reactionVariationsTab';
const temperatureUnits = ['°C', 'K', '°F'];
@@ -168,44 +173,78 @@ function getSequentialId(variations) {
return (ids.length === 0) ? 1 : Math.max(...ids) + 1;
}
-function createVariationsRow(reaction, variations, gasMode = false, vesselVolume = null) {
- const reactionCopy = cloneDeep(reaction);
- const { dispValue: durationValue = null, dispUnit: durationUnit = 'None' } = reactionCopy.durationDisplay ?? {};
- const { userText: temperatureValue = null, valueUnit: temperatureUnit = 'None' } = reactionCopy.temperature ?? {};
- const row = {
- id: getSequentialId(variations),
- properties: {
- temperature: {
+function getPropertyData(propertyType, durationValue, durationUnit, temperatureValue, temperatureUnit) {
+ switch (propertyType) {
+ case 'temperature':
+ return {
value: convertUnit(temperatureValue, temperatureUnit, getStandardUnits('temperature')[0]),
unit: getStandardUnits('temperature')[0]
- },
- duration: {
+ };
+ case 'duration':
+ return {
value: convertUnit(durationValue, durationUnit, getStandardUnits('duration')[0]),
unit: getStandardUnits('duration')[0],
- },
- },
- analyses: [],
- notes: '',
+ };
+ default:
+ return { value: null, unit: null };
+ }
+}
+
+function getMetaData(metadataType) {
+ switch (metadataType) {
+ case 'analyses':
+ return [];
+ case 'notes':
+ return '';
+ default:
+ return null;
+ }
+}
+
+function createVariationsRow({
+ materials,
+ selectedColumns,
+ variations,
+ reactionHasPolymers = false,
+ durationValue = null,
+ durationUnit = 'None',
+ temperatureValue = null,
+ temperatureUnit = 'None',
+ gasMode = false,
+ vesselVolume = null
+}) {
+ const row = {
+ id: getSequentialId(variations),
+ properties: Object.fromEntries(
+ selectedColumns.properties.map((propertyType) => [propertyType, getPropertyData(
+ propertyType,
+ durationValue,
+ durationUnit,
+ temperatureValue,
+ temperatureUnit
+ )])
+ ),
+ metadata: Object.fromEntries(
+ selectedColumns.metadata.map((metadataType) => [metadataType, getMetaData(metadataType)])
+ ),
};
- Object.entries(materialTypes).forEach(([materialType, { reactionAttributeName }]) => {
- row[materialType] = reactionCopy[reactionAttributeName].reduce((a, v) => (
- { ...a, [v.id]: getMaterialData(v, materialType, gasMode, vesselVolume) }), {});
+ Object.keys(materialTypes).forEach((materialType) => {
+ row[materialType] = {};
+ selectedColumns[materialType].forEach((materialID) => {
+ const material = materials[materialType].find((m) => m.id.toString() === materialID.toString());
+ row[materialType][materialID] = getMaterialData(material, materialType, gasMode, vesselVolume);
+ });
});
// Compute dependent values that aren't supplied by initial data.
- let updatedRow = updateVariationsRowOnReferenceMaterialChange(row, reactionCopy.has_polymers);
- if (gasMode) {
- updatedRow = updateVariationsRowOnCatalystMaterialChange(updatedRow);
- updatedRow = updateVariationsRowOnFeedstockMaterialChange(updatedRow);
- }
- return updatedRow;
+ return computeDerivedQuantitiesVariationsRow(row, reactionHasPolymers, gasMode);
}
function copyVariationsRow(row, variations) {
const copiedRow = cloneDeep(row);
copiedRow.id = getSequentialId(variations);
- copiedRow.analyses = [];
- copiedRow.notes = '';
+ copiedRow.metadata.notes = getMetaData('notes');
+ copiedRow.metadata.analyses = getMetaData('analyses');
return copiedRow;
}
@@ -239,6 +278,174 @@ function updateVariationsRow(row, field, value, reactionHasPolymers) {
return updatedRow;
}
+function addMissingColumnsToVariations({
+ materials,
+ selectedColumns,
+ variations,
+ reactionHasPolymers = false,
+ durationValue = null,
+ durationUnit = 'None',
+ temperatureValue = null,
+ temperatureUnit = 'None',
+ gasMode = false,
+ vesselVolume = null
+}) {
+ const updatedVariations = cloneDeep(variations);
+ updatedVariations.forEach((row) => {
+ Object.entries(selectedColumns).forEach(([columnGroupID, columnGroupChildIDs]) => {
+ columnGroupChildIDs.forEach((childID) => {
+ if (row[columnGroupID][childID]) { return; }
+
+ if (Object.keys(materialTypes).includes(columnGroupID)) {
+ const material = materials[columnGroupID].find((m) => m.id.toString() === childID.toString());
+ row[columnGroupID][childID] = getMaterialData(
+ material,
+ columnGroupID,
+ gasMode,
+ vesselVolume
+ );
+ }
+ if (columnGroupID === 'properties') {
+ row.properties[childID] = getPropertyData(
+ childID,
+ durationValue,
+ durationUnit,
+ temperatureValue,
+ temperatureUnit
+ );
+ }
+ if (columnGroupID === 'metadata') {
+ row.metadata[childID] = getMetaData(childID);
+ }
+ });
+ });
+ return computeDerivedQuantitiesVariationsRow(row, reactionHasPolymers, gasMode);
+ });
+
+ return updatedVariations;
+}
+
+function removeObsoleteColumnsFromVariations(variations, selectedColumns) {
+ const updatedVariations = cloneDeep(variations);
+ updatedVariations.forEach((row) => {
+ Object.entries(selectedColumns).forEach(([columnGroupID, columnGroupChildIDs]) => {
+ row[columnGroupID] = Object.fromEntries(
+ Object.entries(row[columnGroupID]).filter(([key, value]) => columnGroupChildIDs.includes(key))
+ );
+ });
+ });
+
+ return updatedVariations;
+}
+
+function getPropertyColumnGroupChild(propertyType, gasMode) {
+ switch (propertyType) {
+ case 'temperature':
+ return {
+ field: 'properties.temperature',
+ cellDataType: getCellDataType('temperature'),
+ entryDefs: {
+ currentEntry: 'temperature',
+ displayUnit: getStandardUnits('temperature')[0],
+ availableEntries: ['temperature']
+ },
+ headerComponent: MenuHeader,
+ headerComponentParams: {
+ names: ['T'],
+ }
+ };
+ case 'duration':
+ return {
+ field: 'properties.duration',
+ cellDataType: getCellDataType('duration'),
+ editable: !gasMode,
+ entryDefs: {
+ currentEntry: 'duration',
+ displayUnit: getStandardUnits('duration')[0],
+ availableEntries: ['duration']
+ },
+ headerComponent: MenuHeader,
+ headerComponentParams: {
+ names: ['t'],
+ }
+ };
+ default:
+ return {};
+ }
+}
+
+function getMetadataColumnGroupChild(metadataType) {
+ switch (metadataType) {
+ case 'notes':
+ return {
+ headerName: 'Notes',
+ field: 'metadata.notes',
+ cellRenderer: NoteCellRenderer,
+ sortable: false,
+ cellDataType: 'text',
+ cellEditor: NoteCellEditor,
+ };
+ case 'analyses':
+ return {
+ headerName: 'Analyses',
+ field: 'metadata.analyses',
+ tooltipValueGetter: getAnalysesOverlay,
+ tooltipComponent: AnalysisOverlay,
+ cellRenderer: AnalysesCellRenderer,
+ cellEditor: AnalysesCellEditor,
+ cellDataType: false,
+ sortable: false,
+ };
+ default:
+ return {};
+ }
+}
+
+function addMissingColumnDefinitions(columnDefinitions, selectedColumns, materials, gasMode) {
+ const updatedColumnDefinitions = cloneDeep(columnDefinitions);
+
+ Object.entries(selectedColumns).forEach(([columnGroupID, columnGroupChildIDs]) => {
+ const columnGroup = updatedColumnDefinitions.find(
+ (currentColumnGroup) => currentColumnGroup.groupId === columnGroupID
+ );
+ columnGroupChildIDs.forEach((childID) => {
+ if (columnGroup.children.some((child) => child.field === `${columnGroupID}.${childID}`)) {
+ return;
+ }
+
+ if (Object.keys(materialTypes).includes(columnGroupID)) {
+ const material = materials[columnGroupID].find((m) => m.id.toString() === childID.toString());
+ columnGroup.children.push(getMaterialColumnGroupChild(material, columnGroupID, gasMode));
+ }
+ if (columnGroupID === 'properties') {
+ columnGroup.children.push(getPropertyColumnGroupChild(childID, gasMode));
+ }
+ if (columnGroupID === 'metadata') {
+ columnGroup.children.push(getMetadataColumnGroupChild(childID));
+ }
+ });
+ });
+
+ return updatedColumnDefinitions;
+}
+
+function removeObsoleteColumnDefinitions(columnDefinitions, selectedColumns) {
+ const updatedColumnDefinitions = cloneDeep(columnDefinitions);
+
+ Object.entries(selectedColumns).forEach(([columnGroupID, columnGroupChildIDs]) => {
+ const columnGroup = updatedColumnDefinitions.find(
+ (currentColumnGroup) => currentColumnGroup.groupId === columnGroupID
+ );
+
+ columnGroup.children = columnGroup.children.filter((child) => {
+ const childID = child.field.split('.').splice(1).join('.'); // Ensure that IDs that contain "." are handled correctly.
+ return columnGroupChildIDs.includes(childID);
+ });
+ });
+
+ return updatedColumnDefinitions;
+}
+
function updateColumnDefinitions(columnDefinitions, field, property, newValue) {
const updatedColumnDefinitions = cloneDeep(columnDefinitions);
@@ -263,6 +470,56 @@ function updateColumnDefinitions(columnDefinitions, field, property, newValue) {
return updatedColumnDefinitions;
}
+function getColumnDefinitions(selectedColumns, materials, gasMode) {
+ return [
+ {
+ headerName: 'Tools',
+ cellRenderer: RowToolsCellRenderer,
+ lockPosition: 'left',
+ sortable: false,
+ maxWidth: 100,
+ cellDataType: false,
+ },
+ {
+ headerName: 'Metadata',
+ groupId: 'metadata',
+ marryChildren: true,
+ children: selectedColumns.metadata.map((entry) => getMetadataColumnGroupChild(entry))
+ },
+ {
+ headerName: 'Properties',
+ groupId: 'properties',
+ marryChildren: true,
+ children: selectedColumns.properties.map((entry) => getPropertyColumnGroupChild(entry, gasMode))
+ },
+ ].concat(
+ Object.entries(materialTypes).map(([materialType, { label }]) => ({
+ headerName: label,
+ groupId: materialType,
+ marryChildren: true,
+ children: selectedColumns[materialType].map(
+ (materialID) => getMaterialColumnGroupChild(
+ materials[materialType].find((material) => material.id.toString() === materialID),
+ materialType,
+ gasMode
+ )
+ )
+ }))
+ );
+}
+
+function getVariationsColumns(variations) {
+ const variationsRow = variations[0];
+ const materialColumns = Object.entries(materialTypes).reduce((materialsByType, [materialType]) => {
+ materialsByType[materialType] = Object.keys(variationsRow ? variationsRow[materialType] : []);
+ return materialsByType;
+ }, {});
+ const propertyColumns = Object.keys(variationsRow ? variationsRow.properties : {});
+ const metadataColumns = Object.keys(variationsRow ? variationsRow.metadata : {});
+
+ return { ...materialColumns, properties: propertyColumns, metadata: metadataColumns };
+}
+
export {
massUnits,
volumeUnits,
@@ -274,12 +531,20 @@ export {
convertUnit,
materialTypes,
getVariationsRowName,
+ getVariationsColumns,
createVariationsRow,
copyVariationsRow,
updateVariationsRow,
updateColumnDefinitions,
+ getColumnDefinitions,
getCellDataType,
getUserFacingUnit,
getStandardValue,
+ addMissingColumnsToVariations,
+ removeObsoleteColumnsFromVariations,
+ addMissingColumnDefinitions,
+ removeObsoleteColumnDefinitions,
+ getMetadataColumnGroupChild,
+ getPropertyColumnGroupChild,
REACTION_VARIATIONS_TAB_KEY
};
diff --git a/db/migrate/20250304140809_add_metadata_to_reaction_variations.rb b/db/migrate/20250304140809_add_metadata_to_reaction_variations.rb
new file mode 100644
index 0000000000..d1ec56af8e
--- /dev/null
+++ b/db/migrate/20250304140809_add_metadata_to_reaction_variations.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class AddMetadataToReactionVariations < ActiveRecord::Migration[6.1]
+ def up
+ Reaction.where.not('variations = ?', '[]').find_each do |reaction|
+ variations = reaction.variations
+ variations.each do |variation|
+ variation['metadata'] ||= {}
+
+ if variation['notes']
+ variation['metadata']['notes'] = variation.delete('notes')
+ end
+ if variation['analyses']
+ variation['metadata']['analyses'] = variation.delete('analyses')
+ end
+ end
+ reaction.update_column(:variations, variations)
+ end
+ end
+
+ def down
+ Reaction.where.not('variations = ?', '[]').find_each do |reaction|
+ variations = reaction.variations
+ variations.each do |variation|
+ next unless variation['metadata']
+
+ variation['notes'] ||= variation['metadata'].delete('notes') || ''
+ variation['analyses'] ||= variation['metadata'].delete('analyses') || []
+ variation.delete('metadata')
+ end
+ reaction.update_column(:variations, variations)
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c5f8c6b56d..7422742009 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2025_02_18_161800) do
+ActiveRecord::Schema.define(version: 2025_03_04_140809) do
# These are extensions that must be enabled in order to support this database
enable_extension "hstore"
diff --git a/spec/factories/reactions.rb b/spec/factories/reactions.rb
index dbd8200e6e..ff587e52fe 100644
--- a/spec/factories/reactions.rb
+++ b/spec/factories/reactions.rb
@@ -96,8 +96,10 @@ def get_aux(sample, material_type)
{
id: i.to_s,
- analyses: [],
- notes: '',
+ metadata: {
+ analyses: [],
+ notes: '',
+ },
properties: {
duration: { unit: 'Hour(s)', value: '42' },
temperature: { unit: '°C', value: '42' },
diff --git a/spec/javascripts/helper/reactionVariationsHelpers.js b/spec/javascripts/helper/reactionVariationsHelpers.js
index e31e1cb55a..3bd112d123 100644
--- a/spec/javascripts/helper/reactionVariationsHelpers.js
+++ b/spec/javascripts/helper/reactionVariationsHelpers.js
@@ -3,18 +3,40 @@ import SampleFactory from 'factories/SampleFactory';
import {
createVariationsRow,
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils';
+import {
+ getReactionMaterials,
+ getReactionMaterialsIDs
+} from '../../../app/javascript/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials';
async function setUpMaterial() {
return SampleFactory.build('SampleFactory.water_100g');
}
+
+function getSelectedColumns(materialIDs) {
+ return { ...materialIDs, properties: ['duration', 'temperature'], metadata: ['analyses', 'notes'] };
+}
+
async function setUpReaction() {
const reaction = await ReactionFactory.build('ReactionFactory.water+water=>water+water');
reaction.starting_materials[0].reference = true;
reaction.reactants = [await setUpMaterial()];
+ const materials = getReactionMaterials(reaction);
+ const materialIDs = getReactionMaterialsIDs(materials);
+
const variations = [];
for (let id = 0; id < 3; id++) {
- variations.push(createVariationsRow(reaction, variations));
+ variations.push(createVariationsRow(
+ {
+ materials,
+ selectedColumns: getSelectedColumns(materialIDs),
+ variations,
+ durationValue: '',
+ durationUnit: 'Hour(s)',
+ temperatureValue: '',
+ temperatureUnit: '°C',
+ }
+ ));
}
reaction.variations = variations;
@@ -38,9 +60,20 @@ async function setUpGaseousReaction() {
reaction.products[0].amount_unit = 'mol';
reaction.products[0].amount_value = 1;
+ const materials = getReactionMaterials(reaction);
+ const materialIDs = getReactionMaterialsIDs(materials);
+
const variations = [];
for (let id = 0; id < 3; id++) {
- variations.push(createVariationsRow(reaction, variations, true, 10));
+ variations.push(createVariationsRow(
+ {
+ materials,
+ selectedColumns: getSelectedColumns(materialIDs),
+ variations,
+ gasMode: true,
+ vesselVolume: 10
+ }
+ ));
}
reaction.variations = variations;
@@ -65,5 +98,10 @@ function getColumnDefinitionsMaterialIDs(columnDefinitions, materialType) {
}
export {
- setUpMaterial, setUpReaction, setUpGaseousReaction, getColumnGroupChild, getColumnDefinitionsMaterialIDs
+ setUpMaterial,
+ setUpReaction,
+ setUpGaseousReaction,
+ getColumnGroupChild,
+ getColumnDefinitionsMaterialIDs,
+ getSelectedColumns
};
diff --git a/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsAnalyses.spec.js b/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsAnalyses.spec.js
index a2505a7a06..eb3e6b5a8f 100644
--- a/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsAnalyses.spec.js
+++ b/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsAnalyses.spec.js
@@ -28,30 +28,30 @@ describe('ReactionVariationsAnalyses', async () => {
});
it('when no update is necessary', async () => {
let { variations } = reaction;
- variations[0].analyses = [analysisFoo.id];
- variations[1].analyses = [analysisBar.id];
+ variations[0].metadata.analyses = [analysisFoo.id];
+ variations[1].metadata.analyses = [analysisBar.id];
expect(updateAnalyses(variations, getReactionAnalyses(reaction))).toEqual(variations);
});
it('when analysis is removed', async () => {
let { variations } = reaction;
- variations[0].analyses = [analysisFoo.id];
- expect(updateAnalyses(variations, getReactionAnalyses(reaction))[0].analyses).toEqual([analysisFoo.id]);
+ variations[0].metadata.analyses = [analysisFoo.id];
+ expect(updateAnalyses(variations, getReactionAnalyses(reaction))[0].metadata.analyses).toEqual([analysisFoo.id]);
reaction.container.children[0].children = reaction.container.children[0].children.filter((child) => child.id !== analysisFoo.id);
- expect(updateAnalyses(variations, getReactionAnalyses(reaction))[0].analyses).toEqual([]);
+ expect(updateAnalyses(variations, getReactionAnalyses(reaction))[0].metadata.analyses).toEqual([]);
});
it('when analysis is marked as deleted', async () => {
let { variations } = reaction;
- variations[1].analyses = [analysisBar.id];
- expect(updateAnalyses(variations, getReactionAnalyses(reaction))[1].analyses).toEqual([analysisBar.id]);
+ variations[1].metadata.analyses = [analysisBar.id];
+ expect(updateAnalyses(variations, getReactionAnalyses(reaction))[1].metadata.analyses).toEqual([analysisBar.id]);
analysisBar.is_deleted = true;
- expect(updateAnalyses(variations, getReactionAnalyses(reaction))[1].analyses).toEqual([]);
+ expect(updateAnalyses(variations, getReactionAnalyses(reaction))[1].metadata.analyses).toEqual([]);
});
it('when analysis is new', async () => {
let { variations } = reaction;
- variations[1].analyses = [analysisBar.id];
- expect(updateAnalyses(variations, getReactionAnalyses(reaction))[1].analyses).toEqual([analysisBar.id]);
+ variations[1].metadata.analyses = [analysisBar.id];
+ expect(updateAnalyses(variations, getReactionAnalyses(reaction))[1].metadata.analyses).toEqual([analysisBar.id]);
analysisBar.is_new = true;
- expect(updateAnalyses(variations, getReactionAnalyses(reaction))[1].analyses).toEqual([]);
+ expect(updateAnalyses(variations, getReactionAnalyses(reaction))[1].metadata.analyses).toEqual([]);
});
});
});
diff --git a/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials.spec.js b/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials.spec.js
index 4890627308..1223f71386 100644
--- a/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials.spec.js
+++ b/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials.spec.js
@@ -1,54 +1,22 @@
import expect from 'expect';
import {
- getReactionMaterials, updateVariationsRowOnReferenceMaterialChange,
- removeObsoleteMaterialsFromVariations, addMissingMaterialsToVariations,
- updateColumnDefinitionsMaterials, updateVariationsRowOnCatalystMaterialChange,
- getMaterialColumnGroupChild, getReactionMaterialsIDs, updateColumnDefinitionsMaterialTypes,
- getReactionMaterialsGasTypes, updateVariationsGasTypes, cellIsEditable
+ getReactionMaterials, updateVariationsRowOnReferenceMaterialChange, removeObsoleteMaterialColumns,
+ updateVariationsRowOnCatalystMaterialChange, getMaterialColumnGroupChild, getReactionMaterialsIDs,
+ resetColumnDefinitionsMaterials, getReactionMaterialsGasTypes, updateVariationsGasTypes, cellIsEditable
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials';
import {
EquivalentParser
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsComponents';
import {
- setUpMaterial, setUpReaction, setUpGaseousReaction, getColumnDefinitionsMaterialIDs, getColumnGroupChild
+ setUpReaction, setUpGaseousReaction, getColumnDefinitionsMaterialIDs, getColumnGroupChild
} from 'helper/reactionVariationsHelpers';
-import { materialTypes } from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils';
+import {
+ materialTypes,
+
+} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils';
import { cloneDeep } from 'lodash';
describe('ReactionVariationsMaterials', () => {
- it('removes obsolete materials', async () => {
- const reaction = await setUpReaction();
- const productIDs = reaction.products.map((product) => product.id);
- reaction.variations.forEach((variation) => {
- expect(Object.keys(variation.products)).toEqual(productIDs);
- });
-
- reaction.products.pop();
- const updatedProductIDs = reaction.products.map((product) => product.id);
- const currentMaterials = getReactionMaterials(reaction);
- const updatedVariations = removeObsoleteMaterialsFromVariations(reaction.variations, currentMaterials);
- updatedVariations
- .forEach((variation) => {
- expect(Object.keys(variation.products)).toEqual(updatedProductIDs);
- });
- });
- it('adds missing materials', async () => {
- const reaction = await setUpReaction();
- const material = await setUpMaterial();
- const startingMaterialIDs = reaction.starting_materials.map((startingMaterial) => startingMaterial.id);
- reaction.variations.forEach((variation) => {
- expect(Object.keys(variation.startingMaterials)).toEqual(startingMaterialIDs);
- });
-
- reaction.starting_materials.push(material);
- const updatedStartingMaterialIDs = reaction.starting_materials.map((startingMaterial) => startingMaterial.id);
- const currentMaterials = getReactionMaterials(reaction);
- const updatedVariations = addMissingMaterialsToVariations(reaction.variations, currentMaterials, false);
- updatedVariations
- .forEach((variation) => {
- expect(Object.keys(variation.startingMaterials)).toEqual(updatedStartingMaterialIDs);
- });
- });
it('updates yield when product mass changes', async () => {
const reaction = await setUpReaction();
const productID = reaction.products[0].id;
@@ -88,31 +56,12 @@ describe('ReactionVariationsMaterials', () => {
newValue: Number(-42).toString()
}).mass.value).toBe(0);
});
- it('removes obsolete materials from column definitions', async () => {
- const reaction = await setUpReaction();
- const reactionMaterials = getReactionMaterials(reaction);
- const columnDefinitions = Object.entries(reactionMaterials).map(([materialType, materials]) => ({
- groupId: materialType,
- children: materials.map((material) => getMaterialColumnGroupChild(material, materialType, null, false))
- }));
-
- const startingMaterialIDs = reactionMaterials.startingMaterials.map((material) => material.id);
- expect(getColumnDefinitionsMaterialIDs(columnDefinitions, 'startingMaterials')).toEqual(startingMaterialIDs);
-
- reactionMaterials.startingMaterials.pop();
- const updatedStartingMaterialIDs = reactionMaterials.startingMaterials.map((material) => material.id);
- const updatedColumnDefinitions = updateColumnDefinitionsMaterials(columnDefinitions, reactionMaterials, null, false);
- expect(getColumnDefinitionsMaterialIDs(
- updatedColumnDefinitions,
- 'startingMaterials'
- )).toEqual(updatedStartingMaterialIDs);
- });
it('retrieves reaction material IDs', async () => {
const reaction = await setUpReaction();
const reactionMaterials = getReactionMaterials(reaction);
const reactionMaterialsIDs = getReactionMaterialsIDs(reactionMaterials);
- expect(Array.isArray(reactionMaterialsIDs)).toBe(true);
- expect(new Set(reactionMaterialsIDs).size).toBe(5);
+ expect(typeof reactionMaterialsIDs).toBe('object');
+ expect(Object.values(reactionMaterialsIDs).flat().length).toEqual(5);
});
it('retrieves reaction material gas types', async () => {
const reaction = await setUpGaseousReaction();
@@ -150,7 +99,7 @@ describe('ReactionVariationsMaterials', () => {
const reactionMaterials = getReactionMaterials(reaction);
const columnDefinitions = Object.entries(reactionMaterials).map(([materialType, materials]) => ({
groupId: materialType,
- children: materials.map((material) => getMaterialColumnGroupChild(material, materialType, null, false))
+ children: materials.map((material) => getMaterialColumnGroupChild(material, materialType, false))
}));
Object.keys(materialTypes).forEach((materialType) => {
@@ -171,9 +120,10 @@ describe('ReactionVariationsMaterials', () => {
});
});
- const updatedColumnDefinitions = updateColumnDefinitionsMaterialTypes(
+ const updatedColumnDefinitions = resetColumnDefinitionsMaterials(
columnDefinitions,
reactionMaterials,
+ getReactionMaterialsIDs(reactionMaterials),
true
);
@@ -232,7 +182,7 @@ describe('ReactionVariationsMaterials', () => {
expect(updatedVariationsRow.products[productID].turnoverNumber.value).toBe(initialTurnoverNumber * 2);
expect(updatedVariationsRow.products[productID].turnoverFrequency.value).toBe(initialTurnoverFrequency * 2);
});
- it("initializes gas product yield", async () => {
+ it('initializes gas product yield', async () => {
const reaction = await setUpGaseousReaction();
const productID = reaction.products[0].id;
const variationsRow = reaction.variations[0];
@@ -240,4 +190,16 @@ describe('ReactionVariationsMaterials', () => {
expect(initialYield).not.toBe(null);
});
+ it('removes obsolete material columns', async () => {
+ const reaction = await setUpReaction();
+ const materials = getReactionMaterials(reaction);
+ const columns = getReactionMaterialsIDs(materials);
+ materials.products.pop();
+
+ expect(columns.products.length).toEqual(2);
+
+ const updatedColumns = removeObsoleteMaterialColumns(materials, columns);
+
+ expect(updatedColumns.products.length).toEqual(1);
+ });
});
diff --git a/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils.spec.js b/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils.spec.js
index 23e4422ede..28224d14e8 100644
--- a/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils.spec.js
+++ b/spec/javascripts/packs/src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils.spec.js
@@ -1,11 +1,18 @@
import expect from 'expect';
import {
- convertUnit, createVariationsRow, copyVariationsRow, updateVariationsRow, updateColumnDefinitions
+ convertUnit, createVariationsRow, copyVariationsRow, updateVariationsRow, updateColumnDefinitions,
+ removeObsoleteColumnsFromVariations, removeObsoleteColumnDefinitions, addMissingColumnDefinitions,
+ addMissingColumnsToVariations, getVariationsColumns
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsUtils';
import {
- getReactionMaterials, getMaterialColumnGroupChild
+ getReactionMaterials, getMaterialColumnGroupChild, getReactionMaterialsIDs
} from 'src/apps/mydb/elements/details/reactions/variationsTab/ReactionVariationsMaterials';
-import { setUpReaction, getColumnGroupChild } from 'helper/reactionVariationsHelpers';
+import {
+ setUpReaction,
+ getColumnGroupChild,
+ getSelectedColumns,
+ getColumnDefinitionsMaterialIDs
+} from 'helper/reactionVariationsHelpers';
describe('ReactionVariationsUtils', () => {
it('converts units', () => {
@@ -18,33 +25,136 @@ describe('ReactionVariationsUtils', () => {
expect(convertUnit(1, 'Second(s)', 'Minute(s)')).toBeCloseTo(0.0167, 0.00001);
expect(convertUnit(1, 'Minute(s)', 'Second(s)')).toBe(60);
});
- it('creates a row in the variations table', async () => {
+ it('creates a row in the variations table with selected materials', async () => {
const reaction = await setUpReaction();
- const row = createVariationsRow(reaction, reaction.variations);
+
+ expect(Object.keys(reaction.variations[0].products).length).toBe(2);
+
+ const materials = getReactionMaterials(reaction);
+ const materialIDs = getReactionMaterialsIDs(materials);
+ materialIDs.products.pop();
+
+ const row = createVariationsRow(
+ {
+ materials,
+ selectedColumns: getSelectedColumns(materialIDs),
+ variations: reaction.variations,
+ durationValue: '',
+ durationUnit: 'Hour(s)',
+ temperatureValue: '',
+ temperatureUnit: '°C'
+ }
+ );
+
+ expect(Object.keys(row.products).length).toBe(1);
+
const nonReferenceStartingMaterial = Object.values(row.startingMaterials).find(
(material) => !material.aux.isReference
);
const reactant = Object.values(row.reactants)[0];
expect(row.id).toBe(4);
- expect(row.analyses).toEqual([]);
- expect(row.notes).toEqual('');
+ expect(row.metadata.analyses).toEqual([]);
+ expect(row.metadata.notes).toEqual('');
expect(row.properties).toEqual({
temperature: { value: '', unit: '°C' },
duration: { value: NaN, unit: 'Second(s)' },
});
- expect(Object.values(row.products).map((product) => product.yield.value)).toEqual([100, 100]);
+ expect(Object.values(row.products).map((product) => product.yield.value)).toEqual([100]);
expect(nonReferenceStartingMaterial.equivalent.value).toBe(1);
expect(reactant.equivalent.value).toBe(1);
});
+ it('adds missing columns to variations', async () => {
+ const reaction = await setUpReaction();
+ const { variations } = reaction;
+
+ let updatedVariations = removeObsoleteColumnsFromVariations(variations, { properties: [], metadata: [] });
+
+ updatedVariations = addMissingColumnsToVariations({
+ materials: {},
+ selectedColumns: { properties: ['temperature', 'duration'], metadata: ['notes', 'analyses'] },
+ variations: updatedVariations,
+ durationValue: 42,
+ durationUnit: 'Second(s)',
+ temperatureValue: 42,
+ temperatureUnit: '°C',
+ });
+
+ expect(updatedVariations[0].properties.duration.value).toBe(42);
+ expect(updatedVariations[0].properties.temperature.value).toBe(42);
+ expect(updatedVariations[0].metadata.notes).toBe('');
+ expect(Array.isArray(updatedVariations[0].metadata.analyses));
+ expect(updatedVariations[0].metadata.analyses.length === 0);
+ });
+ it('removes obsolete materials from variations', async () => {
+ const reaction = await setUpReaction();
+ const productIDs = reaction.products.map((product) => product.id);
+ reaction.variations.forEach((variation) => {
+ expect(Object.keys(variation.products)).toEqual(productIDs);
+ });
+
+ reaction.products.pop();
+ const updatedProductIDs = reaction.products.map((product) => product.id);
+ const materialIDs = getReactionMaterialsIDs(getReactionMaterials(reaction));
+ const updatedVariations = removeObsoleteColumnsFromVariations(reaction.variations, materialIDs);
+ updatedVariations
+ .forEach((variation) => {
+ expect(Object.keys(variation.products)).toEqual(updatedProductIDs);
+ });
+ });
+ it('adds missing column definitions', async () => {
+ const columnDefinitions = [
+ {
+ groupId: 'metadata',
+ children: []
+ },
+ {
+ groupId: 'properties',
+ children: []
+ },
+ ];
+
+ const columns = { properties: ['temperature', 'duration'], metadata: ['notes', 'analyses'] };
+ const updatedColumnDefinitions = addMissingColumnDefinitions(columnDefinitions, columns, {}, false);
+
+ const metadataChildren = updatedColumnDefinitions.find((entry) => entry.groupId === 'metadata').children;
+ expect(metadataChildren.some((child) => child.field === 'metadata.analyses'));
+ expect(metadataChildren.some((child) => child.field === 'metadata.notes'));
+
+ const propertiesChildren = updatedColumnDefinitions.find((entry) => entry.groupId === 'properties').children;
+ expect(propertiesChildren.some((child) => child.field === 'properties.temperature'));
+ expect(propertiesChildren.some((child) => child.field === 'properties.duration'));
+ });
+ it('removes obsolete materials from column definitions', async () => {
+ const reaction = await setUpReaction();
+ const reactionMaterials = getReactionMaterials(reaction);
+ const columnDefinitions = Object.entries(reactionMaterials).map(([materialType, materials]) => ({
+ groupId: materialType,
+ children: materials.map((material) => getMaterialColumnGroupChild(material, materialType, false))
+ }));
+
+ const startingMaterialIDs = reactionMaterials.startingMaterials.map((material) => material.id);
+ expect(getColumnDefinitionsMaterialIDs(columnDefinitions, 'startingMaterials')).toEqual(startingMaterialIDs);
+
+ reactionMaterials.startingMaterials.pop();
+ const updatedStartingMaterialIDs = reactionMaterials.startingMaterials.map((material) => material.id);
+ const updatedColumnDefinitions = removeObsoleteColumnDefinitions(
+ columnDefinitions,
+ getReactionMaterialsIDs(reactionMaterials)
+ );
+ expect(getColumnDefinitionsMaterialIDs(
+ updatedColumnDefinitions,
+ 'startingMaterials'
+ )).toEqual(updatedStartingMaterialIDs);
+ });
it('copies a row in the variations table', async () => {
const reaction = await setUpReaction();
const row = reaction.variations[0];
- row.analyses = [42];
- row.notes = 'foo bar baz';
+ row.metadata.analyses = [42];
+ row.metadata.notes = 'foo bar baz';
const copiedRow = copyVariationsRow(row, reaction.variations);
expect(copiedRow.id).toBeGreaterThan(row.id);
- expect(copiedRow.analyses).toEqual([]);
- expect(copiedRow.notes).toEqual('');
+ expect(copiedRow.metadata.analyses).toEqual([]);
+ expect(copiedRow.metadata.notes).toEqual('');
});
it('updates a row in the variations table', async () => {
const reaction = await setUpReaction();
@@ -74,7 +184,7 @@ describe('ReactionVariationsUtils', () => {
const field = `startingMaterials.${reactionMaterials.startingMaterials[0].id}`;
const columnDefinitions = Object.entries(reactionMaterials).map(([materialType, materials]) => ({
groupId: materialType,
- children: materials.map((material) => getMaterialColumnGroupChild(material, materialType, null, false))
+ children: materials.map((material) => getMaterialColumnGroupChild(material, materialType, false))
}));
expect(getColumnGroupChild(columnDefinitions, 'startingMaterials', field).cellDataType).toBe('material');
const updatedColumnDefinitions = updateColumnDefinitions(
@@ -85,4 +195,18 @@ describe('ReactionVariationsUtils', () => {
);
expect(getColumnGroupChild(updatedColumnDefinitions, 'startingMaterials', field).cellDataType).toBe('equivalent');
});
+ it('gets column names from variations table', async () => {
+ const reaction = await setUpReaction();
+ const reactionMaterialsIDs = getReactionMaterialsIDs(getReactionMaterials(reaction));
+ const variationsColumns = getVariationsColumns(reaction.variations);
+
+ expect(variationsColumns.startingMaterials).toEqual(reactionMaterialsIDs.startingMaterials);
+ expect(variationsColumns.properties).toEqual(expect.arrayContaining(['duration', 'temperature']));
+ expect(variationsColumns.metadata).toEqual(expect.arrayContaining(['notes', 'analyses']));
+
+ const emptyVariationsColumns = getVariationsColumns([]);
+ expect(emptyVariationsColumns.startingMaterials).toEqual([]);
+ expect(emptyVariationsColumns.properties).toEqual([]);
+ expect(emptyVariationsColumns.metadata).toEqual([]);
+ });
});