diff --git a/canvas_modules/common-canvas/src/common-properties/ui-conditions/conditions-utils.js b/canvas_modules/common-canvas/src/common-properties/ui-conditions/conditions-utils.js index ef0d70a6ec..1f72e6eaf6 100644 --- a/canvas_modules/common-canvas/src/common-properties/ui-conditions/conditions-utils.js +++ b/canvas_modules/common-canvas/src/common-properties/ui-conditions/conditions-utils.js @@ -709,6 +709,15 @@ function _validateInput(propertyId, controller, control, showErrors) { if (!controller.getErrorMessage(msgPropertyId, true, true, false)) { errorSet = false; } + // Before setting an error message for table cell, clear the error message for table (if any) + if (typeof msgPropertyId.row !== "undefined" || typeof msgPropertyId.col !== "undefined") { + const tablePropertyId = controller.convertPropertyId(msgPropertyId.name); + const tableErrorMessage = controller.getErrorMessage(tablePropertyId); + if (tableErrorMessage !== null) { + controller.updateErrorMessage(tablePropertyId, null); + } + } + if (isError && !errorSet) { controller.updateErrorMessage(msgPropertyId, errorMessage); if (isError) { diff --git a/canvas_modules/harness/src/client/App.js b/canvas_modules/harness/src/client/App.js index 05b639ddbc..f844a8e44c 100644 --- a/canvas_modules/harness/src/client/App.js +++ b/canvas_modules/harness/src/client/App.js @@ -73,6 +73,7 @@ import * as CustomNonEmptyListLessThan from "./custom/condition-ops/customNonEmp import * as CustomOpSyntaxCheck from "./custom/condition-ops/customSyntaxCheck"; import * as CustomOpFilterKeys from "./custom/condition-ops/customFilterKeys"; import * as CustomOpFilterDuplicates from "./custom/condition-ops/customFilterDuplicates"; +import * as CustomRequiredColumn from "./custom/condition-ops/customRequiredColumn"; import BlankCanvasImage from "../../assets/images/blank_canvas.svg"; @@ -1923,7 +1924,7 @@ class App extends React.Component { RandomEffectsPanel, CustomSubjectsPanel]} callbacks={callbacks} customControls={[CustomToggleControl, CustomTableControl, CustomEmmeansDroplist]} - customConditionOps={[CustomOpMax, CustomNonEmptyListLessThan, CustomOpSyntaxCheck, CustomOpFilterKeys, CustomOpFilterDuplicates]} + customConditionOps={[CustomOpMax, CustomNonEmptyListLessThan, CustomOpSyntaxCheck, CustomOpFilterKeys, CustomOpFilterDuplicates, CustomRequiredColumn]} light={this.state.light} />); diff --git a/canvas_modules/harness/src/client/custom/condition-ops/customNonEmptyListLessThan.js b/canvas_modules/harness/src/client/custom/condition-ops/customNonEmptyListLessThan.js index 797d706886..db719d26de 100644 --- a/canvas_modules/harness/src/client/custom/condition-ops/customNonEmptyListLessThan.js +++ b/canvas_modules/harness/src/client/custom/condition-ops/customNonEmptyListLessThan.js @@ -20,7 +20,8 @@ function op() { function evaluate(paramInfo, param2Info, value, controller) { const supportedControls = ["textarea", "list"]; - if (supportedControls.indexOf(paramInfo.control.controlType) >= 0) { + const emptyTable = paramInfo.value.length === 0; + if (supportedControls.indexOf(paramInfo.control.controlType) >= 0 && !emptyTable) { let nonEmptyCount = 0; paramInfo.value.forEach((item) => { if (typeof item !== "undefined" && item.length > 0) { diff --git a/canvas_modules/harness/src/client/custom/condition-ops/customRequiredColumn.js b/canvas_modules/harness/src/client/custom/condition-ops/customRequiredColumn.js new file mode 100644 index 0000000000..7ac7220a52 --- /dev/null +++ b/canvas_modules/harness/src/client/custom/condition-ops/customRequiredColumn.js @@ -0,0 +1,74 @@ +/* + * Copyright 2017-2023 Elyra Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let cachedCheckbox = null; +let cachedTableData = null; + +function op() { + return "customRequiredColumn"; +} + +/** + * Custom condition to toggle "required" column based on checkbox + * + * @param {Object} paramInfo checkbox property + * @param {Object} param2Info table cell property. Example, for myTable, column conditions on that table are referred to using myTable[0], myTable[1], etc. + * @param {Object} controller The properties controller + * @return {Boolean} + */ +function evaluate(paramInfo, param2Info, value, controller) { + // Check if table is empty + const tableValue = controller.getPropertyValue({ name: param2Info.id.name }); + const emptyTable = tableValue.length === 0; + + const runAllCellValidation = (cachedCheckbox !== paramInfo.value) || (cachedTableData !== JSON.stringify(tableValue)); + cachedCheckbox = paramInfo.value; + cachedTableData = JSON.stringify(tableValue); + + if (runAllCellValidation && Array.isArray(tableValue)) { + tableValue.forEach((row, idx) => { + controller.validateInput({ name: param2Info.id.name, row: idx, col: param2Info.id.col }); // validate all fields in table + }); + } + + // When checkbox is checked, show error message if param2Info value is empty + if (paramInfo.value && !emptyTable) { + const dataType = typeof param2Info.value; + switch (dataType) { + case "undefined": + return false; + case "string": + return param2Info.value.length !== 0; + case "number": + return param2Info.value !== null; + case "object": + if (param2Info.value === null) { + return false; + } + const column = param2Info.id.col; // eslint-disable-line no-case-declarations + return param2Info.value[column] !== null && typeof param2Info.value[column] !== "undefined"; + default: + return true; + } + } + + // When checkbox is unchecked, don't show any error message + return true; +} + +// Public Methods -------------------------------------------------------------> + +export { op, evaluate }; diff --git a/canvas_modules/harness/test_resources/parameterDefs/custom-ctrl-op_paramDef.json b/canvas_modules/harness/test_resources/parameterDefs/custom-ctrl-op_paramDef.json index c033761316..b11cd85b64 100644 --- a/canvas_modules/harness/test_resources/parameterDefs/custom-ctrl-op_paramDef.json +++ b/canvas_modules/harness/test_resources/parameterDefs/custom-ctrl-op_paramDef.json @@ -44,6 +44,15 @@ "enum": [ "blue" ] + }, + { + "id": "checkbox", + "type": "boolean" + }, + { + "id": "keyProperties", + "type": "array[keySubProperties]", + "default": [] } ], "complex_types": [ @@ -83,11 +92,31 @@ "type": "string" } ] + }, + { + "id": "keySubProperties", + "parameters": [ + { + "id": "key", + "type": "string" + }, + { + "id": "sorted-clustered", + "type": "string", + "enum": [ + "clustered", + " ", + "sorted" + ], + "default": " " + } + ] } ], "uihints": { "id": "IBM.Custom.Controls", "icon": "images/custom.svg", + "editor_size": "medium", "label": { "default": "Custom Controls" }, @@ -133,6 +162,26 @@ "description": { "default": "numberfield with an error when value > 100 using a custom operator called `customMax`" } + }, + { + "parameter_ref": "checkbox", + "label": { + "default": "Make 'Key' column required in following table" + }, + "description": { + "default": "When checked, key column is mandatory. When unchecked, key column is optional." + }, + "class_name": "checkbox-control-class" + }, + { + "description": { + "default": "Table having key column." + }, + "parameter_ref": "keyProperties", + "label": { + "default": "Key" + }, + "resource_key": "keyPropertiesRKey" } ], "complex_type_info": [ @@ -197,6 +246,33 @@ } } ] + }, + { + "complex_type_ref": "keySubProperties", + "label": { + "default": "Add keySubProperties" + }, + "parameters": [ + { + "parameter_ref": "key", + "label": { + "default": "Key" + }, + "width": 20, + "edit_style": "inline", + "resource_key": "keyRKey" + }, + { + "parameter_ref": "sorted-clustered", + "label": { + "default": "Sort Key Mode" + }, + "width": 20, + "edit_style": "inline", + "resource_key": "sorted-clusteredRKey", + "control": "oneofselect" + } + ] } ], "group_info": [ @@ -219,7 +295,9 @@ }, "type": "controls", "parameter_refs": [ - "custom_op_num" + "custom_op_num", + "checkbox", + "keyProperties" ] } ] @@ -242,6 +320,24 @@ } } } + }, + { + "validation": { + "fail_message": { + "type": "error", + "message": { + "default": "Please provide value for Key column" + }, + "focus_parameter_ref": "keyProperties[0]" + }, + "evaluate": { + "condition": { + "parameter_ref": "checkbox", + "parameter_2_ref": "keyProperties[0]", + "op": "customRequiredColumn" + } + } + } } ], "dataset_metadata": [