bito-code-review[bot] commented on code in PR #44657:
URL: https://github.com/apache/superset/pull/44657#discussion_r4175632448
##########
superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx:
##########
@@ -173,7 +177,10 @@ const config: ControlPanelConfig = {
name: 'rowTotals',
config: {
type: 'CheckboxControl',
- label: t('Show rows total'),
+ // The displayed value may be a result aggregation (Median,
+ // Average, ...) rather than a plain total once
`aggregateFunction`
+ // is set below, so "summary" rather than "total".
+ label: () => t('Show row summaries'),
Review Comment:
<div>
<div id="suggestion">
<div id="issue"><b>Unnecessary function-form label</b></div>
<div id="fix">
This label is static — it never reads control state — so the function form
adds an indirection the sibling controls (`rowSubTotals`, `colSubTotals`) and
this file's 21 static `label: t(...)` usages don't have.
`BaseControlConfig.label` supports both forms
(packages/superset-ui-chart-controls/src/types.ts), so behavior is fine; prefer
plain `label: t('Show row summaries')` for consistency.
</div>
</div>
<details>
<summary><b>Citations</b></summary>
<ul>
<li>
Rule Violated: <a
href="https://github.com/apache/superset/blob/84bb1b7/.cursor/rules/dev-standard.mdc#L119">dev-standard.mdc:119</a>
</li>
</ul>
</details>
<div id="suggestion">
<div id="issue"><b>Stale description contradicts new label</b></div>
<div id="fix">
The renamed label now diverges from the unchanged `description: t('Display
row level total')` immediately below: per this PR's own comment the displayed
value may be a Median/Average, not a total. Consider updating the description
wording to 'summary' so label and description agree.
</div>
</div>
<small><i>Code Review Run #3b895d</i></small>
</div>
---
Should Bito avoid suggestions like this for future reviews? (<a
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
- [ ] Yes, avoid them
##########
tests/unit_tests/migrations/test_tag_pivot_tables_with_restored_aggregation.py:
##########
@@ -0,0 +1,276 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+"""Tests for migration
+``141b8ada7731_tag_pivot_tables_with_restored_aggregation``.
+
+Covers the helper (_has_legacy_aggregate_function), the full upgrade() path
+across multiple slices (tagging, idempotency, and that unaffected slices are
+left alone -- and that query_context is never touched, see the module
+docstring for why), and that downgrade() strips the default "Metric" value.
+"""
+
+from __future__ import annotations
+
+from importlib import import_module
+from unittest.mock import patch
+
+import pytest
+from sqlalchemy import create_engine
+from sqlalchemy.orm import Session
+
+from superset.tags.models import ObjectType, TagType
+from superset.utils import json
+
+migration = import_module(
+ "superset.migrations.versions."
+ "2026-09-25_00-00_141b8ada7731_tag_pivot_tables_with_restored_"
+)
+
+Slice = migration.Slice
+Tag = migration.Tag
+TaggedObject = migration.TaggedObject
+_has_legacy_aggregate_function = migration._has_legacy_aggregate_function
+_VIZ_TYPE = migration._VIZ_TYPE
+_FIELD = migration._FIELD
+_LEGACY_AGGREGATE_FUNCTIONS = migration._LEGACY_AGGREGATE_FUNCTIONS
+LEGACY_AGGREGATION_TAG = migration.LEGACY_AGGREGATION_TAG
+
+
[email protected]
+def engine():
+ engine = create_engine("sqlite:///:memory:")
+ migration.Base.metadata.create_all(engine)
+ return engine
+
+
+# ---------------------------------------------------------------------------
+# _has_legacy_aggregate_function
+# ---------------------------------------------------------------------------
+
+
[email protected]("value", sorted(_LEGACY_AGGREGATE_FUNCTIONS))
+def test_has_legacy_aggregate_function_true_for_every_recognized_value(value):
+ slc = Slice(params=json.dumps({"viz_type": _VIZ_TYPE, _FIELD: value}))
+ assert _has_legacy_aggregate_function(slc)
+
+
+def test_has_legacy_aggregate_function_false_for_unrecognized_value():
+ params = json.dumps({"viz_type": _VIZ_TYPE, _FIELD: "Metric"})
+ assert not _has_legacy_aggregate_function(Slice(params=params))
+
+
+def test_has_legacy_aggregate_function_false_when_field_absent():
+ params = json.dumps({"viz_type": _VIZ_TYPE, "colTotals": True})
+ assert not _has_legacy_aggregate_function(Slice(params=params))
+
+
[email protected]("malformed_value", [[], {}, 1, None])
+def test_has_legacy_aggregate_function_false_when_field_not_a_string(
+ malformed_value,
+):
+ """A malformed non-string aggregateFunction value (e.g. `[]`/`{}` from an
+ unrelated historical bug) must be skipped, not raise -- `in` on a
+ frozenset requires a hashable left operand, so an unguarded membership
+ check would TypeError on a list/dict and abort `superset db upgrade`
+ partway through paginated_update's batches."""
+ params = json.dumps({"viz_type": _VIZ_TYPE, _FIELD: malformed_value})
+ assert not _has_legacy_aggregate_function(Slice(params=params))
+
+
+def test_has_legacy_aggregate_function_false_when_params_empty():
+ assert not _has_legacy_aggregate_function(Slice(params=None))
+
+
[email protected]("malformed_params", ["[]", "null", "1", '"a string"'])
+def
test_has_legacy_aggregate_function_false_when_params_not_dict(malformed_params):
+ """A historically malformed non-dict params value must be skipped, not
+ raise, so one bad row can't abort `superset db upgrade` partway through
+ paginated_update's batches."""
+ assert not _has_legacy_aggregate_function(Slice(params=malformed_params))
+
+
+def test_has_legacy_aggregate_function_false_on_invalid_json():
+ assert not _has_legacy_aggregate_function(Slice(params="not-json"))
+
+
+# ---------------------------------------------------------------------------
+# Full upgrade() integration test
+# ---------------------------------------------------------------------------
+
+
+def _run_upgrade(engine) -> None:
+ # Simulate the Alembic transaction the same way the real migration runs:
+ # bind the session to an explicit connection so paginated_update's
+ # internal commits are visible once the outer transaction closes.
+ with engine.begin() as conn:
+ upgrade_session = Session(bind=conn)
+ with (
+ patch.object(migration, "op") as mock_op,
+ patch.object(migration, "db") as mock_db,
+ ):
+ mock_op.get_bind.return_value = conn
+ mock_db.Session.return_value = upgrade_session
+ migration.upgrade()
+
+
+def test_upgrade_tags_and_invalidates_only_affected_pivot_tables(engine) ->
None:
+ with Session(engine) as seed:
+ seed.add_all(
+ [
+ # A legacy aggregateFunction value -- must be tagged.
+ Slice(
+ id=1,
+ viz_type=_VIZ_TYPE,
+ params=json.dumps({"viz_type": _VIZ_TYPE, _FIELD:
"Median"}),
+ ),
+ # "Metric" (today's default, "Use metric definition") is not a
+ # legacy value -- was never affected, must be left untouched.
+ Slice(
+ id=2,
+ viz_type=_VIZ_TYPE,
+ params=json.dumps({"viz_type": _VIZ_TYPE, _FIELD:
"Metric"}),
+ ),
+ # No aggregateFunction at all -- must be left untouched.
+ Slice(
+ id=3,
+ viz_type=_VIZ_TYPE,
+ params=json.dumps({"viz_type": _VIZ_TYPE}),
+ ),
+ # A different viz type that happens to reuse the same field
+ # name and a legacy-looking value coincidentally -- must be
+ # left untouched, since result aggregation only exists for
+ # pivot_table_v2.
+ Slice(
+ id=4,
+ viz_type="table",
+ params=json.dumps({"viz_type": "table", _FIELD:
"Average"}),
+ ),
+ ]
+ )
+ seed.commit()
+
+ _run_upgrade(engine)
+
+ with Session(engine) as verify:
+ tag = verify.query(Tag).filter_by(name=LEGACY_AGGREGATION_TAG).one()
+ assert tag.type == TagType.custom
+
+ tagged_object_ids = {
+ row.object_id
+ for row in verify.query(TaggedObject).filter_by(
+ tag_id=tag.id, object_type=ObjectType.chart
+ )
+ }
+ assert tagged_object_ids == {1}
+
+
+def test_upgrade_is_idempotent_across_repeated_runs(engine) -> None:
+ """A re-run (or a slice matching an already-created tag) must not violate
+ the (tag_id, object_id, object_type) unique constraint, and must not
+ create a second Tag row for the same name."""
+ with Session(engine) as seed:
+ seed.add(
+ Slice(
+ id=1,
+ viz_type=_VIZ_TYPE,
+ params=json.dumps({"viz_type": _VIZ_TYPE, _FIELD: "Sum"}),
+ )
+ )
+ seed.commit()
+
+ _run_upgrade(engine)
+ _run_upgrade(engine)
+
+ with Session(engine) as verify:
+ tags = verify.query(Tag).filter_by(name=LEGACY_AGGREGATION_TAG).all()
+ assert len(tags) == 1
+
+ tagged = (
+ verify.query(TaggedObject)
+ .filter_by(tag_id=tags[0].id, object_id=1,
object_type=ObjectType.chart)
+ .all()
+ )
+ assert len(tagged) == 1
+
+
+def test_upgrade_populates_tag_audit_timestamps(engine) -> None:
+ """The tag API subtracts created_on/changed_on from now(); NULLs break
it."""
+ with Session(engine) as seed:
+ seed.add(
+ Slice(
+ id=1,
+ viz_type=_VIZ_TYPE,
+ params=json.dumps({"viz_type": _VIZ_TYPE, _FIELD: "Median"}),
+ )
+ )
+ seed.commit()
+
+ _run_upgrade(engine)
+
+ with Session(engine) as verify:
+ tag = verify.query(Tag).filter_by(name=LEGACY_AGGREGATION_TAG).one()
+ assert tag.created_on is not None
+ assert tag.changed_on is not None
+ tagged = verify.query(TaggedObject).one()
+ assert tagged.created_on is not None
+ assert tagged.changed_on is not None
+
+
+def test_downgrade_strips_default_metric_from_params_and_query_context(
Review Comment:
<div>
<div id="suggestion">
<div id="issue"><b>Missing type annotations</b></div>
<div id="fix">
Repo rules (BITO.md 11810/12490) require explicit annotations on fixtures,
test functions, and helpers. Here `engine()` (line 54), every fixture-injected
`engine` param (lines 66, 130, 181, 210, 233), `_run_upgrade(engine)` (line
115), and the seven helper tests lack annotations/`-> None`, while the four
integration tests carry `-> None` — inconsistent within the file. Add `Engine`
annotations and `-> None` throughout.
</div>
</div>
<div id="suggestion">
<div id="issue"><b>Missing test docstrings</b></div>
<div id="fix">
BITO.md rule 12148 requires a docstring on every new test function. Six
tests here have none: the helper tests at lines 66, 71, 76, 94, 106 and
`test_upgrade_tags_and_invalidates_only_affected_pivot_tables` (line 130),
while sibling tests in this same file document theirs. Add one-line docstrings
stating scenario and expected outcome for consistency and lint compliance.
</div>
</div>
<small><i>Code Review Run #3b895d</i></small>
</div>
---
Should Bito avoid suggestions like this for future reviews? (<a
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
- [ ] Yes, avoid them
##########
superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx:
##########
@@ -243,6 +250,34 @@ const config: ControlPanelConfig = {
},
},
],
+ [
+ {
+ name: 'aggregateFunction',
+ config: {
+ type: 'SelectControl',
+ label: () => t('Aggregation function'),
+ default: 'Metric',
Review Comment:
<div>
<div id="suggestion">
<div id="issue"><b>Magic sentinel string 'Metric' repeated</b></div>
<div id="fix">
The sentinel 'Metric' is repeated as the default (here), a choice value
(line 265), and the visibility comparison (line 310); they must stay in sync or
`showValuesAs` visibility silently diverges from the default. Consider
exporting a shared constant (e.g. from `resultAggregation.ts`) and using it at
all three sites.
</div>
</div>
<small><i>Code Review Run #3b895d</i></small>
</div>
---
Should Bito avoid suggestions like this for future reviews? (<a
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
- [ ] Yes, avoid them
##########
superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx:
##########
@@ -243,6 +250,34 @@ const config: ControlPanelConfig = {
},
},
],
+ [
+ {
+ name: 'aggregateFunction',
+ config: {
+ type: 'SelectControl',
+ label: () => t('Aggregation function'),
Review Comment:
<div>
<div id="suggestion">
<div id="issue"><b>Unnecessary function-form label</b></div>
<div id="fix">
This label is also static; the function form is unnecessary here too and is
the only one among the new control's peers. Prefer `label: t('Aggregation
function')` to match the file's 21 static labels.
</div>
</div>
<details>
<summary><b>Citations</b></summary>
<ul>
<li>
Rule Violated: <a
href="https://github.com/apache/superset/blob/84bb1b7/.cursor/rules/dev-standard.mdc#L119">dev-standard.mdc:119</a>
</li>
</ul>
</details>
<small><i>Code Review Run #3b895d</i></small>
</div>
---
Should Bito avoid suggestions like this for future reviews? (<a
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
- [ ] Yes, avoid them
##########
superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx:
##########
@@ -197,7 +204,7 @@ const config: ControlPanelConfig = {
name: 'colTotals',
config: {
type: 'CheckboxControl',
- label: t('Show columns total'),
+ label: () => t('Show column summaries'),
Review Comment:
<div>
<div id="suggestion">
<div id="issue"><b>Unnecessary function-form label</b></div>
<div id="fix">
Same as `rowTotals`: the label is static, and the sibling subtotal controls
(`rowSubTotals`, `colSubTotals`) use the plain form. Prefer `label: t('Show
column summaries')` to match the file's 21 static labels and keep the panel
consistent.
</div>
</div>
<details>
<summary><b>Citations</b></summary>
<ul>
<li>
Rule Violated: <a
href="https://github.com/apache/superset/blob/84bb1b7/.cursor/rules/dev-standard.mdc#L119">dev-standard.mdc:119</a>
</li>
</ul>
</details>
<div id="suggestion">
<div id="issue"><b>Stale description contradicts new label</b></div>
<div id="fix">
Same mismatch as `rowTotals`: the new `Show column summaries` label sits
above the unchanged `description: t('Display column level total')`, which still
uses the 'total' wording this PR moved away from. Consider aligning the
description wording.
</div>
</div>
<small><i>Code Review Run #3b895d</i></small>
</div>
---
Should Bito avoid suggestions like this for future reviews? (<a
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
- [ ] Yes, avoid them
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]