in packages/scesim-editor/src/table/TestScenarioTable.tsx [60:926]
function TestScenarioTable({
tableData,
scrollableParentRef,
}: {
tableData: SceSim__simulationType | SceSim__backgroundType;
scrollableParentRef: React.RefObject<HTMLElement>;
}) {
enum TestScenarioTableColumnHeaderGroup {
EXPECT = "expect-header",
GIVEN = "given-header",
}
enum TestScenarioTableColumnInstanceGroup {
EXPECT = "expect-instance",
GIVEN = "given-instance",
}
enum TestScenarioTableColumnFieldGroup {
EXPECT = "expect",
GIVEN = "given",
OTHER = "other",
}
type ROWTYPE = any; // FIXME: https://github.com/apache/incubator-kie-issues/issues/169
const { i18n } = useTestScenarioEditorI18n();
const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi();
const settingsModel = useTestScenarioEditorStore((state) => state.scesim.model.ScenarioSimulationModel.settings);
const testScenarioType = settingsModel.type?.__$$text.toUpperCase();
/** BACKGROUND TABLE MANAGMENT */
const isBackground = useMemo(() => {
return "BackgroundData" in tableData.scesimData;
}, [tableData]);
const columnIndexStart = useMemo(() => {
return isBackground ? 0 : 1;
}, [isBackground]);
const retrieveRowsData = useCallback(
(rowData: SceSim__backgroundDatasType | SceSim__scenariosType) => {
if (isBackground) {
return (rowData as SceSim__backgroundDatasType).BackgroundData;
} else {
return (rowData as SceSim__scenariosType).Scenario;
}
},
[isBackground]
);
/** TABLE COLUMNS AND ROWS POPULATION */
/* It determines the Data Type Label based on the given Data Type.
In case of RULE Scenario, the Data Type is a FQCN (eg. java.lang.String). So, the label will take the class name only
In any case, if the Data Type ends with a "Void", that means the type has not been assigned, so we show Undefined. */
const determineDataTypeLabel = useCallback(
(dataType: string, genericTypes: string[]) => {
let dataTypeLabel = dataType;
if (testScenarioType === "RULE") {
dataTypeLabel = dataTypeLabel.split(".").pop() ?? dataTypeLabel;
}
/* List Type */
if (genericTypes.length == 1) {
dataTypeLabel = testScenarioType === "RULE" ? `${dataTypeLabel}<${genericTypes[0]}>` : `${genericTypes[0]}[]`;
}
/* Map Type */
if (testScenarioType === "RULE" && genericTypes.length == 2) {
dataTypeLabel = `${dataTypeLabel}<${genericTypes[0]},${genericTypes[1]}>`;
}
return !dataTypeLabel || dataTypeLabel.endsWith("Void") ? "<Undefined>" : dataTypeLabel;
},
[testScenarioType]
);
/* It updates any column width change in the Model */
const setColumnWidth = useCallback(
(inputIndex: number) => (newWidthAction: React.SetStateAction<number | undefined>) => {
testScenarioEditorStoreApi.setState((state) => {
const factMappings = isBackground
? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping!
: state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!;
const oldWidth = factMappings[inputIndex].columnWidth?.__$$text;
const newWidth = typeof newWidthAction === "function" ? newWidthAction(oldWidth) : newWidthAction;
updateColumnWidth({
factMappings: factMappings,
columnIndex: inputIndex,
newWidth: newWidth,
oldWidth: oldWidth,
});
});
},
[isBackground, testScenarioEditorStoreApi]
);
/* It determines the column data based on the given FactMapping (Scesim column representation).
In case of the Description column, the behavior is slightly different (column dimension, label and no datatype label)
*/
const generateColumnFromFactMapping = useCallback(
(factMapping: SceSim__FactMappingType, factMappingIndex: number, isDescriptionColumn?: boolean) => {
return {
accessor: factMapping.expressionIdentifier.name!.__$$text,
dataType:
isDescriptionColumn || factMapping.factMappingValueType?.__$$text === "EXPRESSION"
? undefined
: determineDataTypeLabel(
factMapping.className.__$$text,
factMapping.genericTypes?.string?.map((genericType) => genericType.__$$text) ?? []
),
groupType: factMapping.expressionIdentifier.type!.__$$text.toLowerCase(),
id: factMapping!.expressionIdentifier.name!.__$$text,
isRowIndexColumn: false,
label: isDescriptionColumn ? factMapping.factAlias.__$$text : factMapping.expressionAlias!.__$$text,
minWidth: isDescriptionColumn ? 300 : 100,
setWidth: setColumnWidth(factMappingIndex),
width: factMapping.columnWidth?.__$$text ?? (isDescriptionColumn ? 300 : factMapping.columnWidth?.__$$text),
};
},
[determineDataTypeLabel, setColumnWidth]
);
/* It determines the Instance Section (the header row in the middle) based on the given FactMapping (Scesim column representation)
and the groupType. An Instance represents the a real implementation of a DMN Type (DMN-based SCESIM) / Java Class (Rule-based Scesim)
*/
const generateInstanceSectionFromFactMapping = useCallback(
(factMapping: SceSim__FactMappingType, groupType: TestScenarioTableColumnInstanceGroup) => {
/* RULE Test Scenarios can have the same instance in both GIVEN and EXPECT section. Therefore, using the following
pattern to identify it */
const instanceID =
factMapping.expressionIdentifier.type?.__$$text + "." + factMapping.factIdentifier.name!.__$$text;
return {
accessor: instanceID,
dataType: determineDataTypeLabel(factMapping.factIdentifier.className!.__$$text, []),
groupType: groupType.toLowerCase(),
id: instanceID,
isRowIndexColumn: false,
label: factMapping.factAlias.__$$text,
columns: [] as ReactTable.Column<ROWTYPE>[],
};
},
[determineDataTypeLabel]
);
/**
It generates the columns of the TestScenarioTable, based on the following logic:
+---+------------------------------------------------------+----------------------------------------+
| | Description | givenSection (given-header) | expectSection (expect-header) |
| | +--------------------------------+-----+----------------------------------+-----+
| # | | givenInstance (given-instance) | ... | expectGroup (expect-instance) | ... |
| | +----------------+---------------+-----+----------------------------------+-----+
| | | field (given) | field (given) | ... | field (expect) | field (expect)| ... |
+---+---------------+----------------+---------------+-----+-----------------+----------------+-----+
Every section has its related groupType in the rounded brackets, that are crucial to determine
the correct context menu behavior (adding/removing an instance requires a different logic than
adding/removing a field)
Background table shows the givenSection only.
The returned object contains all the columns in the above structure and the instancesGroup (givenInstance + expectInstance)
required to correctly manage the instance header's context menu behavior.
*/
const tableColumns = useMemo<{
allColumns: ReactTable.Column<ROWTYPE>[];
instancesGroup: ReactTable.Column<ROWTYPE>[];
}>(() => {
const descriptionColumns: ReactTable.Column<ROWTYPE>[] = [];
const givenInstances: ReactTable.Column<ROWTYPE>[] = [];
const expectInstances: ReactTable.Column<ROWTYPE>[] = [];
(tableData.scesimModelDescriptor.factMappings.FactMapping ?? []).forEach((factMapping, index) => {
/* RULE Test Scenarios can have the same instance in both GIVEN and EXPECT section. Therefore, using the following
pattern to identify it */
const instanceID =
factMapping.expressionIdentifier.type?.__$$text + "." + factMapping.factIdentifier.name!.__$$text;
if (factMapping.expressionIdentifier.type?.__$$text === TestScenarioTableColumnFieldGroup.GIVEN.toUpperCase()) {
const instance = givenInstances.find((instanceColumn) => instanceColumn.id === instanceID);
if (instance) {
instance.columns?.push(generateColumnFromFactMapping(factMapping, index));
} else {
const newInstance = generateInstanceSectionFromFactMapping(
factMapping,
TestScenarioTableColumnInstanceGroup.GIVEN
);
newInstance.columns.push(generateColumnFromFactMapping(factMapping, index));
givenInstances.push(newInstance);
}
} else if (
factMapping.expressionIdentifier.type!.__$$text === TestScenarioTableColumnFieldGroup.EXPECT.toUpperCase()
) {
const instance = expectInstances.find((instanceColumn) => instanceColumn.id === instanceID);
if (instance) {
instance.columns?.push(generateColumnFromFactMapping(factMapping, index));
} else {
const newInstance = generateInstanceSectionFromFactMapping(
factMapping,
TestScenarioTableColumnInstanceGroup.EXPECT
);
newInstance.columns.push(generateColumnFromFactMapping(factMapping, index));
expectInstances.push(newInstance);
}
} else if (
factMapping.expressionIdentifier.type!.__$$text === TestScenarioTableColumnFieldGroup.OTHER.toUpperCase() &&
factMapping.expressionIdentifier.name!.__$$text === "Description"
) {
descriptionColumns.push(generateColumnFromFactMapping(factMapping, index, true));
}
});
const givenSection = [
{
accessor: TestScenarioTableColumnHeaderGroup.GIVEN,
groupType: TestScenarioTableColumnHeaderGroup.GIVEN,
id: TestScenarioTableColumnHeaderGroup.GIVEN,
isRowIndexColumn: false,
label: i18n.table.given.toUpperCase(),
columns: givenInstances,
},
];
const expectSection =
expectInstances.length > 0
? [
{
accessor: TestScenarioTableColumnHeaderGroup.EXPECT,
groupType: TestScenarioTableColumnHeaderGroup.EXPECT,
id: TestScenarioTableColumnHeaderGroup.EXPECT,
isRowIndexColumn: false,
label: i18n.table.expect.toUpperCase(),
columns: expectInstances,
},
]
: [];
return {
allColumns: [...descriptionColumns, ...givenSection, ...expectSection],
instancesGroup: [...givenInstances, ...expectInstances],
};
}, [
generateColumnFromFactMapping,
generateInstanceSectionFromFactMapping,
i18n,
TestScenarioTableColumnHeaderGroup,
TestScenarioTableColumnFieldGroup,
TestScenarioTableColumnInstanceGroup,
tableData.scesimModelDescriptor.factMappings.FactMapping,
]);
/* It generates the columns of the TestScenarioTable */
const tableRows = useMemo(
() =>
(retrieveRowsData(tableData.scesimData) ?? []).map((scenario, index) => {
const factMappingValues = scenario.factMappingValues.FactMappingValue ?? [];
const tableRow = getColumnsAtLastLevel(tableColumns.allColumns, 2).reduce(
(tableRow: ROWTYPE, column: ReactTable.Column<ROWTYPE>) => {
const factMappingValue = factMappingValues.filter(
(fmv) => fmv.expressionIdentifier.name!.__$$text === column.accessor
);
tableRow[column.accessor] = factMappingValue[0]?.rawValue?.__$$text ?? "";
return tableRow;
},
{ id: index }
);
return tableRow;
}),
[retrieveRowsData, tableColumns.allColumns, tableData.scesimData]
);
/** TABLE'S CONTEXT MENU MANAGEMENT */
const allowedOperations = useCallback(
(conditions: BeeTableContextMenuAllowedOperationsConditions) => {
const isHeader =
conditions.column?.groupType === TestScenarioTableColumnHeaderGroup.EXPECT ||
conditions.column?.groupType === TestScenarioTableColumnHeaderGroup.GIVEN;
const isInstance =
conditions.column?.groupType === TestScenarioTableColumnInstanceGroup.EXPECT ||
conditions.column?.groupType === TestScenarioTableColumnInstanceGroup.GIVEN;
const isOther = conditions.column?.groupType === TestScenarioTableColumnFieldGroup.OTHER;
if (!conditions.selection.selectionStart || !conditions.selection.selectionEnd || isHeader) {
return [];
}
const columnIndex = conditions.selection.selectionStart.columnIndex;
const columnCanBeDeleted =
!isOther &&
columnIndex > 0 &&
((isBackground && (conditions.columns?.length ?? 0) > 1) ||
(!isBackground && columnIndex > 0 && (conditions.columns?.length ?? 0) > 2));
const columnsWithNoOperations = isBackground ? [0] : [0, 1];
const columnOperations = (isInstance ? columnIndex in [0] : columnIndex in columnsWithNoOperations)
? []
: [
BeeTableOperation.ColumnInsertLeft,
BeeTableOperation.ColumnInsertRight,
BeeTableOperation.ColumnInsertN,
...(columnCanBeDeleted ? [BeeTableOperation.ColumnDelete] : []),
];
return [
...(columnIndex >= 0 && conditions.selection.selectionStart.rowIndex < 0 ? columnOperations : []),
...(conditions.selection.selectionStart.rowIndex >= 0 && columnIndex > 0
? [
BeeTableOperation.SelectionCopy,
BeeTableOperation.SelectionCut,
BeeTableOperation.SelectionPaste,
BeeTableOperation.SelectionReset,
]
: []),
...(conditions.selection.selectionStart.rowIndex >= 0 && !isBackground
? [
BeeTableOperation.RowInsertAbove,
BeeTableOperation.RowInsertBelow,
BeeTableOperation.RowInsertN,
BeeTableOperation.RowDelete,
BeeTableOperation.RowReset,
BeeTableOperation.RowDuplicate,
]
: []),
];
},
[
TestScenarioTableColumnHeaderGroup,
TestScenarioTableColumnInstanceGroup,
TestScenarioTableColumnFieldGroup,
isBackground,
]
);
const generateOperationConfig = useCallback(
(groupName: string) => {
const isInstance =
groupName === TestScenarioTableColumnInstanceGroup.EXPECT ||
groupName === TestScenarioTableColumnInstanceGroup.GIVEN;
const groupLabel = (!isInstance ? i18n.table.field : i18n.table.instance).toUpperCase();
return [
{
group: groupLabel,
items: [
{
name: isInstance ? i18n.table.insertLeftInstance : i18n.table.insertLeftField,
type: BeeTableOperation.ColumnInsertLeft,
},
{
name: isInstance ? i18n.table.insertRightInstance : i18n.table.insertRightField,
type: BeeTableOperation.ColumnInsertRight,
},
{ name: i18n.table.insert, type: BeeTableOperation.ColumnInsertN },
{
name: isInstance ? i18n.table.deleteInstance : i18n.table.deleteField,
type: BeeTableOperation.ColumnDelete,
},
],
},
{
group: i18n.table.simulation.singleEntry.toUpperCase(),
items: [
{ name: i18n.table.insertAbove, type: BeeTableOperation.RowInsertAbove },
{ name: i18n.table.insertBelow, type: BeeTableOperation.RowInsertBelow },
{ name: i18n.table.insert, type: BeeTableOperation.RowInsertN },
{ name: i18n.table.delete, type: BeeTableOperation.RowDelete },
{ name: i18n.table.duplicate, type: BeeTableOperation.RowDuplicate },
],
},
{
group: i18n.table.selection.toUpperCase(),
items: [
{ name: i18n.table.copy, type: BeeTableOperation.SelectionCopy },
{ name: i18n.table.cut, type: BeeTableOperation.SelectionCut },
{ name: i18n.table.paste, type: BeeTableOperation.SelectionPaste },
{ name: i18n.table.reset, type: BeeTableOperation.SelectionReset },
],
},
];
},
[TestScenarioTableColumnInstanceGroup, i18n]
);
const simulationOperationConfig = useMemo<BeeTableOperationConfig>(() => {
const config: BeeTableOperationConfig = {};
config[""] = generateOperationConfig("");
config[TestScenarioTableColumnHeaderGroup.EXPECT] = generateOperationConfig("");
config[TestScenarioTableColumnHeaderGroup.GIVEN] = generateOperationConfig("");
config[TestScenarioTableColumnInstanceGroup.EXPECT] = generateOperationConfig(
TestScenarioTableColumnInstanceGroup.EXPECT
);
config[TestScenarioTableColumnInstanceGroup.GIVEN] = generateOperationConfig(
TestScenarioTableColumnInstanceGroup.GIVEN
);
config[TestScenarioTableColumnFieldGroup.EXPECT] = generateOperationConfig(
TestScenarioTableColumnFieldGroup.EXPECT
);
config[TestScenarioTableColumnFieldGroup.GIVEN] = generateOperationConfig(TestScenarioTableColumnFieldGroup.GIVEN);
config[TestScenarioTableColumnFieldGroup.OTHER] = generateOperationConfig(TestScenarioTableColumnFieldGroup.OTHER);
return config;
}, [
TestScenarioTableColumnFieldGroup,
TestScenarioTableColumnHeaderGroup,
TestScenarioTableColumnInstanceGroup,
generateOperationConfig,
]);
/** TABLE UPDATES FUNCTIONS */
/**
* It updates every changed Cell in its related FactMappingValue
*/
const onCellUpdates = useCallback(
(cellUpdates: BeeTableCellUpdate<ROWTYPE>[]) => {
cellUpdates.forEach((update) => {
testScenarioEditorStoreApi.setState((state) => {
const factMappings = isBackground
? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping!
: state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!;
const factMappingValuesTypes = isBackground
? state.scesim.model.ScenarioSimulationModel.background.scesimData.BackgroundData!
: state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!;
updateCell({
columnIndex: update.columnIndex + columnIndexStart,
factMappings: factMappings,
factMappingValuesTypes: factMappingValuesTypes,
rowIndex: update.rowIndex,
value: update.value,
});
});
});
},
[columnIndexStart, isBackground, testScenarioEditorStoreApi]
);
/**
This logic determines the selected column index in the table's multi-layer headers. This is required because currently
every layer handles apply a different logic to setting the index. Last-level header follows the natural columns index order,
while the 1th and 2th not. The following schema should clarify the current logic:
+---+------------------------------------------------------+----------------------------------------+
| | 1 | 1 | 2 |
| | +--------------------------------+-----+----------------------------------+-----+
| 0 | | 1 | 2 | 3 | 4 |
| | +----------------+---------------+-----+----------------------------------+-----+
| | | 2 | 3 | 4 | 5 | 6 | 7 |
+---+---------------+----------------+---------------+-----+-----------------+----------------+-----+
The First layer doesn't perform any operation, so it's not managed here.
*/
const determineSelectedColumnIndex = useCallback(
(factMappings: SceSim__FactMappingType[], originalSelectedColumnIndex: number, isInstance: boolean) => {
if (isInstance) {
const instanceSectionID = tableColumns.instancesGroup[originalSelectedColumnIndex - 1].id;
return (
factMappings.findIndex(
(factMapping) =>
factMapping.expressionIdentifier.type?.__$$text + "." + factMapping.factIdentifier.name!.__$$text ===
instanceSectionID
) ?? -1
);
}
/* In case of background, the rowIndex column is not present */
return originalSelectedColumnIndex - (isBackground ? 1 : 0);
},
[isBackground, tableColumns.instancesGroup]
);
/* It determines in which index position a column should be added. In case of a field, the new column index is simply
in the right or in the left of the selected column. In case of a new instance, it's required to find the first column
index outside the selected Instance group. */
const determineNewColumnTargetIndex = (
factMappings: SceSim__FactMappingType[],
insertDirection: InsertRowColumnsDirection,
isInstance: boolean,
selectedColumnIndex: number,
selectedFactMapping: SceSim__FactMappingType
) => {
const groupType = selectedFactMapping.expressionIdentifier.type!.__$$text;
const instanceName = selectedFactMapping.factIdentifier.name!.__$$text;
const instanceType = selectedFactMapping.factIdentifier.className!.__$$text;
if (!isInstance) {
if (insertDirection === InsertRowColumnsDirection.AboveOrRight) {
return selectedColumnIndex + 1;
} else {
return selectedColumnIndex;
}
}
let newColumnTargetColumn = -1;
if (insertDirection === InsertRowColumnsDirection.AboveOrRight) {
for (let i = selectedColumnIndex; i < factMappings.length; i++) {
const currentFM = factMappings[i];
if (
currentFM.expressionIdentifier.type!.__$$text === groupType &&
currentFM.factIdentifier.name?.__$$text === instanceName &&
currentFM.factIdentifier.className?.__$$text === instanceType
) {
if (i == factMappings.length - 1) {
newColumnTargetColumn = i + 1;
}
} else {
newColumnTargetColumn = i;
break;
}
}
} else {
for (let i = selectedColumnIndex; i >= 0; i--) {
const currentFM = factMappings[i];
if (
currentFM.expressionIdentifier.type!.__$$text === groupType &&
currentFM.factIdentifier.name?.__$$text === instanceName &&
currentFM.factIdentifier.className?.__$$text === instanceType
) {
if (i == 0) {
newColumnTargetColumn = 0;
}
} else {
newColumnTargetColumn = i + 1;
break;
}
}
}
return newColumnTargetColumn;
};
/**
* It adds a new FactMapping (Column) in the Model Descriptor structure and adds the new column related FactMapping Value (Cell)
*/
const onColumnAdded = useCallback(
(args: {
beforeIndex: number;
currentIndex: number;
groupType: string;
columnsCount: number;
insertDirection: InsertRowColumnsDirection;
}) => {
/* GIVEN and EXPECTED of FIELD and INSTANCE column types can be added only */
if (
TestScenarioTableColumnFieldGroup.OTHER === args.groupType ||
TestScenarioTableColumnHeaderGroup.EXPECT === args.groupType ||
TestScenarioTableColumnHeaderGroup.GIVEN === args.groupType
) {
console.error("Can't add a " + args.groupType + " type column.");
return;
}
const isInstance =
args.groupType === TestScenarioTableColumnInstanceGroup.EXPECT ||
args.groupType === TestScenarioTableColumnInstanceGroup.GIVEN;
testScenarioEditorStoreApi.setState((state) => {
const factMappings = isBackground
? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping!
: state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!;
const factMappingValues = isBackground
? state.scesim.model.ScenarioSimulationModel.background.scesimData.BackgroundData!
: state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!;
const selectedColumnFactMappingIndex = determineSelectedColumnIndex(
factMappings,
args.currentIndex,
isInstance
);
const selectedColumnFactMapping = factMappings[selectedColumnFactMappingIndex];
const targetColumnIndex = determineNewColumnTargetIndex(
factMappings,
args.insertDirection,
isInstance,
selectedColumnFactMappingIndex,
selectedColumnFactMapping
);
const isNewInstance =
isInstance || selectedColumnFactMapping.factIdentifier.className?.__$$text === "java.lang.Void";
for (let columnIndex = 0; columnIndex < args.columnsCount; columnIndex++) {
isNewInstance
? addColumnWithEmptyInstanceAndProperty({
expressionIdentifierType: selectedColumnFactMapping.expressionIdentifier.type!.__$$text,
factMappings: factMappings,
factMappingValuesTypes: factMappingValues,
targetColumnIndex: targetColumnIndex + columnIndex,
})
: addColumnWithEmptyProperty({
expressionElementsSteps: [
selectedColumnFactMapping.expressionElements!.ExpressionElement![0].step.__$$text,
],
expressionIdentifierType: selectedColumnFactMapping.expressionIdentifier.type!.__$$text,
factAlias: selectedColumnFactMapping.factAlias.__$$text,
factIdentifierClassName: selectedColumnFactMapping.factIdentifier.className!.__$$text,
factIdentifierName: selectedColumnFactMapping.factIdentifier.name!.__$$text,
factMappings: factMappings,
factMappingValuesTypes: factMappingValues,
targetColumnIndex: targetColumnIndex + columnIndex,
});
}
});
},
[
determineSelectedColumnIndex,
isBackground,
testScenarioEditorStoreApi,
TestScenarioTableColumnFieldGroup,
TestScenarioTableColumnHeaderGroup,
TestScenarioTableColumnInstanceGroup,
]
);
/**
* It removes a FactMapping (Column) at the given column index toghter with its related Data Cells.
*/
const onColumnDeleted = useCallback(
(args: { columnIndex: number; groupType: string }) => {
/* GIVEN and EXPECTED of FIELD and INSTANCE column types can be deleted only */
if (
TestScenarioTableColumnFieldGroup.OTHER === args.groupType ||
TestScenarioTableColumnHeaderGroup.EXPECT === args.groupType ||
TestScenarioTableColumnHeaderGroup.GIVEN === args.groupType
) {
console.error("Can't delete a " + args.groupType + " type column.");
return;
}
const isInstance =
args.groupType === TestScenarioTableColumnInstanceGroup.EXPECT ||
args.groupType === TestScenarioTableColumnInstanceGroup.GIVEN;
testScenarioEditorStoreApi.setState((state) => {
const factMappings = isBackground
? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping!
: state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!;
const factMappingValues = isBackground
? state.scesim.model.ScenarioSimulationModel.background.scesimData.BackgroundData!
: state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!;
const factMappingIndexToRemove = determineSelectedColumnIndex(factMappings, args.columnIndex + 1, isInstance);
const factMappingExpressionIdentifierTypeToRemove =
factMappings[factMappingIndexToRemove].expressionIdentifier.type!.__$$text;
const { deletedFactMappingIndexs } = deleteColumn({
factMappingIndexToRemove: factMappingIndexToRemove,
factMappings: factMappings,
factMappingValues: factMappingValues,
isBackground: isBackground,
isInstance: isInstance,
selectedColumnIndex: args.columnIndex,
});
/* If the last elements of factMappingGroup (i.e. "EXPECT" or "GIVEN") has been removed,
a new empty Instance must be created */
const factMappingGroupElementsAfterRemoval = _.groupBy(
factMappings,
(factMapping) => factMapping.expressionIdentifier.type!.__$$text
)[factMappingExpressionIdentifierTypeToRemove];
const isAtLeastOneGroupElementPresent =
!!factMappingGroupElementsAfterRemoval && factMappingGroupElementsAfterRemoval.length > 0;
/* If all element of a group (i.e. "EXPECT" or "GIVEN") are deleted, a new empty column is created for that group */
if (!isAtLeastOneGroupElementPresent) {
addColumnWithEmptyInstanceAndProperty({
expressionIdentifierType: factMappingExpressionIdentifierTypeToRemove,
factMappings: factMappings,
factMappingValuesTypes: factMappingValues,
targetColumnIndex: Math.min(...deletedFactMappingIndexs),
});
}
/** Updating the selectedColumn. When deleting, BEETable automatically shifts the selected cell in the left. */
const firstRemovedIndex = Math.min(...deletedFactMappingIndexs);
const selectedColumnIndex = Math.max(0, firstRemovedIndex - 1);
state.dispatch(state).table.updateSelectedColumn({
factMapping: _.cloneDeep(factMappings[selectedColumnIndex]),
index: selectedColumnIndex,
isBackground: isBackground,
});
});
},
[
determineSelectedColumnIndex,
isBackground,
testScenarioEditorStoreApi,
TestScenarioTableColumnFieldGroup,
TestScenarioTableColumnHeaderGroup,
TestScenarioTableColumnInstanceGroup,
]
);
/**
* It adds a Scenario (Row) at the given row index
*/
const onRowAdded = useCallback(
(args: { beforeIndex: number; insertDirection: InsertRowColumnsDirection; rowsCount: number }) => {
if (isBackground) {
throw new Error("Impossible state. Background table can have a single row only");
}
testScenarioEditorStoreApi.setState((state) => {
const factMappings =
state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!;
const factMappingValues = state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!;
for (let rowIndex = 0; rowIndex < args.rowsCount; rowIndex++) {
addRow({ beforeIndex: args.beforeIndex, factMappings: factMappings, factMappingValues: factMappingValues });
}
});
},
[isBackground, testScenarioEditorStoreApi]
);
/**
* It deletes a Scenario (Row) at the given row index
*/
const onRowDeleted = useCallback(
(args: { rowIndex: number }) => {
if (isBackground) {
throw new Error("Impossible state. Background table can have a single row only");
}
testScenarioEditorStoreApi.setState((state) => {
const factMappingValues = state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!;
deleteRow({ rowIndex: args.rowIndex, factMappingValues: factMappingValues });
/* If all rows (i.e. factMappingValues) have been deleted, a new row is added */
if (factMappingValues.length === 0) {
const factMappings =
state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!;
addRow({ beforeIndex: args.rowIndex, factMappings: factMappings, factMappingValues: factMappingValues });
}
});
},
[isBackground, testScenarioEditorStoreApi]
);
/**
* It duplicates a Scenario (Row) at the given row index
*/
const onRowDuplicated = useCallback(
(args: { rowIndex: number }) => {
if (isBackground) {
throw new Error("Impossible state. Background table can have a single row only");
}
testScenarioEditorStoreApi.setState((state) => {
const factMappingValues = state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!;
dupliacteRow({ rowIndex: args.rowIndex, factMappingValues: factMappingValues });
});
},
[isBackground, testScenarioEditorStoreApi]
);
/**
* Behavior to apply when a DataCell is clicked
*/
const onDataCellClick = useCallback(
(_columnID: string) => {
testScenarioEditorStoreApi.setState((state) => {
state.dispatch(state).table.updateSelectedColumn(null);
});
},
[testScenarioEditorStoreApi]
);
const onHeaderClick = useCallback(
(columnKey: string) => {
console.debug("[TestScenarioTable] columnKey: ", columnKey);
if (
columnKey == TestScenarioTableColumnHeaderGroup.EXPECT ||
columnKey == TestScenarioTableColumnHeaderGroup.GIVEN
) {
testScenarioEditorStoreApi.setState((state) => {
state.dispatch(state).table.updateSelectedColumn(null);
});
return;
}
const modelDescriptor = isBackground
? (tableData as SceSim__backgroundType).scesimModelDescriptor
: (tableData as SceSim__simulationType).scesimModelDescriptor;
if (
columnKey.startsWith(TestScenarioTableColumnFieldGroup.GIVEN.toUpperCase()) ||
columnKey.toUpperCase().startsWith(TestScenarioTableColumnFieldGroup.EXPECT.toUpperCase())
) {
const selectedInstanceGroup = tableColumns.instancesGroup.find((instance) => instance.id === columnKey);
if (
selectedInstanceGroup?.columns?.length === 1 &&
selectedInstanceGroup?.columns[0].dataType === "<Undefined>"
) {
const propertyID = selectedInstanceGroup?.columns[0].id;
const selectedFactMapping = modelDescriptor.factMappings.FactMapping!.find(
(factMapping) => factMapping.expressionIdentifier.name?.__$$text === propertyID
);
const selectedFactIndex = selectedFactMapping
? modelDescriptor.factMappings.FactMapping!.indexOf(selectedFactMapping!)
: -1;
testScenarioEditorStoreApi.setState((state) => {
state.dispatch(state).table.updateSelectedColumn({
factMapping: JSON.parse(JSON.stringify(selectedFactMapping)),
index: selectedFactIndex ?? -1,
isBackground: isBackground,
});
});
} else {
testScenarioEditorStoreApi.setState((state) => {
state.dispatch(state).table.updateSelectedColumn(null);
});
}
return;
}
const selectedFactMapping = modelDescriptor.factMappings.FactMapping!.find(
(factMapping) => factMapping.expressionIdentifier.name?.__$$text == columnKey
);
const selectedFactIndex = selectedFactMapping
? modelDescriptor.factMappings.FactMapping!.indexOf(selectedFactMapping!)
: -1;
testScenarioEditorStoreApi.setState((state) => {
state.dispatch(state).table.updateSelectedColumn({
factMapping: JSON.parse(JSON.stringify(selectedFactMapping)),
index: selectedFactIndex ?? -1,
isBackground: isBackground,
});
});
},
[
TestScenarioTableColumnFieldGroup,
TestScenarioTableColumnHeaderGroup,
isBackground,
tableColumns.instancesGroup,
tableData,
testScenarioEditorStoreApi,
]
);
return (
<div className={"test-scenario-table"}>
<StandaloneBeeTable
allowedOperations={allowedOperations}
columns={tableColumns.allColumns}
enableKeyboardNavigation={true}
headerLevelCountForAppendingRowIndexColumn={2}
headerVisibility={BeeTableHeaderVisibility.AllLevels}
isEditableHeader={false}
isReadOnly={false}
onCellUpdates={onCellUpdates}
onColumnAdded={onColumnAdded}
onColumnDeleted={onColumnDeleted}
onDataCellClick={onDataCellClick}
onDataCellKeyUp={onDataCellClick}
onHeaderClick={onHeaderClick}
onHeaderKeyUp={onHeaderClick}
onRowAdded={onRowAdded}
onRowDeleted={onRowDeleted}
onRowDuplicated={onRowDuplicated}
operationConfig={simulationOperationConfig}
resizerStopBehavior={ResizerStopBehavior.SET_WIDTH_WHEN_SMALLER}
rows={tableRows}
scrollableParentRef={scrollableParentRef}
shouldRenderRowIndexColumn={!isBackground}
shouldShowColumnsInlineControls={true}
shouldShowRowsInlineControls={!isBackground}
/>
</div>
);
}