mirror of
https://github.com/apache/superset.git
synced 2026-07-16 19:55:39 +00:00
Compare commits
6 Commits
refactor/e
...
chore/drop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c273b6d52 | ||
|
|
f9df4da8ae | ||
|
|
332fe2e74b | ||
|
|
4b07d43050 | ||
|
|
ef3a9e925e | ||
|
|
76bb5f8e69 |
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -50,9 +50,8 @@ body:
|
||||
attributes:
|
||||
label: Python version
|
||||
options:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- Not applicable
|
||||
- I don't know
|
||||
validations:
|
||||
|
||||
2
.github/actions/setup-backend/action.yml
vendored
2
.github/actions/setup-backend/action.yml
vendored
@@ -32,8 +32,6 @@ runs:
|
||||
elif [ "$INPUT_PYTHON_VERSION" = "next" ]; then
|
||||
# currently disabled in GHA matrixes because of library compatibility issues
|
||||
RESOLVED_VERSION="3.12"
|
||||
elif [ "$INPUT_PYTHON_VERSION" = "previous" ]; then
|
||||
RESOLVED_VERSION="3.10"
|
||||
elif printf '%s' "$INPUT_PYTHON_VERSION" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then
|
||||
RESOLVED_VERSION="$INPUT_PYTHON_VERSION"
|
||||
else
|
||||
|
||||
2
.github/workflows/bump-python-package.yml
vendored
2
.github/workflows/bump-python-package.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install uv
|
||||
run: pip install uv
|
||||
|
||||
11
.github/workflows/docker.yml
vendored
11
.github/workflows/docker.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
steps:
|
||||
- id: set_matrix
|
||||
run: |
|
||||
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["dev", "lean"]'; else echo '["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]'; fi)
|
||||
MATRIX_CONFIG=$(if [ "${{ github.event_name }}" == "pull_request" ]; then echo '["dev", "lean"]'; else echo '["dev", "lean", "websocket", "dockerize", "py311", "py312"]'; fi)
|
||||
echo "matrix_config=${MATRIX_CONFIG}" >> $GITHUB_OUTPUT
|
||||
echo $GITHUB_OUTPUT
|
||||
|
||||
@@ -123,13 +123,20 @@ jobs:
|
||||
# pull timeouts, 504/401 on push, ECONNRESET) that otherwise fail
|
||||
# the whole job. buildx reuses the buildkit layer cache from the
|
||||
# failed attempt, so a retry mostly re-does just the failed push.
|
||||
#
|
||||
# supersetbot's "dev"/"lean" presets pin their own --build-arg
|
||||
# PY_VER, which lands ahead of --extra-flags on the assembled
|
||||
# buildx command line; docker/buildx keeps the last value for a
|
||||
# repeated --build-arg key, so appending PY_VER here overrides
|
||||
# supersetbot's pin and keeps the build on the Dockerfile's own
|
||||
# supported Python version.
|
||||
for attempt in 1 2 3; do
|
||||
if supersetbot docker \
|
||||
$PUSH_OR_LOAD \
|
||||
--preset "$BUILD_PRESET" \
|
||||
--context "$EVENT" \
|
||||
--context-ref "$RELEASE" $FORCE_LATEST \
|
||||
--extra-flags "--build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
|
||||
--extra-flags "--build-arg PY_VER=3.11.14-slim-trixie --build-arg INCLUDE_CHROMIUM=false --tag $IMAGE_TAG" \
|
||||
$PLATFORM_ARG; then
|
||||
break
|
||||
fi
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
# Run the full version spread on push (master/release) and nightly,
|
||||
# but only the current version on PRs — lint/format/type results
|
||||
# rarely differ across patch versions, so 3x per PR is wasteful.
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
# Mirror the same matrix the release publisher uses so every variant
|
||||
# operators consume from Docker Hub gets the refreshed base.
|
||||
matrix:
|
||||
build_preset: ["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
|
||||
build_preset: ["dev", "lean", "websocket", "dockerize", "py311", "py312"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout release tag: ${{ needs.config.outputs.latest-release }}"
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
matrix:
|
||||
# Full version spread on push (master/release) + nightly; current only
|
||||
# on PRs to cut runner cost (cross-version breaks are caught at merge).
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["previous", "current", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: superset-extensions-cli
|
||||
|
||||
2
.github/workflows/superset-frontend.yml
vendored
2
.github/workflows/superset-frontend.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
git show -s --format=raw HEAD
|
||||
docker buildx build \
|
||||
-t $TAG \
|
||||
--cache-from=type=registry,ref=apache/superset-cache:3.10-slim-trixie \
|
||||
--cache-from=type=registry,ref=apache/superset-cache:3.11-slim-trixie \
|
||||
--target superset-node-ci \
|
||||
.
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
matrix:
|
||||
# Full version spread on push (master/release) + nightly; current only
|
||||
# on PRs to cut runner cost (cross-version breaks are caught at merge).
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "previous", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
SUPERSET_CONFIG: tests.integration_tests.superset_test_config
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
matrix:
|
||||
# Full version spread on push (master/release) + nightly; current only
|
||||
# on PRs to cut runner cost (cross-version breaks are caught at merge).
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["previous", "current", "next"]') }}
|
||||
python-version: ${{ github.event_name == 'pull_request' && fromJSON('["current"]') || fromJSON('["current", "next"]') }}
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
# Promotes the SQLAlchemy 2.0 deprecation warnings already locked in as
|
||||
|
||||
2
.github/workflows/tag-release.yml
vendored
2
.github/workflows/tag-release.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
build_preset:
|
||||
["dev", "lean", "py310", "websocket", "dockerize", "py311", "py312"]
|
||||
["dev", "lean", "websocket", "dockerize", "py311", "py312"]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
FROM python:3.10-slim-trixie
|
||||
FROM python:3.11-slim-trixie
|
||||
|
||||
RUN useradd --user-group --create-home --no-log-init --shell /bin/bash superset
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
FROM python:3.10-slim-trixie
|
||||
FROM python:3.11-slim-trixie
|
||||
|
||||
RUN useradd --user-group --create-home --no-log-init --shell /bin/bash superset
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
FROM python:3.10-slim-trixie
|
||||
FROM python:3.11-slim-trixie
|
||||
ARG VERSION
|
||||
|
||||
RUN git clone --depth 1 --branch ${VERSION} https://github.com/apache/superset.git /superset
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
FROM python:3.10-slim-trixie
|
||||
FROM python:3.11-slim-trixie
|
||||
|
||||
RUN apt-get update -y
|
||||
RUN apt-get install -y \
|
||||
|
||||
@@ -24,6 +24,13 @@ assists people when migrating to a new version.
|
||||
|
||||
## Next
|
||||
|
||||
### Python 3.10 support removed
|
||||
|
||||
Python 3.10 is no longer supported. Superset now requires **Python 3.11 or higher**.
|
||||
Update your environment (virtualenv, Docker base image, CI configuration, etc.) to
|
||||
Python 3.11+ before upgrading. The `apache/superset-cache:3.10-slim-trixie` and
|
||||
`py310` Docker image variants are no longer published.
|
||||
|
||||
### Owners, dashboard roles, and RLS roles replaced by Subjects
|
||||
|
||||
Superset now uses subject-based access assignments for dashboards, charts, datasets,
|
||||
|
||||
@@ -71,7 +71,7 @@ x-common-build: &common-build
|
||||
context: .
|
||||
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
|
||||
cache_from:
|
||||
- apache/superset-cache:3.10-slim-trixie
|
||||
- apache/superset-cache:3.11-slim-trixie
|
||||
args:
|
||||
DEV_MODE: "true"
|
||||
INCLUDE_CHROMIUM: ${INCLUDE_CHROMIUM:-false}
|
||||
|
||||
@@ -33,7 +33,7 @@ x-common-build: &common-build
|
||||
context: .
|
||||
target: dev
|
||||
cache_from:
|
||||
- apache/superset-cache:3.10-slim-trixie
|
||||
- apache/superset-cache:3.11-slim-trixie
|
||||
|
||||
services:
|
||||
redis:
|
||||
|
||||
@@ -38,7 +38,7 @@ x-common-build: &common-build
|
||||
context: .
|
||||
target: ${SUPERSET_BUILD_TARGET:-dev} # can use `dev` (default) or `lean`
|
||||
cache_from:
|
||||
- apache/superset-cache:3.10-slim-trixie
|
||||
- apache/superset-cache:3.11-slim-trixie
|
||||
args:
|
||||
DEV_MODE: "true"
|
||||
INCLUDE_CHROMIUM: ${INCLUDE_CHROMIUM:-false}
|
||||
|
||||
@@ -99,7 +99,7 @@ See [Connecting AI Clients](#connecting-ai-clients) for Claude Code, Claude Web,
|
||||
## Prerequisites
|
||||
|
||||
- Apache Superset 5.0+ running and accessible
|
||||
- Python 3.10+
|
||||
- Python 3.11+
|
||||
- The `fastmcp` package (`pip install fastmcp`)
|
||||
|
||||
---
|
||||
|
||||
@@ -379,7 +379,7 @@ functioning across environments.
|
||||
Make sure your machine meets the [OS dependencies](https://superset.apache.org/docs/installation/pypi#os-dependencies) before following these steps.
|
||||
You also need to install MySQL.
|
||||
|
||||
Ensure that you are using Python version 3.9, 3.10 or 3.11, then proceed with:
|
||||
Ensure that you are using Python version 3.11 or 3.12, then proceed with:
|
||||
|
||||
```bash
|
||||
# Create a virtual environment and activate it (recommended)
|
||||
|
||||
@@ -82,7 +82,7 @@ If applicable, add screenshots or recordings.
|
||||
|
||||
### Environment
|
||||
- Superset version: [e.g., 3.0.0]
|
||||
- Python version: [e.g., 3.9.7]
|
||||
- Python version: [e.g., 3.11.7]
|
||||
- Node version: [e.g., 18.17.0]
|
||||
- Database: [e.g., PostgreSQL 14]
|
||||
- Browser: [e.g., Chrome 120]
|
||||
@@ -125,7 +125,7 @@ No error messages in browser console or server logs.
|
||||
|
||||
### Environment
|
||||
- Superset version: 3.0.0
|
||||
- Python version: 3.9.16
|
||||
- Python version: 3.11.16
|
||||
- Database: PostgreSQL 14.9
|
||||
- Browser: Chrome 120.0.6099.71
|
||||
- OS: macOS 14.2
|
||||
|
||||
@@ -52,7 +52,7 @@ Everything you need to contribute to the Apache Superset project. This section i
|
||||
## Development Resources
|
||||
|
||||
### Prerequisites
|
||||
- **Python**: 3.9, 3.10, or 3.11
|
||||
- **Python**: 3.11 or 3.12
|
||||
- **Node.js**: 18.x or 20.x
|
||||
- **npm**: 9.x or 10.x
|
||||
- **Git**: Basic understanding
|
||||
|
||||
@@ -24,13 +24,12 @@ name = "apache_superset"
|
||||
description = "A modern, enterprise-ready business intelligence web application"
|
||||
readme = "README.md"
|
||||
dynamic = ["version", "scripts", "entry-points"]
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.11"
|
||||
license = { file="LICENSE.txt" }
|
||||
authors = [
|
||||
{ name = "Apache Software Foundation", email = "dev@superset.apache.org" },
|
||||
]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
]
|
||||
@@ -167,7 +166,6 @@ gevent = ["gevent>=26.4.0"]
|
||||
gsheets = ["shillelagh[gsheetsapi]>=1.4.4, <2"]
|
||||
hana = ["hdbcli==2.28.21", "sqlalchemy_hana==3.0.3"]
|
||||
hive = [
|
||||
"pyhive[hive]>=0.6.5;python_version<'3.11'",
|
||||
"pyhive[hive_pure_sasl]>=0.7.0",
|
||||
"tableschema",
|
||||
"thrift>=0.23.0, <1.0.0",
|
||||
@@ -201,7 +199,6 @@ singlestore = ["sqlalchemy-singlestoredb>=1.2.1, <2"]
|
||||
snowflake = ["snowflake-sqlalchemy>=1.10.2, <2"]
|
||||
sqlite = ["syntaqlite>=0.6.0,<0.7.0"]
|
||||
spark = [
|
||||
"pyhive[hive]>=0.6.5;python_version<'3.11'",
|
||||
"pyhive[hive_pure_sasl]>=0.7",
|
||||
"tableschema",
|
||||
"thrift>=0.23.0, <1",
|
||||
@@ -344,8 +341,8 @@ exclude = [
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
|
||||
# Assume Python 3.10
|
||||
target-version = "py310"
|
||||
# Assume Python 3.11
|
||||
target-version = "py311"
|
||||
|
||||
[tool.ruff.lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
|
||||
@@ -133,8 +133,8 @@ def main(docker: bool, frontend: bool, backend: bool) -> None: # noqa: C901
|
||||
requirements = [
|
||||
Requirement(
|
||||
"python",
|
||||
(Version("3.10.0"), Version("3.10.999")),
|
||||
(Version("3.9.0"), Version("3.11.999")),
|
||||
(Version("3.11.0"), Version("3.11.999")),
|
||||
(Version("3.11.0"), Version("3.12.999")),
|
||||
"backend",
|
||||
"python --version",
|
||||
),
|
||||
|
||||
@@ -21,7 +21,7 @@ under the License.
|
||||
|
||||
[](https://badge.fury.io/py/apache-superset-core)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
The official core package for building Apache Superset backend extensions and integrations. This package provides essential building blocks including base classes, API utilities, type definitions, and decorators for both the host application and extensions.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ authors = [
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE.txt"]
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.11"
|
||||
keywords = ["superset", "apache", "analytics", "business-intelligence", "extensions", "visualization"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
@@ -34,7 +34,6 @@ classifiers = [
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Database",
|
||||
|
||||
@@ -21,7 +21,7 @@ under the License.
|
||||
|
||||
[](https://badge.fury.io/py/apache-superset-extensions-cli)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://www.python.org/downloads/)
|
||||
[](https://www.python.org/downloads/)
|
||||
|
||||
Official command-line interface for building, bundling, and managing Apache Superset extensions. This CLI tool provides developers with everything needed to create, develop, and package extensions for the Superset ecosystem.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ authors = [
|
||||
]
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE.txt"]
|
||||
requires-python = ">=3.10"
|
||||
requires-python = ">=3.11"
|
||||
keywords = ["superset", "apache", "cli", "extensions", "analytics", "business-intelligence", "development-tools"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
@@ -33,7 +33,6 @@ classifiers = [
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Database",
|
||||
|
||||
@@ -1998,4 +1998,56 @@ describe('async actions', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
|
||||
describe('toggleLeftBar', () => {
|
||||
const activeId = 'active-qe';
|
||||
const makeState = (hideLeftBar: boolean, unsavedOverride?: boolean) => ({
|
||||
sqlLab: {
|
||||
tabHistory: [activeId],
|
||||
queryEditors: [{ id: activeId, hideLeftBar }],
|
||||
unsavedQueryEditor:
|
||||
unsavedOverride !== undefined
|
||||
? { id: activeId, hideLeftBar: unsavedOverride }
|
||||
: {},
|
||||
},
|
||||
});
|
||||
|
||||
test('dispatches QUERY_EDITOR_TOGGLE_LEFT_BAR when state differs', () => {
|
||||
const store = mockStore(makeState(false));
|
||||
store.dispatch(actions.toggleLeftBar(true));
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: actions.QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
queryEditorId: activeId,
|
||||
hideLeftBar: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('does not dispatch when state is already the same', () => {
|
||||
const store = mockStore(makeState(true));
|
||||
store.dispatch(actions.toggleLeftBar(true));
|
||||
expect(store.getActions()).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('uses unsavedQueryEditor state when available', () => {
|
||||
// qe in queryEditors says false, but unsaved override says true
|
||||
const store = mockStore(makeState(false, true));
|
||||
store.dispatch(actions.toggleLeftBar(true));
|
||||
expect(store.getActions()).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('does not dispatch when there is no active query editor', () => {
|
||||
const store = mockStore({
|
||||
sqlLab: {
|
||||
tabHistory: [],
|
||||
queryEditors: [],
|
||||
unsavedQueryEditor: {},
|
||||
},
|
||||
});
|
||||
store.dispatch(actions.toggleLeftBar(true));
|
||||
expect(store.getActions()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -956,12 +956,24 @@ export function setActiveSouthPaneTab(tabId: string): SqlLabAction {
|
||||
return { type: SET_ACTIVE_SOUTHPANE_TAB, tabId };
|
||||
}
|
||||
|
||||
export function toggleLeftBar(queryEditor: QueryEditor): SqlLabAction {
|
||||
const hideLeftBar = !queryEditor.hideLeftBar;
|
||||
return {
|
||||
type: QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
queryEditor,
|
||||
hideLeftBar,
|
||||
export function toggleLeftBar(shouldHide: boolean): SqlLabThunkAction {
|
||||
return (dispatch: AppDispatch, getState: GetState) => {
|
||||
const { sqlLab } = getState();
|
||||
const id = sqlLab.tabHistory.slice(-1)[0];
|
||||
if (!id) return;
|
||||
const qe = sqlLab.queryEditors.find(e => e.id === id);
|
||||
const merged =
|
||||
qe && sqlLab.unsavedQueryEditor?.id === id
|
||||
? { ...qe, ...sqlLab.unsavedQueryEditor }
|
||||
: qe;
|
||||
if (!merged) return;
|
||||
const isCurrentlyHidden = Boolean(merged.hideLeftBar);
|
||||
if (shouldHide === isCurrentlyHidden) return;
|
||||
dispatch({
|
||||
type: QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
queryEditorId: id,
|
||||
hideLeftBar: shouldHide,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { render, userEvent, waitFor } from 'spec/helpers/testing-library';
|
||||
import { initialState } from 'src/SqlLab/fixtures';
|
||||
import useStoredSidebarWidth from 'src/components/ResizableSidebar/useStoredSidebarWidth';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
import * as sqlLabActions from 'src/SqlLab/actions/sqlLab';
|
||||
import {
|
||||
registerTestView,
|
||||
cleanupExtensions,
|
||||
@@ -108,6 +109,46 @@ test('right sidebar is hidden when no extensions registered', () => {
|
||||
expect(queryByText('Right Sidebar Content')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('dispatches toggleLeftBar(true) when sidebar is resized to zero', async () => {
|
||||
const toggleLeftBarSpy = jest
|
||||
.spyOn(sqlLabActions, 'toggleLeftBar')
|
||||
.mockReturnValue({
|
||||
type: sqlLabActions.QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
} as any);
|
||||
const { getByRole } = render(<AppLayout {...defaultProps} />, {
|
||||
useRedux: true,
|
||||
initialState,
|
||||
});
|
||||
await userEvent.click(getByRole('button', { name: 'Resize to zero' }));
|
||||
await waitFor(() => expect(toggleLeftBarSpy).toHaveBeenCalledWith(true));
|
||||
toggleLeftBarSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('dispatches toggleLeftBar(false) when sidebar is resized to non-zero', async () => {
|
||||
const collapsedState = {
|
||||
...initialState,
|
||||
sqlLab: {
|
||||
...initialState.sqlLab,
|
||||
unsavedQueryEditor: {
|
||||
id: initialState.sqlLab.tabHistory[0],
|
||||
hideLeftBar: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const toggleLeftBarSpy = jest
|
||||
.spyOn(sqlLabActions, 'toggleLeftBar')
|
||||
.mockReturnValue({
|
||||
type: sqlLabActions.QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
} as any);
|
||||
const { getByRole } = render(<AppLayout {...defaultProps} />, {
|
||||
useRedux: true,
|
||||
initialState: collapsedState,
|
||||
});
|
||||
await userEvent.click(getByRole('button', { name: 'Resize' }));
|
||||
await waitFor(() => expect(toggleLeftBarSpy).toHaveBeenCalledWith(false));
|
||||
toggleLeftBarSpy.mockRestore();
|
||||
});
|
||||
|
||||
test('renders right sidebar when view is contributed at rightSidebar location', () => {
|
||||
registerTestView(
|
||||
ViewLocations.sqllab.rightSidebar,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { noop } from 'lodash-es';
|
||||
import type { SqlLabRootState } from 'src/SqlLab/types';
|
||||
import { css, styled } from '@apache-superset/core/theme';
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from 'src/SqlLab/constants';
|
||||
import { ViewLocations } from 'src/SqlLab/contributions';
|
||||
import ViewListExtension from 'src/components/ViewListExtension';
|
||||
import { toggleLeftBar } from 'src/SqlLab/actions/sqlLab';
|
||||
|
||||
import SqlEditorLeftBar from '../SqlEditorLeftBar';
|
||||
import StatusBar from '../StatusBar';
|
||||
@@ -66,6 +67,7 @@ const ContentWrapper = styled.div`
|
||||
`;
|
||||
|
||||
const AppLayout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const dispatch = useDispatch();
|
||||
const queryEditorId = useSelector<SqlLabRootState, string>(
|
||||
({ sqlLab: { tabHistory } }) => tabHistory.slice(-1)[0],
|
||||
);
|
||||
@@ -91,6 +93,7 @@ const AppLayout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
const onSidebarChange = (sizes: number[]) => {
|
||||
const [updatedWidth, _, possibleRightWidth] = sizes;
|
||||
setLeftWidth(updatedWidth);
|
||||
dispatch(toggleLeftBar(updatedWidth === 0));
|
||||
|
||||
if (typeof possibleRightWidth === 'number') {
|
||||
setRightWidth(possibleRightWidth);
|
||||
|
||||
@@ -121,6 +121,7 @@ import KeyboardShortcutButton, {
|
||||
KeyboardShortcut,
|
||||
} from '../KeyboardShortcutButton';
|
||||
import SqlEditorTopBar from '../SqlEditorTopBar';
|
||||
import SqlEditorLeftBar from '../SqlEditorLeftBar';
|
||||
|
||||
const bootstrapData = getBootstrapData();
|
||||
const scheduledQueriesConf = bootstrapData?.common?.conf?.SCHEDULED_QUERIES;
|
||||
@@ -240,34 +241,36 @@ const SqlEditor: FC<Props> = ({
|
||||
const theme = useTheme();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { database, latestQuery, currentQueryEditorId, hasSqlStatement } =
|
||||
useSelector<
|
||||
SqlLabRootState,
|
||||
{
|
||||
database?: DatabaseObject;
|
||||
latestQuery?: QueryResponse;
|
||||
hideLeftBar?: boolean;
|
||||
currentQueryEditorId: QueryEditor['id'];
|
||||
hasSqlStatement: boolean;
|
||||
}
|
||||
>(({ sqlLab: { unsavedQueryEditor, databases, queries, tabHistory } }) => {
|
||||
let { dbId, latestQueryId, hideLeftBar } = queryEditor;
|
||||
if (unsavedQueryEditor?.id === queryEditor.id) {
|
||||
dbId = unsavedQueryEditor.dbId || dbId;
|
||||
latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId;
|
||||
hideLeftBar =
|
||||
typeof unsavedQueryEditor.hideLeftBar === 'boolean'
|
||||
? unsavedQueryEditor.hideLeftBar
|
||||
: hideLeftBar;
|
||||
}
|
||||
return {
|
||||
hasSqlStatement: Boolean(queryEditor.sql?.trim().length > 0),
|
||||
database: databases[dbId || ''],
|
||||
latestQuery: queries[latestQueryId || ''],
|
||||
hideLeftBar,
|
||||
currentQueryEditorId: tabHistory.slice(-1)[0],
|
||||
};
|
||||
}, shallowEqual);
|
||||
const {
|
||||
database,
|
||||
latestQuery,
|
||||
hideLeftBar,
|
||||
currentQueryEditorId,
|
||||
hasSqlStatement,
|
||||
} = useSelector<
|
||||
SqlLabRootState,
|
||||
{
|
||||
database?: DatabaseObject;
|
||||
latestQuery?: QueryResponse;
|
||||
hideLeftBar?: boolean;
|
||||
currentQueryEditorId: QueryEditor['id'];
|
||||
hasSqlStatement: boolean;
|
||||
}
|
||||
>(({ sqlLab: { unsavedQueryEditor, databases, queries, tabHistory } }) => {
|
||||
let { dbId, latestQueryId, hideLeftBar } = queryEditor;
|
||||
if (unsavedQueryEditor?.id === queryEditor.id) {
|
||||
dbId = unsavedQueryEditor.dbId || dbId;
|
||||
latestQueryId = unsavedQueryEditor.latestQueryId || latestQueryId;
|
||||
hideLeftBar = unsavedQueryEditor.hideLeftBar === true;
|
||||
}
|
||||
return {
|
||||
hasSqlStatement: Boolean(queryEditor.sql?.trim().length > 0),
|
||||
database: databases[dbId || ''],
|
||||
latestQuery: queries[latestQueryId || ''],
|
||||
hideLeftBar,
|
||||
currentQueryEditorId: tabHistory.slice(-1)[0],
|
||||
};
|
||||
}, shallowEqual);
|
||||
|
||||
const logAction = useLogAction({ queryEditorId: queryEditor.id });
|
||||
const isActive = currentQueryEditorId === queryEditor.id;
|
||||
@@ -975,6 +978,11 @@ const SqlEditor: FC<Props> = ({
|
||||
queryEditorId={queryEditor.id}
|
||||
defaultPrimaryActions={renderEditorPrimaryAction()}
|
||||
defaultSecondaryActions={getSecondaryMenuItems()}
|
||||
extra={
|
||||
hideLeftBar && (
|
||||
<SqlEditorLeftBar queryEditorId={queryEditor.id} collapsed />
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{queryEditor.isDataset && renderDatasetWarning()}
|
||||
|
||||
@@ -39,6 +39,7 @@ import TableExploreTree from '../TableExploreTree';
|
||||
|
||||
export interface SqlEditorLeftBarProps {
|
||||
queryEditorId: string;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
const LeftBarStyles = styled.div`
|
||||
@@ -63,7 +64,10 @@ const StyledDivider = styled.div`
|
||||
margin: 0 -${({ theme }) => theme.sizeUnit * 2.5}px 0;
|
||||
`;
|
||||
|
||||
const SqlEditorLeftBar = ({ queryEditorId }: SqlEditorLeftBarProps) => {
|
||||
const SqlEditorLeftBar = ({
|
||||
queryEditorId,
|
||||
collapsed = false,
|
||||
}: SqlEditorLeftBarProps) => {
|
||||
const activeQEId = queryEditorId || EMPTY_STATE_QE_ID;
|
||||
const dbSelectorProps = useDatabaseSelector(activeQEId);
|
||||
const { db, catalog, schema, onDbChange, onCatalogChange, onSchemaChange } =
|
||||
@@ -180,29 +184,38 @@ const SqlEditorLeftBar = ({ queryEditorId }: SqlEditorLeftBarProps) => {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const dbSelectorTrigger = (
|
||||
<Popover
|
||||
content={popoverContent}
|
||||
open={selectorModalOpen}
|
||||
onOpenChange={open => !open && closeSelectorModal()}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
>
|
||||
{/* Wrap in a span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>
|
||||
<DatabaseSelector
|
||||
key={`db-selector-${db ? db.id : 'no-db'}:${catalog ?? 'no-catalog'}:${
|
||||
schema ?? 'no-schema'
|
||||
}`}
|
||||
{...dbSelectorProps}
|
||||
emptyState={<EmptyState />}
|
||||
sqlLabMode
|
||||
compactMode={collapsed}
|
||||
onOpenModal={openSelectorModal}
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
if (collapsed) {
|
||||
return dbSelectorTrigger;
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftBarStyles data-test="sql-editor-left-bar">
|
||||
<Popover
|
||||
content={popoverContent}
|
||||
open={selectorModalOpen}
|
||||
onOpenChange={open => !open && closeSelectorModal()}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
>
|
||||
{/* Wrap in a span so the Popover can attach a ref without relying
|
||||
on findDOMNode (deprecated in React 18+). */}
|
||||
<span>
|
||||
<DatabaseSelector
|
||||
key={`db-selector-${db ? db.id : 'no-db'}:${catalog ?? 'no-catalog'}:${
|
||||
schema ?? 'no-schema'
|
||||
}`}
|
||||
{...dbSelectorProps}
|
||||
emptyState={<EmptyState />}
|
||||
sqlLabMode
|
||||
onOpenModal={openSelectorModal}
|
||||
/>
|
||||
</span>
|
||||
</Popover>
|
||||
{dbSelectorTrigger}
|
||||
<StyledDivider />
|
||||
<TableExploreTree queryEditorId={activeQEId} />
|
||||
{shouldShowReset && (
|
||||
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
removeAllOtherQueryEditors,
|
||||
queryEditorSetTitle,
|
||||
cloneQueryToNewTab,
|
||||
toggleLeftBar,
|
||||
} from 'src/SqlLab/actions/sqlLab';
|
||||
import { QueryEditor, SqlLabRootState } from 'src/SqlLab/types';
|
||||
import { Icons, type IconType } from '@superset-ui/core/components/Icons';
|
||||
@@ -105,7 +104,6 @@ const SqlEditorTabHeader: FC<Props> = ({ queryEditor }) => {
|
||||
removeAllOtherQueryEditors,
|
||||
queryEditorSetTitle,
|
||||
cloneQueryToNewTab,
|
||||
toggleLeftBar,
|
||||
},
|
||||
dispatch,
|
||||
),
|
||||
|
||||
@@ -31,11 +31,13 @@ export interface SqlEditorTopBarProps {
|
||||
queryEditorId: string;
|
||||
defaultPrimaryActions: React.ReactNode;
|
||||
defaultSecondaryActions: MenuItemType[];
|
||||
extra?: React.ReactNode;
|
||||
}
|
||||
|
||||
const SqlEditorTopBar = ({
|
||||
defaultPrimaryActions,
|
||||
defaultSecondaryActions,
|
||||
extra,
|
||||
}: SqlEditorTopBarProps) => (
|
||||
<StyledFlex justify="space-between" gap="small" id="js-sql-toolbar">
|
||||
<Flex gap="small" align="center">
|
||||
@@ -47,6 +49,7 @@ const SqlEditorTopBar = ({
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{extra}
|
||||
</StyledFlex>
|
||||
);
|
||||
|
||||
|
||||
@@ -293,6 +293,45 @@ describe('sqlLabReducer', () => {
|
||||
newQueryEditor.tabViewId,
|
||||
);
|
||||
});
|
||||
test('should toggle hideLeftBar via queryEditorId for the active editor', () => {
|
||||
const action = {
|
||||
type: actions.QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
queryEditorId: qe!.id,
|
||||
hideLeftBar: true,
|
||||
};
|
||||
newState = sqlLabReducer(newState, action as SqlLabAction);
|
||||
expect(newState.unsavedQueryEditor.hideLeftBar).toBe(true);
|
||||
expect(newState.unsavedQueryEditor.id).toBe(qe!.id);
|
||||
});
|
||||
|
||||
test('should toggle hideLeftBar back to false via queryEditorId', () => {
|
||||
// first set to true
|
||||
newState = sqlLabReducer(newState, {
|
||||
type: actions.QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
queryEditorId: qe!.id,
|
||||
hideLeftBar: true,
|
||||
} as SqlLabAction);
|
||||
// then back to false
|
||||
newState = sqlLabReducer(newState, {
|
||||
type: actions.QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
queryEditorId: qe!.id,
|
||||
hideLeftBar: false,
|
||||
} as SqlLabAction);
|
||||
expect(newState.unsavedQueryEditor.hideLeftBar).toBe(false);
|
||||
});
|
||||
|
||||
test('should toggle hideLeftBar in queryEditors array for non-active editor', () => {
|
||||
const nonActiveId = defaultQueryEditor.id;
|
||||
const action = {
|
||||
type: actions.QUERY_EDITOR_TOGGLE_LEFT_BAR,
|
||||
queryEditorId: nonActiveId,
|
||||
hideLeftBar: true,
|
||||
};
|
||||
newState = sqlLabReducer(newState, action as SqlLabAction);
|
||||
const updated = newState.queryEditors.find(e => e.id === nonActiveId);
|
||||
expect(updated?.hideLeftBar).toBe(true);
|
||||
});
|
||||
|
||||
test('should clear the destroyed query editors', () => {
|
||||
const expectedQEId = '1233289';
|
||||
const action = {
|
||||
|
||||
@@ -715,7 +715,7 @@ export default function sqlLabReducer(
|
||||
{
|
||||
hideLeftBar: action.hideLeftBar,
|
||||
},
|
||||
action.queryEditor!.id!,
|
||||
action.queryEditorId!,
|
||||
),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -186,6 +186,7 @@ export function DatabaseSelector({
|
||||
onSchemaChange,
|
||||
schema,
|
||||
readOnly = false,
|
||||
compactMode = false,
|
||||
sqlLabMode = false,
|
||||
onOpenModal,
|
||||
}: DatabaseSelectorProps) {
|
||||
@@ -382,6 +383,7 @@ export function DatabaseSelector({
|
||||
});
|
||||
|
||||
const catalogOptions = catalogData || EMPTY_CATALOG_OPTIONS;
|
||||
const sqlLabCompactMode = sqlLabMode && compactMode;
|
||||
|
||||
function changeDatabase(
|
||||
value: { label: string; value: number },
|
||||
@@ -604,8 +606,8 @@ export function DatabaseSelector({
|
||||
>
|
||||
{renderDatabaseSelect()}
|
||||
{renderError()}
|
||||
{showCatalogSelector && renderCatalogSelect()}
|
||||
{showSchemaSelector && renderSchemaSelect()}
|
||||
{!sqlLabCompactMode && showCatalogSelector && renderCatalogSelect()}
|
||||
{!sqlLabCompactMode && showSchemaSelector && renderSchemaSelect()}
|
||||
</DatabaseSelectorWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,5 +50,6 @@ export interface DatabaseSelectorProps {
|
||||
schema?: string;
|
||||
readOnly?: boolean;
|
||||
sqlLabMode?: boolean;
|
||||
compactMode?: boolean;
|
||||
onOpenModal?: () => void;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function useStoredSidebarWidth(
|
||||
widthsMapRef.current =
|
||||
widthsMapRef.current ??
|
||||
getItem(LocalStorageKeys.CommonResizableSidebarWidths, {});
|
||||
if (widthsMapRef.current[id]) {
|
||||
if (typeof widthsMapRef.current[id] === 'number') {
|
||||
setSidebarWidth(widthsMapRef.current[id]);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
@@ -137,6 +137,7 @@ export function saveChartCustomization(
|
||||
dispatch({
|
||||
type: SET_NATIVE_FILTERS_CONFIG_COMPLETE,
|
||||
filterChanges: mergedResult,
|
||||
deletedIds,
|
||||
});
|
||||
|
||||
dispatch(
|
||||
|
||||
@@ -50,6 +50,7 @@ export interface SetNativeFiltersConfigComplete {
|
||||
filterChanges: Array<
|
||||
Filter | Divider | ChartCustomization | ChartCustomizationDivider
|
||||
>;
|
||||
deletedIds?: string[];
|
||||
}
|
||||
|
||||
export const SET_NATIVE_FILTERS_CONFIG_FAIL = 'SET_NATIVE_FILTERS_CONFIG_FAIL';
|
||||
@@ -92,6 +93,7 @@ export const setFilterConfiguration =
|
||||
dispatch({
|
||||
type: SET_NATIVE_FILTERS_CONFIG_COMPLETE,
|
||||
filterChanges: response.result,
|
||||
deletedIds: filterChanges.deleted,
|
||||
});
|
||||
dispatch(nativeFiltersConfigChanged(response.result));
|
||||
dispatch(setDataMaskForFilterChangesComplete(filterChanges, oldFilters));
|
||||
|
||||
@@ -287,13 +287,14 @@ test('SET_NATIVE_FILTERS_CONFIG_COMPLETE removes deleted filters from state', ()
|
||||
},
|
||||
};
|
||||
|
||||
// Backend response only includes filter1 and filter3 (filter2 was deleted)
|
||||
// filter2 was deleted: it's absent from the payload and listed in deletedIds
|
||||
const action = {
|
||||
type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof SET_NATIVE_FILTERS_CONFIG_COMPLETE,
|
||||
filterChanges: [
|
||||
createMockFilter('filter1', [1, 2], ['tab1']),
|
||||
createMockFilter('filter3', [5, 6], ['tab3']),
|
||||
],
|
||||
deletedIds: ['filter2'],
|
||||
};
|
||||
|
||||
const result = nativeFilterReducer(initialState, action);
|
||||
@@ -305,7 +306,7 @@ test('SET_NATIVE_FILTERS_CONFIG_COMPLETE removes deleted filters from state', ()
|
||||
expect(Object.keys(result.filters)).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('SET_NATIVE_FILTERS_CONFIG_COMPLETE removes all filters when backend returns empty array', () => {
|
||||
test('SET_NATIVE_FILTERS_CONFIG_COMPLETE removes all filters when all ids are deleted', () => {
|
||||
const initialState = {
|
||||
filters: {
|
||||
filter1: createMockFilter('filter1', [1, 2], ['tab1']),
|
||||
@@ -316,6 +317,7 @@ test('SET_NATIVE_FILTERS_CONFIG_COMPLETE removes all filters when backend return
|
||||
const action = {
|
||||
type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof SET_NATIVE_FILTERS_CONFIG_COMPLETE,
|
||||
filterChanges: [],
|
||||
deletedIds: ['filter1', 'filter2'],
|
||||
};
|
||||
|
||||
const result = nativeFilterReducer(initialState, action);
|
||||
@@ -324,6 +326,36 @@ test('SET_NATIVE_FILTERS_CONFIG_COMPLETE removes all filters when backend return
|
||||
expect(result.filters).toEqual({});
|
||||
});
|
||||
|
||||
test('SET_NATIVE_FILTERS_CONFIG_COMPLETE preserves untouched filters when payload omits them', () => {
|
||||
// Regression for the crash where saving a chart customization wiped native
|
||||
// filters: a customization-only payload (no deletedIds) must not drop the
|
||||
// native filters sharing the map.
|
||||
const initialState = {
|
||||
filters: {
|
||||
nativeFilter1: createMockFilter('nativeFilter1', [1, 2], ['tab1']),
|
||||
customization1: createMockChartCustomization(
|
||||
'customization1',
|
||||
[3, 4],
|
||||
['tab2'],
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof SET_NATIVE_FILTERS_CONFIG_COMPLETE,
|
||||
filterChanges: [
|
||||
createMockChartCustomization('customization1', [5, 6], ['tab3']),
|
||||
],
|
||||
};
|
||||
|
||||
const result = nativeFilterReducer(initialState, action);
|
||||
|
||||
expect(result.filters.nativeFilter1).toBeDefined();
|
||||
expect(result.filters.nativeFilter1.chartsInScope).toEqual([1, 2]);
|
||||
expect(result.filters.customization1.chartsInScope).toEqual([5, 6]);
|
||||
expect(Object.keys(result.filters)).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('SET_NATIVE_FILTERS_CONFIG_COMPLETE handles mixed Filter and ChartCustomization types', () => {
|
||||
const initialState = {
|
||||
filters: {
|
||||
@@ -360,13 +392,14 @@ test('SET_NATIVE_FILTERS_CONFIG_COMPLETE adds new filters while removing deleted
|
||||
},
|
||||
};
|
||||
|
||||
// Backend response: keep filter1, delete filter2, add filter3
|
||||
// keep filter1, delete filter2, add filter3
|
||||
const action = {
|
||||
type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof SET_NATIVE_FILTERS_CONFIG_COMPLETE,
|
||||
filterChanges: [
|
||||
createMockFilter('filter1', [1, 2], ['tab1']),
|
||||
createMockFilter('filter3', [5, 6], ['tab3']),
|
||||
],
|
||||
deletedIds: ['filter2'],
|
||||
};
|
||||
|
||||
const result = nativeFilterReducer(initialState, action);
|
||||
@@ -378,7 +411,7 @@ test('SET_NATIVE_FILTERS_CONFIG_COMPLETE adds new filters while removing deleted
|
||||
expect(Object.keys(result.filters)).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('SET_NATIVE_FILTERS_CONFIG_COMPLETE treats backend response as source of truth', () => {
|
||||
test('SET_NATIVE_FILTERS_CONFIG_COMPLETE merges payload and applies explicit deletions', () => {
|
||||
const initialState = {
|
||||
filters: {
|
||||
filter1: createMockFilter('filter1', [1, 2], ['tab1']),
|
||||
@@ -392,25 +425,28 @@ test('SET_NATIVE_FILTERS_CONFIG_COMPLETE treats backend response as source of tr
|
||||
},
|
||||
};
|
||||
|
||||
// Backend only returns filter2 and customization1
|
||||
// Payload updates filter2 and customization1; filter3 is explicitly deleted;
|
||||
// filter1 is untouched and must survive.
|
||||
const action = {
|
||||
type: SET_NATIVE_FILTERS_CONFIG_COMPLETE as typeof SET_NATIVE_FILTERS_CONFIG_COMPLETE,
|
||||
filterChanges: [
|
||||
createMockFilter('filter2', [10, 11], ['tab5']),
|
||||
createMockChartCustomization('customization1', [12, 13], ['tab6']),
|
||||
],
|
||||
deletedIds: ['filter3'],
|
||||
};
|
||||
|
||||
const result = nativeFilterReducer(initialState, action);
|
||||
|
||||
// Only filter2 and customization1 should remain
|
||||
expect(result.filters.filter1).toBeUndefined();
|
||||
expect(result.filters.filter1).toBeDefined();
|
||||
expect(result.filters.filter2).toBeDefined();
|
||||
expect(result.filters.filter3).toBeUndefined();
|
||||
expect(result.filters.customization1).toBeDefined();
|
||||
expect(Object.keys(result.filters)).toHaveLength(2);
|
||||
expect(Object.keys(result.filters)).toHaveLength(3);
|
||||
|
||||
// Values should be from backend response
|
||||
// Untouched filter keeps its previous values
|
||||
expect(result.filters.filter1.chartsInScope).toEqual([1, 2]);
|
||||
// Updated values come from the payload
|
||||
expect(result.filters.filter2.chartsInScope).toEqual([10, 11]);
|
||||
expect(result.filters.filter2.tabsInScope).toEqual(['tab5']);
|
||||
expect(result.filters.customization1.chartsInScope).toEqual([12, 13]);
|
||||
|
||||
@@ -76,12 +76,17 @@ function handleFilterChangesComplete(
|
||||
filters: Array<
|
||||
Filter | Divider | ChartCustomization | ChartCustomizationDivider
|
||||
>,
|
||||
deletedIds: string[] = [],
|
||||
) {
|
||||
// Create new filters object from backend response (deleted filters won't be included)
|
||||
// Native filters and chart customizations share this map, but each save path
|
||||
// only reports its own domain. Merge the incoming changes into the existing
|
||||
// map (instead of rebuilding from scratch) so, e.g., saving a chart
|
||||
// customization doesn't drop native filters and vice versa. Deletions are
|
||||
// applied explicitly via deletedIds.
|
||||
const newFilters: Record<
|
||||
string,
|
||||
Filter | Divider | ChartCustomization | ChartCustomizationDivider
|
||||
> = {};
|
||||
> = { ...state.filters };
|
||||
|
||||
filters.forEach(filter => {
|
||||
const existingFilter = state.filters[filter.id];
|
||||
@@ -96,6 +101,10 @@ function handleFilterChangesComplete(
|
||||
}
|
||||
});
|
||||
|
||||
deletedIds.forEach(id => {
|
||||
delete newFilters[id];
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
filters: newFilters,
|
||||
@@ -143,7 +152,11 @@ export default function nativeFilterReducer(
|
||||
return getInitialState({ filterConfig: action.filterConfig, state });
|
||||
|
||||
case SET_NATIVE_FILTERS_CONFIG_COMPLETE:
|
||||
return handleFilterChangesComplete(state, action.filterChanges);
|
||||
return handleFilterChangesComplete(
|
||||
state,
|
||||
action.filterChanges,
|
||||
action.deletedIds,
|
||||
);
|
||||
|
||||
case SET_FOCUSED_NATIVE_FILTER:
|
||||
return {
|
||||
|
||||
@@ -168,3 +168,10 @@ test('getRelatedChartsForChartCustomization falls back to ID match when type is
|
||||
getRelatedChartsForChartCustomization(customization, mixedSlices).sort(),
|
||||
).toEqual([10, 11]);
|
||||
});
|
||||
|
||||
test('getRelatedCharts returns empty array when the filter is undefined', () => {
|
||||
// A native filter can transiently disappear from the redux map (e.g. right
|
||||
// after saving a chart customization) while it is still hovered/focused.
|
||||
// Guard against reading .scope on undefined so the dashboard doesn't crash.
|
||||
expect(getRelatedCharts('missing', undefined, slices)).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -84,10 +84,13 @@ function getRelatedChartsForCrossFilter(
|
||||
|
||||
export function getRelatedCharts(
|
||||
filterKey: string,
|
||||
filter: AppliedNativeFilterType | AppliedCrossFilterType | Filter,
|
||||
filter: AppliedNativeFilterType | AppliedCrossFilterType | Filter | undefined,
|
||||
slices: Record<string, Slice>,
|
||||
) {
|
||||
let related: number[] = [];
|
||||
if (!filter) {
|
||||
return related;
|
||||
}
|
||||
const isCrossFilter =
|
||||
Object.keys(slices).includes(filterKey) && isAppliedCrossFilterType(filter);
|
||||
|
||||
|
||||
279
superset-websocket/package-lock.json
generated
279
superset-websocket/package-lock.json
generated
@@ -24,7 +24,7 @@
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^26.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.63.0",
|
||||
"@typescript-eslint/parser": "^8.63.0",
|
||||
"eslint": "^10.5.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
@@ -741,17 +741,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz",
|
||||
"integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz",
|
||||
"integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/type-utils": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"@typescript-eslint/scope-manager": "8.64.0",
|
||||
"@typescript-eslint/type-utils": "8.64.0",
|
||||
"@typescript-eslint/utils": "8.64.0",
|
||||
"@typescript-eslint/visitor-keys": "8.64.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -764,22 +764,22 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.63.0",
|
||||
"@typescript-eslint/parser": "^8.64.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz",
|
||||
"integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz",
|
||||
"integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"@typescript-eslint/scope-manager": "8.64.0",
|
||||
"@typescript-eslint/types": "8.64.0",
|
||||
"@typescript-eslint/typescript-estree": "8.64.0",
|
||||
"@typescript-eslint/visitor-keys": "8.64.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -794,145 +794,15 @@
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
|
||||
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.63.0",
|
||||
"@typescript-eslint/types": "^8.63.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
|
||||
"integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
|
||||
"integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
|
||||
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.63.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz",
|
||||
"integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
|
||||
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
|
||||
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz",
|
||||
"integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.63.0",
|
||||
"@typescript-eslint/types": "^8.63.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.64.0",
|
||||
"@typescript-eslint/types": "^8.64.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -947,14 +817,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
|
||||
"integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz",
|
||||
"integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0"
|
||||
"@typescript-eslint/types": "8.64.0",
|
||||
"@typescript-eslint/visitor-keys": "8.64.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -965,9 +835,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz",
|
||||
"integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -982,15 +852,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz",
|
||||
"integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0",
|
||||
"@typescript-eslint/types": "8.64.0",
|
||||
"@typescript-eslint/typescript-estree": "8.64.0",
|
||||
"@typescript-eslint/utils": "8.64.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -1007,9 +877,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
|
||||
"integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz",
|
||||
"integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1021,16 +891,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
|
||||
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz",
|
||||
"integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.63.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"@typescript-eslint/project-service": "8.64.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.64.0",
|
||||
"@typescript-eslint/types": "8.64.0",
|
||||
"@typescript-eslint/visitor-keys": "8.64.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -1049,16 +919,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
|
||||
"integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz",
|
||||
"integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0"
|
||||
"@typescript-eslint/scope-manager": "8.64.0",
|
||||
"@typescript-eslint/types": "8.64.0",
|
||||
"@typescript-eslint/typescript-estree": "8.64.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1073,13 +943,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz",
|
||||
"integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz",
|
||||
"integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/types": "8.64.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2991,41 +2861,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz",
|
||||
"integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==",
|
||||
"version": "8.64.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz",
|
||||
"integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz",
|
||||
"integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.62.1",
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/typescript-estree": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1",
|
||||
"debug": "^4.4.3"
|
||||
"@typescript-eslint/eslint-plugin": "8.64.0",
|
||||
"@typescript-eslint/parser": "8.64.0",
|
||||
"@typescript-eslint/typescript-estree": "8.64.0",
|
||||
"@typescript-eslint/utils": "8.64.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^26.1.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.62.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.63.0",
|
||||
"@typescript-eslint/parser": "^8.63.0",
|
||||
"eslint": "^10.5.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
|
||||
@@ -18,21 +18,12 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import cast, Iterable, Optional
|
||||
from wsgiref.types import StartResponse, WSGIApplication, WSGIEnvironment
|
||||
|
||||
from alembic.config import Config
|
||||
from alembic.runtime.migration import MigrationContext
|
||||
from alembic.script import ScriptDirectory
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from wsgiref.types import StartResponse, WSGIApplication, WSGIEnvironment
|
||||
else:
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment
|
||||
|
||||
from flask import Flask, Response
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ If Docker is not available, you can set up manually:
|
||||
git clone https://github.com/apache/superset.git
|
||||
cd superset
|
||||
|
||||
# 2. Set up Python environment (Python 3.10 or 3.11 required)
|
||||
# 2. Set up Python environment (Python 3.11 or 3.12 required)
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
|
||||
@@ -58,20 +58,12 @@ closed-set discipline.
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from typing import Iterable, Optional
|
||||
from urllib.parse import quote
|
||||
from wsgiref.types import StartResponse, WSGIApplication, WSGIEnvironment
|
||||
|
||||
from werkzeug.wrappers import Response
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from wsgiref.types import StartResponse, WSGIApplication, WSGIEnvironment
|
||||
else:
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment
|
||||
|
||||
#: The legacy URL token the shim recognises. Hard-coded — not configurable.
|
||||
_LEGACY_PREFIX: str = "/superset"
|
||||
|
||||
|
||||
@@ -14,13 +14,4 @@
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import sys
|
||||
from enum import Enum
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
# pylint: disable=unused-import
|
||||
from enum import StrEnum # nopycln: import
|
||||
else:
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
pass
|
||||
from enum import StrEnum # noqa: F401
|
||||
|
||||
Reference in New Issue
Block a user