diff --git a/RESOURCES/STANDARD_ROLES.md b/RESOURCES/STANDARD_ROLES.md index 51fcc6479b6c4..e1734314f4d50 100644 --- a/RESOURCES/STANDARD_ROLES.md +++ b/RESOURCES/STANDARD_ROLES.md @@ -57,7 +57,6 @@ | can external metadata on Datasource | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O | | can save on Datasource | :heavy_check_mark: | :heavy_check_mark: | O | O | | can get on Datasource | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O | -| can shortner on R | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O | | can my queries on SqlLab | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | can log on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O | | can schemas access for csv upload on Superset | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | O | diff --git a/UPDATING.md b/UPDATING.md index cf4f846715aea..b239866221e36 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -41,6 +41,7 @@ assists people when migrating to a new version. - [26636](https://github.com/apache/superset/issues/26636): Sets the `DASHBOARD_VIRTUALIZATION` feature flag to `True` by default. This feature was introduced by [21438](https://github.com/apache/superset/pull/21438) and will enable virtualization when rendering a dashboard's charts in an attempt to reduce the number of elements (DOM nodes) rendered at once. This is especially useful for large dashboards. - [26637](https://github.com/apache/superset/issues/26637): Sets the `DRILL_BY` feature flag to `True` by default given that the feature has been tested for a while and reached a stable state. - [26462](https://github.com/apache/superset/issues/26462): Removes the Profile feature given that it's not actively maintained and not widely used. +- [26377](https://github.com/apache/superset/pull/26377): Removes the deprecated Redirect API that supported short URLs used before the permalink feature. ### Potential Downtime diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.jsx b/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.jsx index 8a4f5117187c0..359e58b5a8d93 100644 --- a/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.jsx +++ b/superset-frontend/src/dashboard/components/gridComponents/Tabs.test.jsx @@ -25,7 +25,6 @@ import { HTML5Backend } from 'react-dnd-html5-backend'; import { LineEditableTabs } from 'src/components/Tabs'; import { AntdModal } from 'src/components'; -import fetchMock from 'fetch-mock'; import { styledMount as mount } from 'spec/helpers/theming'; import DashboardComponent from 'src/dashboard/containers/DashboardComponent'; import DeleteComponentButton from 'src/dashboard/components/DeleteComponentButton'; @@ -40,8 +39,6 @@ import { nativeFilters } from 'spec/fixtures/mockNativeFilters'; import { initialState } from 'src/SqlLab/fixtures'; describe('Tabs', () => { - fetchMock.post('glob:*/r/shortener/', {}); - const props = { id: 'TABS_ID', parentId: DASHBOARD_ROOT_ID, diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 9da84c1fa323f..807f430ee44ab 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -183,7 +183,6 @@ def init_views(self) -> None: from superset.views.key_value import KV from superset.views.log.api import LogRestApi from superset.views.log.views import LogModelView - from superset.views.redirects import R from superset.views.sql_lab.views import ( SavedQueryView, SavedQueryViewApi, @@ -309,7 +308,6 @@ def init_views(self) -> None: appbuilder.add_view_no_menu(ExploreView) appbuilder.add_view_no_menu(ExplorePermalinkView) appbuilder.add_view_no_menu(KV) - appbuilder.add_view_no_menu(R) appbuilder.add_view_no_menu(SavedQueryView) appbuilder.add_view_no_menu(SavedQueryViewApi) appbuilder.add_view_no_menu(SliceAsync) diff --git a/superset/migrations/versions/2024-01-19_10-03_e863403c0c50_drop_url_table.py b/superset/migrations/versions/2024-01-19_10-03_e863403c0c50_drop_url_table.py new file mode 100644 index 0000000000000..49b320f13f6a9 --- /dev/null +++ b/superset/migrations/versions/2024-01-19_10-03_e863403c0c50_drop_url_table.py @@ -0,0 +1,45 @@ +# 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. +"""drop_url_table + +Revision ID: e863403c0c50 +Revises: 214f580d09c9 +Create Date: 2023-12-28 16:03:31.691033 + +""" + +# revision identifiers, used by Alembic. +revision = "e863403c0c50" +down_revision = "214f580d09c9" + +from importlib import import_module + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +module = import_module("superset.migrations.versions.2016-01-13_20-24_8e80a26a31db_") + + +def upgrade(): + module.downgrade() + + +def downgrade(): + module.upgrade() + op.alter_column("url", "changed_on", existing_type=sa.DATETIME(), nullable=True) + op.alter_column("url", "created_on", existing_type=sa.DATETIME(), nullable=True) diff --git a/superset/models/core.py b/superset/models/core.py index eece661ec5143..a8d0cdeb517ba 100755 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -90,14 +90,6 @@ DB_CONNECTION_MUTATOR = config["DB_CONNECTION_MUTATOR"] -class Url(Model, AuditMixinNullable): - """Used for the short url feature""" - - __tablename__ = "url" - id = Column(Integer, primary_key=True) - url = Column(Text) - - class KeyValue(Model): # pylint: disable=too-few-public-methods """Used for any type of key-value store""" diff --git a/superset/views/__init__.py b/superset/views/__init__.py index 1b8d1b8f09567..838e92aca231e 100644 --- a/superset/views/__init__.py +++ b/superset/views/__init__.py @@ -22,7 +22,6 @@ css_templates, dynamic_plugins, health, - redirects, sql_lab, tags, ) diff --git a/superset/views/redirects.py b/superset/views/redirects.py deleted file mode 100644 index 93a4339403109..0000000000000 --- a/superset/views/redirects.py +++ /dev/null @@ -1,74 +0,0 @@ -# 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. -import logging -from typing import Optional - -from flask import flash -from flask_appbuilder import expose -from werkzeug.utils import redirect - -from superset import db, event_logger -from superset.models import core as models -from superset.superset_typing import FlaskResponse -from superset.views.base import BaseSupersetView - -logger = logging.getLogger(__name__) - - -class R(BaseSupersetView): # pylint: disable=invalid-name - - """used for short urls""" - - @staticmethod - def _validate_explore_url(url: str) -> Optional[str]: - if url.startswith("//superset/explore/p/"): - return url - - if url.startswith("//superset/explore"): - return "/" + url[10:] # Remove /superset from old Explore URLs - - if url.startswith("//explore"): - return url - - return None - - @staticmethod - def _validate_dashboard_url(url: str) -> Optional[str]: - if url.startswith("//superset/dashboard/"): - return url - - return None - - @event_logger.log_this - @expose("/") - def index(self, url_id: int) -> FlaskResponse: - url = db.session.query(models.Url).get(url_id) - if url and url.url: - explore_url = self._validate_explore_url(url.url) - if explore_url: - if explore_url.startswith("//explore/?"): - explore_url = f"//explore/?r={url_id}" - return redirect(explore_url[1:]) - - dashboard_url = self._validate_dashboard_url(url.url) - if dashboard_url: - return redirect(dashboard_url[1:]) - - return redirect("/") - - flash("URL to nowhere...", "danger") - return redirect("/") diff --git a/superset/views/utils.py b/superset/views/utils.py index 574fedb66b7d0..bf1099a6aa42a 100644 --- a/superset/views/utils.py +++ b/superset/views/utils.py @@ -19,7 +19,6 @@ from collections import defaultdict from functools import wraps from typing import Any, Callable, DefaultDict, Optional, Union -from urllib import parse import msgpack import pyarrow as pa @@ -31,7 +30,6 @@ from sqlalchemy.exc import NoResultFound from werkzeug.wrappers.response import Response -import superset.models.core as models from superset import app, dataframe, db, result_set, viz from superset.common.db_query_status import QueryStatus from superset.daos.datasource import DatasourceDAO @@ -145,7 +143,7 @@ def loads_request_json(request_json_data: str) -> dict[Any, Any]: return {} -def get_form_data( # pylint: disable=too-many-locals +def get_form_data( slice_id: Optional[int] = None, use_slice_data: bool = False, initial_form_data: Optional[dict[str, Any]] = None, @@ -185,19 +183,6 @@ def get_form_data( # pylint: disable=too-many-locals json_data = form_data["queries"][0] if "queries" in form_data else {} form_data.update(json_data) - if has_request_context(): - url_id = request.args.get("r") - if url_id: - saved_url = db.session.query(models.Url).filter_by(id=url_id).first() - if saved_url: - url_str = parse.unquote_plus( - saved_url.url.split("?")[1][10:], encoding="utf-8" - ) - url_form_data = loads_request_json(url_str) - # allow form_date in request override saved url - url_form_data.update(form_data) - form_data = url_form_data - form_data = {k: v for k, v in form_data.items() if k not in REJECTED_FORM_DATA_KEYS} # When a slice_id is present, load from DB and override diff --git a/tests/integration_tests/core_tests.py b/tests/integration_tests/core_tests.py index 526a5592e9637..9e1a9ad11c825 100644 --- a/tests/integration_tests/core_tests.py +++ b/tests/integration_tests/core_tests.py @@ -413,17 +413,6 @@ def test_cache_logging(self): db.session.delete(ck) app.config["STORE_CACHE_KEYS_IN_METADATA_DB"] = store_cache_keys - def test_redirect_invalid(self): - model_url = models.Url(url="hhttp://invalid.com") - db.session.add(model_url) - db.session.commit() - - self.login(username="admin") - response = self.client.get(f"/r/{model_url.id}") - assert response.headers["Location"] == "/" - db.session.delete(model_url) - db.session.commit() - @with_feature_flags(KV_STORE=False) def test_kv_disabled(self): self.login(username="admin") diff --git a/tests/integration_tests/tags/commands_tests.py b/tests/integration_tests/tags/commands_tests.py index 48abfd31b4128..83762f8f6e876 100644 --- a/tests/integration_tests/tags/commands_tests.py +++ b/tests/integration_tests/tags/commands_tests.py @@ -112,6 +112,7 @@ def test_delete_tags_command(self): TaggedObject.object_id == example_dashboard.id, Tag.type == TagType.custom, ) + .order_by(Tag.name) .all() ) assert example_tags == [tag.name for tag in created_tags]