mirror of
https://github.com/apache/superset.git
synced 2026-06-28 02:45:32 +00:00
Compare commits
1 Commits
master
...
chore/ci-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6162c90ee9 |
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -63,7 +63,7 @@ jobs:
|
||||
yarn install --immutable
|
||||
|
||||
- name: Cache pre-commit environments
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.cache/pre-commit
|
||||
key: pre-commit-v2-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('.pre-commit-config.yaml') }}
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
- name: Cache npm
|
||||
if: env.HAS_TAGS
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
- name: Cache npm
|
||||
if: env.HAS_TAGS
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
|
||||
|
||||
@@ -45,7 +45,6 @@ export const IconTooltip = forwardRef<HTMLElement, IconTooltipProps>(
|
||||
}}
|
||||
buttonStyle="link"
|
||||
className={`IconTooltip ${className}`}
|
||||
aria-label={tooltip ?? undefined}
|
||||
>
|
||||
{children}
|
||||
</Button>
|
||||
|
||||
@@ -2963,17 +2963,6 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
|
||||
tp = self.get_template_processor()
|
||||
processed_expression = self._process_expression_template(expression, tp)
|
||||
|
||||
# Apply the same parsing policy used for stored adhoc column and
|
||||
# metric expressions (single statement, no set operations, and no
|
||||
# sub-queries unless ALLOW_ADHOC_SUBQUERY is enabled), so expression
|
||||
# validation follows one policy across the query pipeline. Imported
|
||||
# locally to avoid a circular import with the connectors package.
|
||||
from superset.connectors.sqla.models import validate_stored_expression
|
||||
|
||||
validate_stored_expression(
|
||||
self.database, self.catalog, self.schema or "", processed_expression
|
||||
)
|
||||
|
||||
# Build validation query
|
||||
tbl, cte = self.get_from_clause(tp)
|
||||
validation_query = self._build_validation_query(
|
||||
|
||||
@@ -3164,6 +3164,9 @@ msgstr "Culoare după"
|
||||
msgid "Color for breakpoint"
|
||||
msgstr "Culoare pentru punct de întrerupere"
|
||||
|
||||
msgid "Color Metric"
|
||||
msgstr "Indicator culoare"
|
||||
|
||||
msgid "Color of the source location"
|
||||
msgstr "Culoarea locației sursă"
|
||||
|
||||
@@ -4641,7 +4644,7 @@ msgid "Deleted %(num)d theme"
|
||||
msgid_plural "Deleted %(num)d themes"
|
||||
msgstr[0] "Ștearsă %(num)d temă"
|
||||
msgstr[1] "Șterse %(num)d teme"
|
||||
msgstr[2] "Șterse %(num)d de teme"
|
||||
msgstr[2] "Șterse %(num)d de teme""
|
||||
|
||||
#, python-format
|
||||
msgid "Deleted %s"
|
||||
@@ -10215,7 +10218,7 @@ msgid "Saved expressions"
|
||||
msgstr "Expresii salvate"
|
||||
|
||||
msgid "Saved metric"
|
||||
msgstr "Indicator salvat"
|
||||
msgstr "Indicator salvat""
|
||||
|
||||
msgid "Saved queries"
|
||||
msgstr "Interogări salvate"
|
||||
@@ -13419,7 +13422,7 @@ msgid "This was triggered by:"
|
||||
msgid_plural "This may be triggered by:"
|
||||
msgstr[0] "Aceasta a fost declanșată de:"
|
||||
msgstr[1] "Acestea pot fi declanșate de:"
|
||||
msgstr[2] "Acestea pot fi declanșate de către:"
|
||||
msgstr[2] "Acestea pot fi declanșate de către:""
|
||||
|
||||
msgid ""
|
||||
"This will be applied to the whole table. Arrows (↑ and ↓) will be added "
|
||||
@@ -14444,7 +14447,7 @@ msgstr ""
|
||||
"Vizualizează un indicator corelat pentru perechi de grupuri. Hartile "
|
||||
"termice (Heatmaps) excelează în evidențierea corelației sau a "
|
||||
"intensității dintre două grupuri. Culoarea este utilizată pentru a "
|
||||
"sublinia intensitatea legăturii dintre fiecare pereche de grupuri."
|
||||
"sublinia intensitatea legăturii dintre fiecare pereche de grupuri.""
|
||||
|
||||
msgid ""
|
||||
"Visualize geospatial data like 3D buildings, landscapes, or objects in "
|
||||
|
||||
@@ -54,13 +54,6 @@ NUMPY_FUNCTIONS: dict[str, Callable[..., Any]] = {
|
||||
"var": np.var,
|
||||
}
|
||||
|
||||
# Operators that pandas GroupBy.agg accepts as string names. Passing the string
|
||||
# avoids a FutureWarning raised when pandas receives a numpy callable it internally
|
||||
# maps to its own method (e.g. np.mean → SeriesGroupBy.mean).
|
||||
_PANDAS_STRING_AGGREGATORS: frozenset[str] = frozenset(
|
||||
{"max", "mean", "median", "min", "prod", "std", "sum", "var"}
|
||||
)
|
||||
|
||||
DENYLIST_ROLLING_FUNCTIONS = (
|
||||
"count",
|
||||
"corr",
|
||||
@@ -173,7 +166,7 @@ def _get_aggregate_funcs(
|
||||
)
|
||||
operator = agg_obj["operator"]
|
||||
if callable(operator):
|
||||
aggfunc: str | Callable[..., Any] = operator
|
||||
aggfunc = operator
|
||||
else:
|
||||
func = NUMPY_FUNCTIONS.get(operator)
|
||||
if not func:
|
||||
@@ -184,10 +177,7 @@ def _get_aggregate_funcs(
|
||||
)
|
||||
)
|
||||
options = agg_obj.get("options", {})
|
||||
if not options and operator in _PANDAS_STRING_AGGREGATORS:
|
||||
aggfunc = operator
|
||||
else:
|
||||
aggfunc = partial(func, **options)
|
||||
aggfunc = partial(func, **options)
|
||||
agg_funcs[name] = NamedAgg(column=column, aggfunc=aggfunc)
|
||||
|
||||
return agg_funcs
|
||||
|
||||
@@ -440,9 +440,9 @@ class BaseViz: # pylint: disable=too-many-public-methods
|
||||
"metrics": metrics,
|
||||
"row_limit": row_limit,
|
||||
"filter": self.form_data.get("filters", []),
|
||||
"series_limit": limit,
|
||||
"timeseries_limit": limit,
|
||||
"extras": extras,
|
||||
"series_limit_metric": timeseries_limit_metric,
|
||||
"timeseries_limit_metric": timeseries_limit_metric,
|
||||
"order_desc": order_desc,
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ class TestValidateExpression:
|
||||
self.table.schema = "test_schema"
|
||||
self.table.catalog = None
|
||||
self.table.database = MagicMock()
|
||||
self.table.database.backend = "sqlite"
|
||||
self.table.database.db_engine_spec = MagicMock()
|
||||
self.table.database.db_engine_spec.make_sqla_column_compatible = lambda x, _: x
|
||||
self.table.columns = []
|
||||
@@ -106,8 +105,10 @@ class TestValidateExpression:
|
||||
|
||||
@patch("superset.connectors.sqla.models.SqlaTable._execute_validation_query")
|
||||
def test_validate_invalid_expression(self, mock_execute):
|
||||
"""Unparseable SQL is rejected by the shared expression parser before the
|
||||
validation query is built or executed."""
|
||||
"""Test validation of invalid SQL expressions"""
|
||||
# Mock _execute_validation_query to raise an exception
|
||||
mock_execute.side_effect = Exception("Invalid SQL syntax")
|
||||
|
||||
result = self.table.validate_expression(
|
||||
expression="INVALID SQL HERE",
|
||||
expression_type=SqlExpressionType.COLUMN,
|
||||
@@ -115,8 +116,7 @@ class TestValidateExpression:
|
||||
|
||||
assert result["valid"] is False
|
||||
assert len(result["errors"]) == 1
|
||||
assert result["errors"][0]["message"]
|
||||
mock_execute.assert_not_called()
|
||||
assert "Invalid SQL syntax" in result["errors"][0]["message"]
|
||||
|
||||
@patch("superset.connectors.sqla.models.SqlaTable._execute_validation_query")
|
||||
def test_validate_having_with_non_aggregated_column(self, mock_execute):
|
||||
@@ -152,38 +152,6 @@ class TestValidateExpression:
|
||||
# The actual error message will come from the exception
|
||||
assert "empty" in result["errors"][0]["message"].lower()
|
||||
|
||||
@patch("superset.models.helpers.is_feature_enabled", return_value=False)
|
||||
@patch("superset.connectors.sqla.models.SqlaTable._execute_validation_query")
|
||||
def test_validate_expression_rejects_subquery(
|
||||
self, mock_execute: MagicMock, mock_ff: MagicMock
|
||||
) -> None:
|
||||
"""A sub-query expression is rejected by the same validate_adhoc_subquery
|
||||
gate used for stored adhoc expressions, before any validation query is
|
||||
built or run (with ALLOW_ADHOC_SUBQUERY off, the default). Locks in that
|
||||
expression validation never executes the sub-query."""
|
||||
result = self.table.validate_expression(
|
||||
expression="(SELECT 1) IS NOT NULL OR 1 = 1",
|
||||
expression_type=SqlExpressionType.WHERE,
|
||||
)
|
||||
|
||||
assert result["valid"] is False
|
||||
mock_execute.assert_not_called()
|
||||
|
||||
@patch("superset.models.helpers.is_feature_enabled", return_value=False)
|
||||
@patch("superset.connectors.sqla.models.SqlaTable._execute_validation_query")
|
||||
def test_validate_expression_rejects_set_operation(
|
||||
self, mock_execute: MagicMock, mock_ff: MagicMock
|
||||
) -> None:
|
||||
"""A set-operation expression is rejected before the validation query is
|
||||
built or run, matching the stored-adhoc-expression policy."""
|
||||
result = self.table.validate_expression(
|
||||
expression="1 UNION SELECT 1",
|
||||
expression_type=SqlExpressionType.WHERE,
|
||||
)
|
||||
|
||||
assert result["valid"] is False
|
||||
mock_execute.assert_not_called()
|
||||
|
||||
@patch("superset.connectors.sqla.models.SqlaTable._execute_validation_query")
|
||||
def test_validate_expression_with_rls(self, mock_execute):
|
||||
"""Test that RLS filters are applied during validation"""
|
||||
|
||||
@@ -38,31 +38,3 @@ def test_aggregate():
|
||||
assert series_to_list(df["asc sum"])[0] == 5050
|
||||
assert series_to_list(df["asc q2"])[0] == 75
|
||||
assert series_to_list(df["desc q1"])[0] == 25
|
||||
|
||||
|
||||
def test_aggregate_string_operators():
|
||||
"""mean, median, and other operators in _PANDAS_STRING_AGGREGATORS use the
|
||||
pandas string path; verify results match expected values on asc_idx [0..100]."""
|
||||
aggregates = {
|
||||
"asc mean": {"column": "asc_idx", "operator": "mean"},
|
||||
"asc median": {"column": "asc_idx", "operator": "median"},
|
||||
"asc max": {"column": "asc_idx", "operator": "max"},
|
||||
"asc min": {"column": "asc_idx", "operator": "min"},
|
||||
}
|
||||
df = aggregate(df=categories_df, groupby=["constant"], aggregates=aggregates)
|
||||
assert series_to_list(df["asc mean"])[0] == 50.0
|
||||
assert series_to_list(df["asc median"])[0] == 50.0
|
||||
assert series_to_list(df["asc max"])[0] == 100
|
||||
assert series_to_list(df["asc min"])[0] == 0
|
||||
|
||||
|
||||
def test_aggregate_count_includes_nulls():
|
||||
"""'count' operator uses np.ma.count, which counts all rows including NaN.
|
||||
It is intentionally excluded from _PANDAS_STRING_AGGREGATORS to preserve this
|
||||
behavior (pandas SeriesGroupBy.count excludes NaN)."""
|
||||
aggregates = {
|
||||
"null_count": {"column": "idx_nulls", "operator": "count"},
|
||||
}
|
||||
df = aggregate(df=categories_df, groupby=["constant"], aggregates=aggregates)
|
||||
# idx_nulls has 101 rows total; np.ma.count returns all 101 (NaN included)
|
||||
assert series_to_list(df["null_count"])[0] == 101
|
||||
|
||||
Reference in New Issue
Block a user