This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/texera.git
commit 5e41efae2650c3e70d7490b15bb1fdf9b1983568 Author: Kary Zheng <[email protected]> AuthorDate: Fri Aug 7 12:20:45 2026 -0700 feat(time-series): declare Plot Type's two values as a schema enum (#7256) ### What changes were proposed in this PR? `plotType` — the **Plot Type** field — was a plain string whose two legal values existed only in its description, so the form rendered a free-text box. It now declares them as a schema `enum`, which ngx-formly renders as a dropdown; `ECDFPlotOpDesc` already declares its string modes this way, so no frontend change is involved. The property's declared name stays `line`, since that is what saved workflows carry, and the generator's fallback is untouched. ### Why are the changes needed? The value is matched exactly — `if (plotType == "area") "px.area" else "px.line"` — so anything else draws a line chart and reports nothing. Generating the module on `main`: | Plot Type | Generated call | | --- | --- | | `area` | `px.area(table, ...)` | | `Area` — capitalised | `px.line(table, ...)` | | `aera` — one letter off | `px.line(table, ...)` | `Area` is the sharp case: likely to be typed, and the result is indistinguishable from having asked for a line chart. ### Any related issues, documentation, discussions? Closes #7251 ### How was this PR tested? `WorkflowOperator/compile`, `scalafmtCheckAll`, and `TimeSeriesOpDescSpec` (13 tests). That suite pins the fallback by setting `plotType = "bar"` directly and asserting `px.line`; it bypasses the form, so it still passes. ### Does this PR introduce any user-facing change? Yes. Plot Type becomes a dropdown of `line` and `area`. Saved workflows carrying either are unaffected; one carrying anything else was already drawn as a line and still is. ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Claude Opus 5) --------- Co-authored-by: Claude Opus 5 (1M context) <[email protected]> Co-authored-by: Xuan Gu <[email protected]> --- .../visualization/timeSeriesplot/TimeSeriesOpDesc.scala | 5 +++++ .../operator/timeSeriesPlot/TimeSeriesOpDescSpec.scala | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/timeSeriesplot/TimeSeriesOpDesc.scala b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/timeSeriesplot/TimeSeriesOpDesc.scala index 572fef82e4..7d09972717 100644 --- a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/timeSeriesplot/TimeSeriesOpDesc.scala +++ b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/timeSeriesplot/TimeSeriesOpDesc.scala @@ -59,9 +59,14 @@ class TimeSeriesOpDesc extends PythonOperatorDescriptor { @AutofillAttributeName var facetColumn: EncodableString = "No Selection" + // Declared as a schema enum rather than named only in the description: the code + // below reads exactly two values (`plotType == "area"`, else a line), so a free-text + // box let a typo through and silently drew a line -- no error, just not the chart + // that was asked for. @JsonProperty(value = "line", defaultValue = "line", required = true) @JsonSchemaTitle("Plot Type") @JsonPropertyDescription("Select the type of time series plot (line, area).") + @JsonSchemaInject(json = """{ "enum": ["line", "area"], "default": "line" }""") @NotBlank(message = "Plot Type cannot be empty") var plotType: String = "line" diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/timeSeriesPlot/TimeSeriesOpDescSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/timeSeriesPlot/TimeSeriesOpDescSpec.scala index 75478edf23..7a02ce2275 100644 --- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/timeSeriesPlot/TimeSeriesOpDescSpec.scala +++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/timeSeriesPlot/TimeSeriesOpDescSpec.scala @@ -22,13 +22,14 @@ package org.apache.texera.amber.operator.timeSeriesPlot import org.apache.texera.amber.core.tuple.{AttributeType, Schema} import org.apache.texera.amber.core.workflow.PortIdentity import org.apache.texera.amber.operator.LogicalOp -import org.apache.texera.amber.operator.metadata.OperatorGroupConstants +import org.apache.texera.amber.operator.metadata.{OperatorGroupConstants, OperatorMetadataGenerator} import org.apache.texera.amber.operator.visualization.timeSeriesplot.TimeSeriesOpDesc import org.apache.texera.amber.util.JSONUtils.objectMapper import org.scalatest.funsuite.AnyFunSuite import java.nio.charset.StandardCharsets import java.util.Base64 +import scala.jdk.CollectionConverters._ class TimeSeriesOpDescSpec extends AnyFunSuite { @@ -186,6 +187,20 @@ class TimeSeriesOpDescSpec extends AnyFunSuite { assert(!new TimeSeriesOpDesc().showRangeSlider) } + test("the generated schema offers Plot Type as exactly the two values the code reads") { + // The property is declared under the name "line", which is what saved workflows carry. + val plotType = OperatorMetadataGenerator + .generateOperatorJsonSchema(classOf[TimeSeriesOpDesc]) + .path("properties") + .path("line") + + assert(plotType.path("enum").elements().asScala.map(_.asText()).toList == List("line", "area")) + // the schema default is what the descriptor itself starts at, so an untouched form + // and an untouched descriptor draw the same chart + assert(plotType.path("default").asText() == "line") + assert(plotType.path("default").asText() == new TimeSeriesOpDesc().plotType) + } + // --- generated code shape ---------------------------------------------------- test("generated code coerces the time column, sorts by it, and guards both empty cases") {
