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 a96bf3e70c0e9d5bf72402637633b36e99049a25 Author: Kary Zheng <[email protected]> AuthorDate: Fri Aug 7 12:09:08 2026 -0700 fix(visualization): define the render_error the table charts already call (#7260) ### What changes were proposed in this PR? `TablesPlotOpDesc` and `FigureFactoryTableOpDesc` both generate a `TableChartOperator` that calls `self.render_error(...)` on two branches, but neither generated class defines that method. Each now defines it, in the same shape the other visualization operators use. ### Why are the changes needed? Both branches are reachable — an empty input table, and a value column left with only non-positive or null values. Executing each operator's generated module against an empty frame raises `AttributeError: 'TableChartOperator' object has no attribute 'render_error'` instead of showing the message the code was written to show. With the definition added, the same run yields `Tables Plot is not available. Reason is: input table is empty.` ### Any related issues, documentation, discussions? Closes #7244 ### How was this PR tested? `WorkflowOperator/scalafmtCheckAll` and both operators' descriptor specs (14 tests). Each spec now asserts its generated class defines `render_error` and formats the operator's name. Separately, each generated module was run locally against an empty frame before and after the fix, with the `pytexera` and `plotly` imports stubbed — the before run reproduces the `AttributeError`, the after run returns `Tables Plot is not available. Reason is: input table is empty.` ### Does this PR introduce any user-facing change? Yes. Those two cases now render the intended message instead of failing the operator. Nothing changes on the path that produces a chart. ### 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]> --- .../figureFactoryTable/FigureFactoryTableOpDesc.scala | 3 +++ .../operator/visualization/tablesChart/TablesPlotOpDesc.scala | 3 +++ .../figureFactoryTable/FigureFactoryTableOpDescSpec.scala | 11 +++++++++++ .../visualization/tablesChart/TablesPlotOpDescSpec.scala | 11 +++++++++++ 4 files changed, 28 insertions(+) diff --git a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/figureFactoryTable/FigureFactoryTableOpDesc.scala b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/figureFactoryTable/FigureFactoryTableOpDesc.scala index ae9f9073e0..c1c94f7b12 100644 --- a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/figureFactoryTable/FigureFactoryTableOpDesc.scala +++ b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/figureFactoryTable/FigureFactoryTableOpDesc.scala @@ -104,6 +104,9 @@ class FigureFactoryTableOpDesc extends PythonOperatorDescriptor { | |class TableChartOperator(UDFTableOperator): | + | def render_error(self, error_msg) -> str: + | return f"<h1>Figure Factory Table is not available.</h1><p>Reason is: {error_msg}</p>" + | | def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: | | if table.empty: diff --git a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/tablesChart/TablesPlotOpDesc.scala b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/tablesChart/TablesPlotOpDesc.scala index bf23016275..7290c057dd 100644 --- a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/tablesChart/TablesPlotOpDesc.scala +++ b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/tablesChart/TablesPlotOpDesc.scala @@ -76,6 +76,9 @@ class TablesPlotOpDesc extends PythonOperatorDescriptor { |import plotly.io |class TableChartOperator(UDFTableOperator): | + | def render_error(self, error_msg) -> str: + | return f"<h1>Tables Plot is not available.</h1><p>Reason is: {error_msg}</p>" + | | def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]: | | if table.empty: diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/figureFactoryTable/FigureFactoryTableOpDescSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/figureFactoryTable/FigureFactoryTableOpDescSpec.scala index 83eeed1f96..9d564fd358 100644 --- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/figureFactoryTable/FigureFactoryTableOpDescSpec.scala +++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/figureFactoryTable/FigureFactoryTableOpDescSpec.scala @@ -99,4 +99,15 @@ class FigureFactoryTableOpDescSpec extends AnyFlatSpec with BeforeAndAfter with assert(carries(code, "col_two")) code should include("class TableChartOperator(UDFTableOperator)") } + + it should "define the render_error the empty-table branches call" in { + // Both empty-table branches call self.render_error; without the definition they + // raised AttributeError instead of rendering the message. + withColumns() + val code = opDesc.generatePythonCode() + code should include("def render_error(self, error_msg) -> str:") + code should include( + """return f"<h1>Figure Factory Table is not available.</h1><p>Reason is: {error_msg}</p>"""" + ) + } } diff --git a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/tablesChart/TablesPlotOpDescSpec.scala b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/tablesChart/TablesPlotOpDescSpec.scala index cc19113906..c088cacc67 100644 --- a/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/tablesChart/TablesPlotOpDescSpec.scala +++ b/common/workflow-operator/src/test/scala/org/apache/texera/amber/operator/visualization/tablesChart/TablesPlotOpDescSpec.scala @@ -88,4 +88,15 @@ class TablesPlotOpDescSpec extends AnyFlatSpec with BeforeAndAfter with Matchers ) code should not include "')','" } + + it should "define the render_error the empty-table branches call" in { + // Both empty-table branches call self.render_error; without the definition they + // raised AttributeError instead of rendering the message. + opDesc.includedColumns = List(column("col_one"), column("col_two")) + val code = opDesc.generatePythonCode() + code should include("def render_error(self, error_msg) -> str:") + code should include( + """return f"<h1>Tables Plot is not available.</h1><p>Reason is: {error_msg}</p>"""" + ) + } }
