mirror of
https://github.com/apache/superset.git
synced 2026-09-03 14:02:09 +00:00
villebro noted on #43390 that these system-generated tags appear to be unused. Confirmed: every tags list and filter in the frontend explicitly excludes non-custom tags (ChartList, DashboardList, SavedQueryList, the chart PropertiesModal, the dashboard Header), so nothing ever surfaced them to a user. What remained was pure write-side overhead: 13 SQLAlchemy event listeners across 5 models firing on every chart/ dashboard/query/dataset save and every favorite/unfavorite, plus a whole performance-optimization mixin (CustomTagsOptimizationMixin, DASHBOARD_LIST_CUSTOM_TAGS_ONLY) that existed purely to strip the resulting noise back out of dashboard-list responses. This removes the generation: - superset/tags/models.py: drop ObjectUpdater's editor:/type: generation (after_insert/after_update) and FavStarUpdater's favorited_by: generation entirely. Keeps after_delete (tagged_object cleanup applies to every tag, custom included, and nothing else removes those rows since tagged_object.object_id has no FK - see its column comment). - superset/tags/core.py: only registers the delete-cleanup listeners now. - superset/common/tags.py + the `sync_tags` CLI command: removed (the backfill path for the generation this removes). - superset/views/custom_tags_api_mixin.py, DASHBOARD_LIST_CUSTOM_TAGS_ONLY, Dashboard.custom_tags, and the schema/API plumbing built around them: removed - nothing left to optimize away once implicit tags stop accumulating. Kept for backward compatibility, since MCP's list_tags/get_tag_info tools document these tag types and upgraded deployments may already have rows of these types: the TagType enum values, the custom_tag API filter, and bulk-delete protection for non-custom tags. Docstrings updated to say these are legacy/no longer generated rather than actively implicit. Also fixes a real, currently-broken import in superset/daos/tag.py (current_user_can_modify_object doesn't live in superset.commands.tag.utils, only in superset.commands.utils) that otherwise blocks every test in this area from running at all. Filed and fixed separately as #43467; this commit will collapse away on rebase once that merges. Follow-up to #43390. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
153 lines
5.8 KiB
Python
153 lines
5.8 KiB
Python
# 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.
|
|
"""Unit tests for async query celery jobs in Superset"""
|
|
|
|
from unittest import mock
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from celery.exceptions import SoftTimeLimitExceeded
|
|
from parameterized import parameterized
|
|
|
|
from superset.async_events.cache_backend import (
|
|
RedisCacheBackend,
|
|
RedisSentinelCacheBackend,
|
|
)
|
|
from superset.commands.chart.data.get_data_command import ChartDataCommand
|
|
from superset.commands.chart.exceptions import ChartDataQueryFailedError
|
|
from superset.extensions import async_query_manager, security_manager
|
|
from tests.integration_tests.base_tests import SupersetTestCase
|
|
from tests.integration_tests.fixtures.birth_names_dashboard import (
|
|
load_birth_names_dashboard_with_slices, # noqa: F401
|
|
load_birth_names_data, # noqa: F401
|
|
)
|
|
from tests.integration_tests.fixtures.query_context import get_query_context
|
|
from tests.integration_tests.test_app import app
|
|
|
|
|
|
@pytest.mark.usefixtures(
|
|
"load_birth_names_data", "load_birth_names_dashboard_with_slices"
|
|
)
|
|
class TestAsyncQueries(SupersetTestCase):
|
|
@parameterized.expand(
|
|
[
|
|
("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)),
|
|
("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)),
|
|
]
|
|
)
|
|
@mock.patch("superset.tasks.async_queries.set_form_data")
|
|
@mock.patch.object(async_query_manager, "update_job")
|
|
def test_load_chart_data_into_cache(
|
|
self, cache_type, cache_backend, mock_update_job, mock_set_form_data
|
|
):
|
|
from superset.tasks.async_queries import load_chart_data_into_cache
|
|
|
|
app._got_first_request = False
|
|
|
|
async_query_manager.get_cache_backend = mock.Mock(return_value=cache_backend)
|
|
async_query_manager.init_app(app)
|
|
|
|
query_context = get_query_context("birth_names")
|
|
user = security_manager.find_user("gamma")
|
|
job_metadata = {
|
|
"channel_id": str(uuid4()),
|
|
"job_id": str(uuid4()),
|
|
"user_id": user.id,
|
|
"status": "pending",
|
|
"errors": [],
|
|
}
|
|
|
|
load_chart_data_into_cache(job_metadata, query_context)
|
|
|
|
mock_set_form_data.assert_called_once_with(query_context)
|
|
mock_update_job.assert_called_once_with(
|
|
job_metadata, "done", result_url=mock.ANY
|
|
)
|
|
|
|
@parameterized.expand(
|
|
[
|
|
("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)),
|
|
("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)),
|
|
]
|
|
)
|
|
@mock.patch.object(
|
|
ChartDataCommand, "run", side_effect=ChartDataQueryFailedError("Error: foo")
|
|
)
|
|
@mock.patch.object(async_query_manager, "update_job")
|
|
def test_load_chart_data_into_cache_error(
|
|
self, cache_type, cache_backend, mock_update_job, mock_run_command
|
|
):
|
|
from superset.tasks.async_queries import load_chart_data_into_cache
|
|
|
|
app._got_first_request = False
|
|
|
|
async_query_manager.get_cache_backend = mock.Mock(return_value=cache_backend)
|
|
async_query_manager.init_app(app)
|
|
|
|
query_context = get_query_context("birth_names")
|
|
user = security_manager.find_user("gamma")
|
|
job_metadata = {
|
|
"channel_id": str(uuid4()),
|
|
"job_id": str(uuid4()),
|
|
"user_id": user.id,
|
|
"status": "pending",
|
|
"errors": [],
|
|
}
|
|
with pytest.raises(ChartDataQueryFailedError):
|
|
load_chart_data_into_cache(job_metadata, query_context)
|
|
|
|
mock_run_command.assert_called_once_with(cache=True)
|
|
errors = [{"message": "Error: foo"}]
|
|
mock_update_job.assert_called_once_with(job_metadata, "error", errors=errors)
|
|
|
|
@parameterized.expand(
|
|
[
|
|
("RedisCacheBackend", mock.Mock(spec=RedisCacheBackend)),
|
|
("RedisSentinelCacheBackend", mock.Mock(spec=RedisSentinelCacheBackend)),
|
|
]
|
|
)
|
|
@mock.patch.object(ChartDataCommand, "run")
|
|
@mock.patch.object(async_query_manager, "update_job")
|
|
def test_soft_timeout_load_chart_data_into_cache(
|
|
self, cache_type, cache_backend, mock_update_job, mock_run_command
|
|
):
|
|
from superset.tasks.async_queries import load_chart_data_into_cache
|
|
|
|
app._got_first_request = False
|
|
|
|
async_query_manager.get_cache_backend = mock.Mock(return_value=cache_backend)
|
|
async_query_manager.init_app(app)
|
|
|
|
user = security_manager.find_user("gamma")
|
|
form_data = {}
|
|
job_metadata = {
|
|
"channel_id": str(uuid4()),
|
|
"job_id": str(uuid4()),
|
|
"user_id": user.id,
|
|
"status": "pending",
|
|
"errors": [],
|
|
}
|
|
errors = ["A timeout occurred while loading chart data"]
|
|
|
|
with pytest.raises(SoftTimeLimitExceeded): # noqa: PT012
|
|
with mock.patch(
|
|
"superset.tasks.async_queries.set_form_data"
|
|
) as set_form_data:
|
|
set_form_data.side_effect = SoftTimeLimitExceeded()
|
|
load_chart_data_into_cache(job_metadata, form_data)
|
|
set_form_data.assert_called_once_with(form_data, "error", errors=errors)
|