This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-8483-7190a8113ca9ec3ae30d7a1dfdbde4c288034e2c in repository https://gitbox.apache.org/repos/asf/texera.git
commit 6131848b494dabf9ee2d8f24fb81a39408db6960 Author: Prateek Ganigi <[email protected]> AuthorDate: Sat Sep 12 23:48:52 2026 +0000 fix(workflow-operator): validate the spam operator's result attributes like its siblings (#8483) ### What changes were proposed in this PR? `HuggingFaceSpamSMSDetectionOpDesc.getOutputSchemas` was the only one of the four legacy Hugging Face operators that neither validated its result-attribute names nor keyed the input schema by port id. `getOutputSchemas` is called as the user configures an operator, so it needs an answer for "not filled in yet". `HuggingFaceSentimentAnalysisOpDesc` and `HuggingFaceIrisLogisticRegressionOpDesc` answer by returning `null`; this operator instead passed the unset name straight into `Schema.add`. It now returns `null` when either `resultAttributeSpam` or `resultAttributeProbability` is null or blank, matching its siblings. The input schema is also now read as `inputSchemas(operatorInfo.inputPorts.head.id)` rather than `inputSchemas.values.head`, consistent with the sibling operators. With a single input port these are equivalent, so this is a consistency change rather than a behavioral fix. Unifying the error contract across all four legacy operators, two return `null`, one throws, this one did neither — is a broader question and is not attempted here. ### Any related issues? Closes #8482 ### How was this PR tested? 347 tests pass across the `huggingFace` and operator-metadata suites, and `scalafmtCheck` is clean for main and test sources. Two tests were added to `HuggingFaceSpamSMSDetectionOpDescSpec` covering an unset and a blank name for both result attributes. Reverting the operator change and re-running makes exactly those two tests fail, confirming they exercise the fix; the existing happy-path test already keys the input schema by the declared input port, so it covers the lookup change. ### Was this PR authored or co-authored using generative AI tooling? Yes, this PR was co-authored with Claude in compliance with ASF policy. --- .../HuggingFaceSpamSMSDetectionOpDesc.scala | 7 +++++- .../HuggingFaceSpamSMSDetectionOpDescSpec.scala | 29 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceSpamSMSDetectionOpDesc.scala b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceSpamSMSDetectionOpDesc.scala index 0daa7cd4aa..5ac3c03858 100644 --- a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceSpamSMSDetectionOpDesc.scala +++ b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceSpamSMSDetectionOpDesc.scala @@ -87,8 +87,13 @@ class HuggingFaceSpamSMSDetectionOpDesc extends PythonOperatorDescriptor { override def getOutputSchemas( inputSchemas: Map[PortIdentity, Schema] ): Map[PortIdentity, Schema] = { + if ( + resultAttributeSpam == null || resultAttributeSpam.trim.isEmpty || + resultAttributeProbability == null || resultAttributeProbability.trim.isEmpty + ) + return null Map( - operatorInfo.outputPorts.head.id -> inputSchemas.values.head + operatorInfo.outputPorts.head.id -> inputSchemas(operatorInfo.inputPorts.head.id) .add(resultAttributeSpam, AttributeType.BOOLEAN) .add(resultAttributeProbability, AttributeType.DOUBLE) ) diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceSpamSMSDetectionOpDescSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceSpamSMSDetectionOpDescSpec.scala index f7d39d7beb..e028a986ac 100644 --- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceSpamSMSDetectionOpDescSpec.scala +++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/huggingFace/HuggingFaceSpamSMSDetectionOpDescSpec.scala @@ -82,6 +82,35 @@ class HuggingFaceSpamSMSDetectionOpDescSpec extends AnyFlatSpec with Matchers { schema.getAttribute("score").getType shouldBe AttributeType.DOUBLE } + it should "return null while a result attribute is still unset" in { + // getOutputSchemas is called continuously as the user configures the operator, + // so an unset name means "no schema yet", not "build one with a null column". + // The sibling operators (sentiment analysis, iris) already answer this way. + val in = Schema().add("msg", AttributeType.STRING) + val ports = Map((new HuggingFaceSpamSMSDetectionOpDesc).operatorInfo.inputPorts.head.id -> in) + + val noSpamCol = configured() + noSpamCol.resultAttributeSpam = null + noSpamCol.getOutputSchemas(ports) shouldBe null + + val noScoreCol = configured() + noScoreCol.resultAttributeProbability = null + noScoreCol.getOutputSchemas(ports) shouldBe null + } + + it should "return null when a result attribute is blank" in { + val in = Schema().add("msg", AttributeType.STRING) + val ports = Map((new HuggingFaceSpamSMSDetectionOpDesc).operatorInfo.inputPorts.head.id -> in) + + val blankSpamCol = configured() + blankSpamCol.resultAttributeSpam = " " + blankSpamCol.getOutputSchemas(ports) shouldBe null + + val blankScoreCol = configured() + blankScoreCol.resultAttributeProbability = "" + blankScoreCol.getOutputSchemas(ports) shouldBe null + } + "HuggingFaceSpamSMSDetectionOpDesc.generatePythonCode" should "emit the spam-detection pipeline carrying the configured columns (encoded)" in { val d = configured()
