Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
f3bb8e0e0a chore(deps): bump docusaurus-plugin-openapi-docs in /docs
Bumps [docusaurus-plugin-openapi-docs](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/tree/HEAD/packages/docusaurus-plugin-openapi-docs) from 5.0.2 to 5.1.0.
- [Release notes](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/releases)
- [Changelog](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/commits/v5.1.0/packages/docusaurus-plugin-openapi-docs)

---
updated-dependencies:
- dependency-name: docusaurus-plugin-openapi-docs
  dependency-version: 5.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 07:04:50 +00:00
17 changed files with 36 additions and 262 deletions

View File

@@ -74,7 +74,7 @@
"antd": "^6.4.5",
"baseline-browser-mapping": "^2.10.38",
"caniuse-lite": "^1.0.30001799",
"docusaurus-plugin-openapi-docs": "^5.0.2",
"docusaurus-plugin-openapi-docs": "^5.1.0",
"docusaurus-theme-openapi-docs": "^5.0.2",
"js-yaml": "^5.1.0",
"js-yaml-loader": "^1.2.2",

View File

@@ -7163,10 +7163,10 @@ doctrine@^2.1.0:
dependencies:
esutils "^2.0.2"
docusaurus-plugin-openapi-docs@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.0.2.tgz#f00028621deb9179065fe7d6a541256692ef941b"
integrity sha512-WCC2m6PpylXZfNga+ScelTG0a7jUGtbB9+AmbR9lUj93FPryTs8VHTMJ3fKtO0senJTWgOU3MDvZw0v+mE3ztA==
docusaurus-plugin-openapi-docs@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/docusaurus-plugin-openapi-docs/-/docusaurus-plugin-openapi-docs-5.1.0.tgz#9732f81f45a5bc126bcafcb150332b7623ddece7"
integrity sha512-ocRemE3KmdhhPKaow5hja1m1NLIPfNlfRYFt7pja+nG26Wlp0MEC9ERS99gSWEWaxU+txDFBpUXsxo7nGlk8ZA==
dependencies:
"@apidevtools/json-schema-ref-parser" "^15.3.3"
"@redocly/openapi-core" "^2.25.2"

View File

@@ -101,7 +101,7 @@
"geostyler-openlayers-parser": "^5.7.1",
"geostyler-style": "11.0.2",
"geostyler-wfs-parser": "^3.0.1",
"google-auth-library": "^10.9.0",
"google-auth-library": "^10.7.0",
"immer": "^11.1.8",
"interweave": "^13.1.1",
"jquery": "^4.0.0",
@@ -202,7 +202,7 @@
"@types/json-bigint": "^1.0.4",
"@types/lodash-es": "^4.17.12",
"@types/mousetrap": "^1.6.15",
"@types/node": "^26.0.1",
"@types/node": "^26.0.0",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/react-loadable": "^5.5.11",
@@ -11706,9 +11706,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "26.0.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz",
"integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==",
"version": "26.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz",
"integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
@@ -22193,9 +22193,9 @@
}
},
"node_modules/google-auth-library": {
"version": "10.9.0",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz",
"integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==",
"version": "10.7.0",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.7.0.tgz",
"integrity": "sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
@@ -44345,7 +44345,7 @@
"@types/d3-time-format": "^4.0.3",
"@types/jquery": "^4.0.1",
"@types/lodash": "^4.17.24",
"@types/node": "^26.0.1",
"@types/node": "^26.0.0",
"@types/prop-types": "^15.7.15",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",

View File

@@ -184,7 +184,7 @@
"geostyler-openlayers-parser": "^5.7.1",
"geostyler-style": "11.0.2",
"geostyler-wfs-parser": "^3.0.1",
"google-auth-library": "^10.9.0",
"google-auth-library": "^10.7.0",
"immer": "^11.1.8",
"interweave": "^13.1.1",
"jquery": "^4.0.0",
@@ -284,7 +284,7 @@
"@types/js-levenshtein": "^1.1.3",
"@types/json-bigint": "^1.0.4",
"@types/mousetrap": "^1.6.15",
"@types/node": "^26.0.1",
"@types/node": "^26.0.0",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/react-loadable": "^5.5.11",

View File

@@ -78,7 +78,7 @@
"@types/d3-time-format": "^4.0.3",
"@types/jquery": "^4.0.1",
"@types/lodash": "^4.17.24",
"@types/node": "^26.0.1",
"@types/node": "^26.0.0",
"@types/prop-types": "^15.7.15",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/react-table": "^7.7.20",

View File

@@ -1132,49 +1132,4 @@ describe('ResultSet', () => {
});
expect(mockOpenInNewTab).not.toHaveBeenCalled();
});
test('filters the results table when typing in the search box', async () => {
const filterableQuery = {
...queries[0],
id: 'filterableQueryId',
cached: false,
results: {
columns: [{ is_dttm: false, column_name: 'fruit', type: 'STRING' }],
selected_columns: [
{ is_dttm: false, column_name: 'fruit', type: 'STRING' },
],
data: [{ fruit: 'apple' }, { fruit: 'banana' }],
},
};
const { getByTestId, getByPlaceholderText, queryByText } = setup(
{ ...mockedProps, cache: false, queryId: filterableQuery.id },
mockStore({
...initialState,
user,
sqlLab: {
...initialState.sqlLab,
queries: {
[filterableQuery.id]: filterableQuery,
},
},
}),
);
await waitFor(() => {
expect(getByTestId('table-container')).toBeInTheDocument();
});
// Both rows are visible before filtering
expect(queryByText('apple')).toBeInTheDocument();
expect(queryByText('banana')).toBeInTheDocument();
// Typing in the search box filters the rows (case-insensitive substring)
fireEvent.change(getByPlaceholderText('Filter results'), {
target: { value: 'APP' },
});
await waitFor(() => {
expect(queryByText('banana')).not.toBeInTheDocument();
});
expect(queryByText('apple')).toBeInTheDocument();
});
});

View File

@@ -127,28 +127,6 @@ describe('SaveDatasetModal', () => {
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
});
test('disables the save button when the dataset name is empty or whitespace-only', async () => {
renderModal();
const nameInput = screen.getByRole('textbox');
const saveBtn = screen.getByRole('button', { name: /save/i });
// Default name is present, so save starts enabled
expect(saveBtn).toBeEnabled();
// Clearing the name disables save
await userEvent.clear(nameInput);
await waitFor(() => expect(saveBtn).toBeDisabled());
// Whitespace-only name keeps save disabled
await userEvent.type(nameInput, ' ');
await waitFor(() => expect(saveBtn).toBeDisabled());
// A non-empty name re-enables save
await userEvent.type(nameInput, 'My dataset');
await waitFor(() => expect(saveBtn).toBeEnabled());
});
test('renders an overwrite button when "Overwrite existing" is selected', () => {
renderModal();
@@ -267,22 +245,6 @@ describe('SaveDatasetModal', () => {
});
});
test('trims surrounding whitespace from the dataset name on save', async () => {
renderModal();
const inputFieldText = screen.getByDisplayValue(/unimportant/i);
fireEvent.change(inputFieldText, { target: { value: ' my dataset ' } });
const saveConfirmationBtn = screen.getByRole('button', {
name: /save/i,
});
userEvent.click(saveConfirmationBtn);
expect(createDatasource).toHaveBeenCalledWith(
expect.objectContaining({ datasourceName: 'my dataset' }),
);
});
test('sends the catalog when creating the dataset', async () => {
renderModal({
datasource: { ...mockedProps.datasource, catalog: 'public' },

View File

@@ -371,7 +371,7 @@ export const SaveDatasetModal = ({
catalog: datasource?.catalog ?? null,
schema: datasource?.schema ?? '',
templateParams,
datasourceName: datasetName.trim(),
datasourceName: datasetName,
}),
)
.then((data: { id: number }) => {
@@ -417,7 +417,7 @@ export const SaveDatasetModal = ({
const disableSaveAndExploreBtn =
(newOrOverwrite === DatasetRadioState.SaveNew &&
datasetName.trim().length === 0) ||
datasetName.length === 0) ||
(newOrOverwrite === DatasetRadioState.OverwriteDataset &&
isEmpty(selectedDatasetToOverwrite));

View File

@@ -337,32 +337,4 @@ describe('SavedQuery', () => {
).not.toBeInTheDocument();
});
});
test('disables the save button when the query name is empty or whitespace-only', async () => {
render(<SaveQuery {...mockedProps} />, {
useRedux: true,
store: mockStore(mockState),
});
userEvent.click(screen.getByRole('button', { name: /save/i }));
const nameInput = screen.getAllByRole('textbox')[0] as HTMLInputElement;
const modalSaveBtn = () =>
screen.getAllByRole('button', { name: /save/i })[1];
// Default label is present, so the save button starts enabled
expect(modalSaveBtn()).toBeEnabled();
// Clearing the name disables the save button
userEvent.clear(nameInput);
await waitFor(() => expect(modalSaveBtn()).toBeDisabled());
// A whitespace-only name keeps the save button disabled
userEvent.type(nameInput, ' ');
await waitFor(() => expect(modalSaveBtn()).toBeDisabled());
// A non-empty name re-enables the save button
userEvent.type(nameInput, 'My query');
await waitFor(() => expect(modalSaveBtn()).toBeEnabled());
});
});

View File

@@ -112,7 +112,6 @@ const SaveQuery = ({
const [showSave, setShowSave] = useState<boolean>(false);
const [showSaveDatasetModal, setShowSaveDatasetModal] = useState(false);
const isSaved = !!query.remoteId;
const isLabelEmpty = label.trim().length === 0;
const canExploreDatabase = !!database?.allows_virtual_table_explore;
const shouldShowSaveButton =
database?.allows_virtual_table_explore !== undefined;
@@ -236,18 +235,12 @@ const SaveQuery = ({
<Button
buttonStyle={isSaved ? 'secondary' : 'primary'}
onClick={onSaveWrapper}
disabled={isLabelEmpty}
cta
>
{isSaved ? t('Save as new') : t('Save')}
</Button>
{isSaved && (
<Button
buttonStyle="primary"
onClick={onUpdateWrapper}
disabled={isLabelEmpty}
cta
>
<Button buttonStyle="primary" onClick={onUpdateWrapper} cta>
{t('Update')}
</Button>
)}

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { useMemo, useCallback, memo } from 'react';
import { useMemo, useCallback, useRef, memo } from 'react';
import { GridSize } from 'src/components/GridTable/constants';
import { GridTable } from 'src/components/GridTable';
import { type ColDef } from 'src/components/GridTable/types';
@@ -109,15 +109,15 @@ export const FilterableTable = ({
[orderedColumnKeys, allowHTML, getCellContent],
);
const keywordFilter = useCallback(
(node: { data: Datum }) => {
if (filterText && node.data) {
return hasMatch(filterText, node.data);
}
return true;
},
[filterText],
);
const keyword = useRef<string | undefined>(filterText);
keyword.current = filterText;
const keywordFilter = useCallback((node: { data: Datum }) => {
if (keyword.current && node.data) {
return hasMatch(keyword.current, node.data);
}
return true;
}, []);
return (
<div className="filterable-table-container" data-test="table-container">

View File

@@ -22,7 +22,7 @@
"@types/eslint__js": "^8.42.3",
"@types/jsonwebtoken": "^9.0.10",
"@types/lodash": "^4.17.24",
"@types/node": "^26.0.1",
"@types/node": "^26.0.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.62.0",
"@typescript-eslint/parser": "^8.62.0",
@@ -769,9 +769,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "26.0.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz",
"integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==",
"version": "26.0.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz",
"integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -30,7 +30,7 @@
"@types/eslint__js": "^8.42.3",
"@types/jsonwebtoken": "^9.0.10",
"@types/lodash": "^4.17.24",
"@types/node": "^26.0.1",
"@types/node": "^26.0.0",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.62.0",
"@typescript-eslint/parser": "^8.62.0",

View File

@@ -52,7 +52,6 @@ from superset.queries.saved_queries.schemas import (
get_delete_ids_schema,
get_export_ids_schema,
openapi_spec_methods_override,
validate_label,
)
from superset.utils import json
from superset.utils.core import sanitize_cookie_token
@@ -194,12 +193,8 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
allowed_rel_fields = {"database", "changed_by", "created_by"}
allowed_distinct_fields = {"catalog", "schema"}
validators_columns = {"label": validate_label}
def pre_add(self, item: SavedQuery) -> None:
item.user = g.user
if item.label:
item.label = item.label.strip()
def pre_update(self, item: SavedQuery) -> None:
self.pre_add(item)

View File

@@ -14,17 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from flask_babel import lazy_gettext as _
from marshmallow import fields, Schema, ValidationError
from marshmallow import fields, Schema
from marshmallow.validate import Length
def validate_label(value: str | None) -> None:
"""Reject blank or whitespace-only saved query labels."""
if value is None or not value.strip():
raise ValidationError(_("Label must not be empty."))
openapi_spec_methods_override = {
"get": {"get": {"summary": "Get a saved query"}},
"get_list": {

View File

@@ -677,69 +677,6 @@ class TestSavedQueryApi(SupersetTestCase):
db.session.delete(model)
db.session.commit()
def test_create_saved_query_blank_label(self) -> None:
"""
Saved Query API: Test create rejects blank/whitespace-only labels
"""
example_db = get_example_database()
self.login(ADMIN_USERNAME)
uri = "api/v1/saved_query/"
for label in ("", " ", "\t\n"):
post_data = {
"schema": "schema1",
"label": label,
"description": "some description",
"sql": "SELECT col1, col2 from table1",
"db_id": example_db.id,
}
rv = self.client.post(uri, json=post_data)
data = json.loads(rv.data.decode("utf-8"))
assert rv.status_code == 422
assert "label" in data["message"]
def test_create_saved_query_strips_label(self) -> None:
"""
Saved Query API: Test create trims surrounding whitespace from labels
"""
example_db = get_example_database()
self.login(ADMIN_USERNAME)
uri = "api/v1/saved_query/"
post_data = {
"schema": "schema1",
"label": " label1 ",
"description": "some description",
"sql": "SELECT col1, col2 from table1",
"db_id": example_db.id,
}
rv = self.client.post(uri, json=post_data)
data = json.loads(rv.data.decode("utf-8"))
assert rv.status_code == 201
model = db.session.query(SavedQuery).get(data.get("id"))
assert model.label == "label1"
# Rollback changes
db.session.delete(model)
db.session.commit()
@pytest.mark.usefixtures("create_saved_queries")
def test_update_saved_query_blank_label(self) -> None:
"""
Saved Query API: Test update rejects blank/whitespace-only labels
"""
saved_query = (
db.session.query(SavedQuery).filter(SavedQuery.label == "label1").all()[0]
)
self.login(ADMIN_USERNAME)
uri = f"api/v1/saved_query/{saved_query.id}"
rv = self.client.put(uri, json={"label": " "})
data = json.loads(rv.data.decode("utf-8"))
assert rv.status_code == 422
assert "label" in data["message"]
@pytest.mark.usefixtures("create_saved_queries")
def test_update_saved_query(self):
"""

View File

@@ -1,32 +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 pytest
from marshmallow import ValidationError
from superset.queries.saved_queries.schemas import validate_label
@pytest.mark.parametrize("value", ["", " ", "\t\n", None])
def test_validate_label_rejects_blank(value: str | None) -> None:
with pytest.raises(ValidationError):
validate_label(value)
@pytest.mark.parametrize("value", ["my query", " padded "])
def test_validate_label_accepts_non_blank(value: str) -> None:
# Should not raise for labels containing non-whitespace characters.
validate_label(value)