mirror of
https://github.com/apache/superset.git
synced 2026-07-28 09:32:28 +00:00
Compare commits
3 Commits
ci/actions
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecc7f726a4 | ||
|
|
26b6f7bb5d | ||
|
|
a10c3f0b1d |
21
.github/actions/setup-backend/action.yml
vendored
21
.github/actions/setup-backend/action.yml
vendored
@@ -5,6 +5,10 @@ inputs:
|
||||
description: 'Python version to set up. Accepts a version number, "current", or "next".'
|
||||
required: true
|
||||
default: 'current'
|
||||
cache:
|
||||
description: 'Cache dependencies. Options: pip'
|
||||
required: false
|
||||
default: 'pip'
|
||||
requirements-type:
|
||||
description: 'Type of requirements to install. Options: base, development, default'
|
||||
required: false
|
||||
@@ -39,24 +43,17 @@ runs:
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ steps.set-python-version.outputs.python-version }}
|
||||
- name: Install uv
|
||||
if: inputs.install-superset == 'true'
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
with:
|
||||
python-version: ${{ steps.set-python-version.outputs.python-version }}
|
||||
enable-cache: true
|
||||
- name: Install apt packages
|
||||
if: inputs.install-superset == 'true'
|
||||
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
|
||||
with:
|
||||
packages: libldap2-dev libsasl2-dev
|
||||
version: 1.0
|
||||
cache: ${{ inputs.cache }}
|
||||
- name: Install dependencies
|
||||
env:
|
||||
INPUT_INSTALL_SUPERSET: ${{ inputs.install-superset }}
|
||||
INPUT_REQUIREMENTS_TYPE: ${{ inputs.requirements-type }}
|
||||
run: |
|
||||
if [ "$INPUT_INSTALL_SUPERSET" = "true" ]; then
|
||||
sudo apt-get update && sudo apt-get -y install libldap2-dev libsasl2-dev
|
||||
|
||||
pip install --upgrade pip setuptools wheel uv
|
||||
|
||||
if [ "$INPUT_REQUIREMENTS_TYPE" = "dev" ]; then
|
||||
uv pip install --system -r requirements/development.txt
|
||||
elif [ "$INPUT_REQUIREMENTS_TYPE" = "base" ]; then
|
||||
|
||||
5
.github/workflows/bump-python-package.yml
vendored
5
.github/workflows/bump-python-package.yml
vendored
@@ -45,10 +45,7 @@ jobs:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
enable-cache: true
|
||||
run: pip install uv
|
||||
|
||||
- name: supersetbot bump-python -p "${{ github.event.inputs.package }}"
|
||||
env:
|
||||
|
||||
5
.github/workflows/check-python-deps.yml
vendored
5
.github/workflows/check-python-deps.yml
vendored
@@ -11,7 +11,6 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
actions: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
@@ -22,10 +21,6 @@ jobs:
|
||||
check-python-deps:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
|
||||
5
.github/workflows/dependency-review.yml
vendored
5
.github/workflows/dependency-review.yml
vendored
@@ -20,7 +20,6 @@ concurrency:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
@@ -51,10 +50,6 @@ jobs:
|
||||
# You cannot use a liccheck.ini file in this workflow.
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
|
||||
11
.github/workflows/docker.yml
vendored
11
.github/workflows/docker.yml
vendored
@@ -213,14 +213,3 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
docker compose -f docker-compose-image-tag.yml up superset-init --exit-code-from superset-init
|
||||
|
||||
actions-timeline:
|
||||
needs: [docker-build, docker-compose-image-tag]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
5
.github/workflows/pre-commit.yml
vendored
5
.github/workflows/pre-commit.yml
vendored
@@ -15,7 +15,6 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
@@ -33,10 +32,6 @@ jobs:
|
||||
# rarely differ across patch versions, so 3x per PR is wasteful.
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
|
||||
5
.github/workflows/superset-app-cli.yml
vendored
5
.github/workflows/superset-app-cli.yml
vendored
@@ -11,7 +11,6 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
actions: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
@@ -41,10 +40,6 @@ jobs:
|
||||
ports:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
|
||||
10
.github/workflows/superset-docs-deploy.yml
vendored
10
.github/workflows/superset-docs-deploy.yml
vendored
@@ -30,7 +30,6 @@ concurrency:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
config:
|
||||
@@ -60,10 +59,6 @@ jobs:
|
||||
name: Build & Deploy
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
- name: "Checkout ${{ github.event.workflow_run.head_sha || github.sha }}"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
@@ -81,10 +76,7 @@ jobs:
|
||||
distribution: "zulu"
|
||||
java-version: "21"
|
||||
- name: Install Graphviz
|
||||
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
|
||||
with:
|
||||
packages: graphviz
|
||||
version: 1.0
|
||||
run: sudo apt-get install -y graphviz
|
||||
- name: Compute Entity Relationship diagram (ERD)
|
||||
env:
|
||||
SUPERSET_SECRET_KEY: not-a-secret
|
||||
|
||||
11
.github/workflows/superset-e2e.yml
vendored
11
.github/workflows/superset-e2e.yml
vendored
@@ -340,14 +340,3 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "playwright-tests result: $RESULT (changes: $CHANGES)"
|
||||
|
||||
actions-timeline:
|
||||
needs: [cypress-matrix, playwright-tests]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
@@ -11,7 +11,6 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
actions: read
|
||||
|
||||
# cancel previous workflow jobs for PRs
|
||||
concurrency:
|
||||
@@ -31,10 +30,6 @@ jobs:
|
||||
run:
|
||||
working-directory: superset-extensions-cli
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
|
||||
11
.github/workflows/superset-frontend.yml
vendored
11
.github/workflows/superset-frontend.yml
vendored
@@ -195,14 +195,3 @@ jobs:
|
||||
run: |
|
||||
docker run --rm $TAG bash -c \
|
||||
"npm run build-storybook && npx playwright install-deps && npx playwright install chromium && npm run test-storybook:ci"
|
||||
|
||||
actions-timeline:
|
||||
needs: [report-coverage, lint-frontend, validate-frontend, test-storybook]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
@@ -8,7 +8,6 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
# Serialize runs per PR without cancelling: when a first-time contributor's
|
||||
# queued runs are approved together, cancel-in-progress lets an older run
|
||||
@@ -22,10 +21,6 @@ jobs:
|
||||
lint-test:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
|
||||
5
.github/workflows/superset-playwright.yml
vendored
5
.github/workflows/superset-playwright.yml
vendored
@@ -54,7 +54,6 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
actions: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -80,10 +79,6 @@ jobs:
|
||||
ports:
|
||||
- 16379:6379
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
# -------------------------------------------------------
|
||||
# Conditional checkout based on context (same as Cypress workflow)
|
||||
- name: Checkout for push or pull_request event
|
||||
|
||||
@@ -255,14 +255,3 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
echo "test-postgres result: $RESULT"
|
||||
|
||||
actions-timeline:
|
||||
needs: [test-mysql, test-postgres, test-sqlite]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
@@ -158,14 +158,3 @@ jobs:
|
||||
verbose: true
|
||||
use_oidc: true
|
||||
slug: apache/superset
|
||||
|
||||
actions-timeline:
|
||||
needs: [test-postgres-presto, test-postgres-hive]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
@@ -101,14 +101,7 @@ jobs:
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
- name: Check unit-tests result
|
||||
env:
|
||||
RESULT: ${{ needs.unit-tests.result }}
|
||||
|
||||
16
.github/workflows/superset-translations.yml
vendored
16
.github/workflows/superset-translations.yml
vendored
@@ -78,10 +78,7 @@ jobs:
|
||||
|
||||
- name: Install gettext tools
|
||||
if: steps.check.outputs.python == 'true' || steps.check.outputs.frontend == 'true'
|
||||
uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3
|
||||
with:
|
||||
packages: gettext
|
||||
version: 1.0
|
||||
run: sudo apt-get update && sudo apt-get install -y gettext
|
||||
|
||||
# Fetch the base ref so we can compare PR-introduced regressions
|
||||
# against a fair baseline (also runs babel_update against the base
|
||||
@@ -156,14 +153,3 @@ jobs:
|
||||
- name: Fail if regression detected
|
||||
if: steps.regression.outcome == 'failure'
|
||||
run: exit 1
|
||||
|
||||
actions-timeline:
|
||||
needs: [frontend-check-translations, babel-extract]
|
||||
if: always()
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1
|
||||
with:
|
||||
expand-composite-actions: true
|
||||
|
||||
@@ -318,7 +318,7 @@ class QueryContextFactory: # pylint: disable=too-few-public-methods
|
||||
# another temporal filter. A new filter based on the value of
|
||||
# the granularity will be added later in the code.
|
||||
# In practice, this is replacing the previous default temporal filter.
|
||||
if is_adhoc_column(filter_to_remove): # type: ignore
|
||||
if filter_to_remove and is_adhoc_column(filter_to_remove): # type: ignore
|
||||
filter_to_remove = filter_to_remove.get("sqlExpression")
|
||||
|
||||
if filter_to_remove:
|
||||
|
||||
@@ -206,11 +206,9 @@ class QueryCacheManager:
|
||||
)
|
||||
query_cache.status = QueryStatus.SUCCESS
|
||||
query_cache.is_loaded = True
|
||||
query_cache.is_cached = cache_value is not None
|
||||
query_cache.is_cached = True
|
||||
query_cache.sql_rowcount = cache_value.get("sql_rowcount", None)
|
||||
query_cache.cache_dttm = (
|
||||
cache_value["dttm"] if cache_value is not None else None
|
||||
)
|
||||
query_cache.cache_dttm = cache_value["dttm"]
|
||||
query_cache.queried_dttm = cache_value.get(
|
||||
"queried_dttm", cache_value.get("dttm")
|
||||
)
|
||||
|
||||
@@ -397,7 +397,8 @@ class DashboardDAO(BaseDAO[Dashboard]):
|
||||
md["color_namespace"] = data.get("color_namespace")
|
||||
|
||||
md["expanded_slices"] = data.get("expanded_slices", {})
|
||||
md["refresh_frequency"] = data.get("refresh_frequency", 0)
|
||||
if "refresh_frequency" in data:
|
||||
md["refresh_frequency"] = data["refresh_frequency"]
|
||||
md["color_scheme"] = data.get("color_scheme", "")
|
||||
md["label_colors"] = data.get("label_colors", {})
|
||||
md["shared_label_colors"] = data.get("shared_label_colors", [])
|
||||
|
||||
@@ -534,6 +534,7 @@ class DatabricksDynamicBaseEngineSpec(BasicParametersMixin, DatabricksBaseEngine
|
||||
],
|
||||
) -> list[SupersetError]:
|
||||
errors: list[SupersetError] = []
|
||||
connect_args: dict[str, Any] = {}
|
||||
if extra := json.loads(properties.get("extra")): # type: ignore
|
||||
engine_params = extra.get("engine_params", {})
|
||||
connect_args = engine_params.get("connect_args", {})
|
||||
|
||||
@@ -1235,6 +1235,7 @@ class PrestoEngineSpec(PrestoBaseEngineSpec):
|
||||
all_columns: list[ResultSetColumnType] = []
|
||||
expanded_columns = []
|
||||
current_array_level = None
|
||||
unnested_rows: dict[int, int] = defaultdict(int)
|
||||
while to_process:
|
||||
column, level = to_process.popleft()
|
||||
if column["column_name"] not in [
|
||||
@@ -1248,7 +1249,7 @@ class PrestoEngineSpec(PrestoBaseEngineSpec):
|
||||
# added by the first. every time we change a level in the nested arrays
|
||||
# we reinitialize this.
|
||||
if level != current_array_level:
|
||||
unnested_rows: dict[int, int] = defaultdict(int)
|
||||
unnested_rows = defaultdict(int)
|
||||
current_array_level = level
|
||||
|
||||
name = column["column_name"]
|
||||
|
||||
@@ -532,6 +532,42 @@ class TestQueryContextFactory:
|
||||
|
||||
assert query_object.columns == ["ds", "other_col"]
|
||||
|
||||
def test_apply_granularity_no_filter_to_remove(self):
|
||||
"""No x-axis and no temporal filters leaves the filters untouched."""
|
||||
query_object = Mock(spec=QueryObject)
|
||||
query_object.granularity = "P1D"
|
||||
query_object.columns = ["other_col"]
|
||||
query_object.post_processing = []
|
||||
query_object.filter = [{"col": "other_col", "op": "==", "val": "value"}]
|
||||
|
||||
datasource = Mock()
|
||||
datasource.columns = [{"column_name": "ds", "is_dttm": True}]
|
||||
|
||||
self.factory._apply_granularity(query_object, {}, datasource)
|
||||
|
||||
assert query_object.filter == [{"col": "other_col", "op": "==", "val": "value"}]
|
||||
|
||||
def test_apply_granularity_with_adhoc_temporal_filter(self):
|
||||
"""An adhoc temporal filter is matched on its SQL expression."""
|
||||
adhoc_column = {"label": "ds_expr", "sqlExpression": "DATE(ds)"}
|
||||
query_object = Mock(spec=QueryObject)
|
||||
query_object.granularity = "P1D"
|
||||
query_object.columns = ["other_col"]
|
||||
query_object.post_processing = []
|
||||
query_object.filter = [
|
||||
{"col": adhoc_column, "op": "TEMPORAL_RANGE", "val": "a : b"},
|
||||
{"col": "DATE(ds)", "op": "TEMPORAL_RANGE", "val": "a : b"},
|
||||
]
|
||||
|
||||
datasource = Mock()
|
||||
datasource.columns = [{"column_name": "ds", "is_dttm": True}]
|
||||
|
||||
self.factory._apply_granularity(query_object, {}, datasource)
|
||||
|
||||
assert query_object.filter == [
|
||||
{"col": adhoc_column, "op": "TEMPORAL_RANGE", "val": "a : b"}
|
||||
]
|
||||
|
||||
def test_apply_filters_with_time_range(self):
|
||||
"""Test _apply_filters with time_range"""
|
||||
query_object = Mock(spec=QueryObject)
|
||||
|
||||
@@ -24,6 +24,7 @@ from superset.connectors.sqla.models import Database, SqlaTable
|
||||
from superset.daos.dashboard import DashboardDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.utils import json
|
||||
from tests.unit_tests.conftest import with_feature_flags
|
||||
|
||||
|
||||
@@ -117,3 +118,53 @@ def test_set_dash_metadata_preserves_soft_deleted_members(
|
||||
)
|
||||
# And the position slot kept its UUID rather than being nulled.
|
||||
assert positions["CHART-trashed"]["meta"]["uuid"] == str(trashed_chart.uuid)
|
||||
|
||||
|
||||
def test_set_dash_metadata_preserves_refresh_frequency(session: Session) -> None:
|
||||
"""set_dash_metadata must not reset refresh_frequency when absent from data.
|
||||
|
||||
Regression test for #42116: ``data.get("refresh_frequency", 0)`` would
|
||||
unconditionally overwrite the existing value with 0 whenever the caller
|
||||
did not include ``refresh_frequency`` in the data dict.
|
||||
"""
|
||||
Dashboard.metadata.create_all(session.get_bind())
|
||||
|
||||
dashboard = Dashboard(
|
||||
dashboard_title="refresh_test_dash",
|
||||
json_metadata=json.dumps({"refresh_frequency": 30}),
|
||||
)
|
||||
db.session.add(dashboard)
|
||||
db.session.flush()
|
||||
|
||||
# Simulate a save that does NOT include refresh_frequency
|
||||
# (e.g. changing only the title via the PropertiesModal).
|
||||
DashboardDAO.set_dash_metadata(dashboard, {"color_scheme": "superset"})
|
||||
|
||||
md = json.loads(dashboard.json_metadata)
|
||||
assert md["refresh_frequency"] == 30, (
|
||||
"refresh_frequency should be preserved when not present in data"
|
||||
)
|
||||
|
||||
|
||||
def test_set_dash_metadata_updates_refresh_frequency_when_present(
|
||||
session: Session,
|
||||
) -> None:
|
||||
"""set_dash_metadata must update refresh_frequency when it IS in data."""
|
||||
Dashboard.metadata.create_all(session.get_bind())
|
||||
|
||||
dashboard = Dashboard(
|
||||
dashboard_title="refresh_test_dash_2",
|
||||
json_metadata=json.dumps({"refresh_frequency": 30}),
|
||||
)
|
||||
db.session.add(dashboard)
|
||||
db.session.flush()
|
||||
|
||||
# Simulate a save that explicitly sets refresh_frequency to 0.
|
||||
DashboardDAO.set_dash_metadata(
|
||||
dashboard, {"refresh_frequency": 0, "color_scheme": "superset"}
|
||||
)
|
||||
|
||||
md = json.loads(dashboard.json_metadata)
|
||||
assert md["refresh_frequency"] == 0, (
|
||||
"refresh_frequency should be updated when present in data"
|
||||
)
|
||||
|
||||
@@ -571,3 +571,53 @@ def test_stringify_values_non_serializable_dict_falls_back_to_str() -> None:
|
||||
# Must not raise — falls back to str()
|
||||
result = stringify_values(data)
|
||||
assert result[0] == str({"key": _Unserializable()})
|
||||
|
||||
|
||||
def test_empty_result_set_preserves_column_metadata() -> None:
|
||||
"""
|
||||
Test that column metadata is preserved when query returns zero rows.
|
||||
|
||||
When a query returns no data but has a valid cursor description, the
|
||||
column names and types from cursor_description should be preserved
|
||||
in the result set. This allows downstream consumers (like the UI)
|
||||
to display column headers even for empty result sets.
|
||||
"""
|
||||
data: DbapiResult = []
|
||||
description = [
|
||||
("id", "int", None, None, None, None, True),
|
||||
("name", "varchar", None, None, None, None, True),
|
||||
("created_at", "timestamp", None, None, None, None, True),
|
||||
]
|
||||
|
||||
result_set = SupersetResultSet(
|
||||
data,
|
||||
description, # type: ignore
|
||||
BaseEngineSpec,
|
||||
)
|
||||
|
||||
# Verify column count
|
||||
assert len(result_set.columns) == 3
|
||||
|
||||
# Verify column names are preserved
|
||||
column_names = [col["column_name"] for col in result_set.columns]
|
||||
assert column_names == ["id", "name", "created_at"]
|
||||
|
||||
assert result_set.columns[0]["type"] == BaseEngineSpec.get_datatype(
|
||||
description[0][1]
|
||||
)
|
||||
assert result_set.columns[1]["type"] == BaseEngineSpec.get_datatype(
|
||||
description[1][1]
|
||||
)
|
||||
assert result_set.columns[2]["type"] == BaseEngineSpec.get_datatype(
|
||||
description[2][1]
|
||||
)
|
||||
|
||||
# Verify the PyArrow table has the correct schema
|
||||
assert result_set.table.num_rows == 0
|
||||
assert len(result_set.table.column_names) == 3
|
||||
assert list(result_set.table.column_names) == ["id", "name", "created_at"]
|
||||
|
||||
# Verify DataFrame conversion works
|
||||
df = result_set.to_pandas_df()
|
||||
assert len(df) == 0
|
||||
assert list(map(str, df.columns)) == ["id", "name", "created_at"]
|
||||
|
||||
Reference in New Issue
Block a user