Compare commits

...

3 Commits

Author SHA1 Message Date
dependabot[bot]
ebdd3f29f7 chore(deps-dev): bump databricks-sqlalchemy from 1.0.5 to 2.0.9
Bumps [databricks-sqlalchemy](https://github.com/databricks/databricks-sqlalchemy) from 1.0.5 to 2.0.9.
- [Release notes](https://github.com/databricks/databricks-sqlalchemy/releases)
- [Changelog](https://github.com/databricks/databricks-sqlalchemy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/databricks/databricks-sqlalchemy/compare/v1.0.5...v2.0.9)

---
updated-dependencies:
- dependency-name: databricks-sqlalchemy
  dependency-version: 2.0.9
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-06 18:52:40 +00:00
Đỗ Trọng Hải
ad8e6d4140 build(deps): upgrade react-markdown to v9 and remark-gfm to v4 (#41778)
Signed-off-by: hainenber <dotronghai96@gmail.com>
2026-07-06 11:50:09 -07:00
marun
83d93b8b42 fix(view-query): Add ownState parameter to ViewQueryModal for query consistency (#35208)
Co-authored-by: Evan Rusackas <evan@preset.io>
2026-07-06 11:16:55 -07:00
13 changed files with 722 additions and 2148 deletions

View File

@@ -10,11 +10,7 @@ updates:
- package-ecosystem: "npm"
ignore:
- dependency-name: "react-error-boundary"
- dependency-name: "@rjsf/*"
# remark-gfm v4+ requires react-markdown v9+, which needs React 18
- dependency-name: "remark-gfm"
- dependency-name: "react-markdown"
# TODO: remove below entries until React >= 19.0.0
- dependency-name: "react-icons"
# JSDOM v30 doesn't play well with Jest v30
@@ -78,8 +74,6 @@ updates:
- package-ecosystem: "npm"
directory: "/docs/"
ignore:
- dependency-name: "react-error-boundary"
schedule:
interval: "daily"
open-pull-requests-limit: 10
@@ -376,11 +370,6 @@ updates:
- package-ecosystem: "npm"
directory: "/superset-frontend/packages/superset-ui-core/"
ignore:
# not until React >= 18.0.0
- dependency-name: "react-markdown"
- dependency-name: "remark-gfm"
- dependency-name: "react-error-boundary"
schedule:
interval: "daily"
labels:

View File

@@ -140,7 +140,7 @@ d1 = [
databend = ["databend-sqlalchemy>=0.3.2, <1.0"]
databricks = [
"databricks-sql-connector==4.2.6",
"databricks-sqlalchemy==1.0.5",
"databricks-sqlalchemy==2.0.9",
]
db2 = ["ibm-db-sa>0.3.8, <=0.4.4"]
denodo = ["denodo-sqlalchemy>=2.0.5,<2.1.0"]

File diff suppressed because it is too large Load Diff

View File

@@ -393,7 +393,6 @@
"http-proxy-middleware": "^2.0.10",
"tar": "^7.5.16",
"puppeteer": "^22.4.1",
"remark-gfm": "^3.0.1",
"underscore": "^1.13.7",
"jspdf": "^4.2.0",
"nwsapi": "^2.2.13",

View File

@@ -53,16 +53,16 @@
"re-resizable": "^6.11.2",
"react-ace": "^14.0.1",
"react-draggable": "^4.7.0",
"react-error-boundary": "6.0.0",
"react-error-boundary": "^6.1.2",
"react-js-cron": "^5.2.0",
"react-markdown": "^8.0.7",
"react-markdown": "^9.0.7",
"react-resize-detector": "^7.1.2",
"react-syntax-highlighter": "^16.1.1",
"react-ultimate-pagination": "^1.3.2",
"regenerator-runtime": "^0.14.1",
"rehype-raw": "^7.0.0",
"rehype-sanitize": "^6.0.0",
"remark-gfm": "^3.0.1",
"remark-gfm": "^4.0.1",
"reselect": "^5.2.0",
"rison": "^0.1.1",
"seedrandom": "^3.0.5",

View File

@@ -18,9 +18,6 @@
*/
import { useEffect, useMemo, useState } from 'react';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
// TODO: Upgrade to remark-gfm v4+ after migrating to React 18.
// remark-gfm v4+ requires react-markdown v9+, which requires React 18.
// Currently pinned to v3.0.1 for compatibility with react-markdown v8 and React 17.
import remarkGfm from 'remark-gfm';
import { cloneDeep, mergeWith } from 'lodash-es';
import { FeatureFlag, isFeatureEnabled } from '../../utils';
@@ -139,7 +136,7 @@ export function SafeMarkdown({
rehypePlugins={rehypePlugins}
remarkPlugins={[remarkGfm]}
skipHtml={false}
transformLinkUri={transformLinkUri}
urlTransform={transformLinkUri}
>
{source}
</ReactMarkdown>

View File

@@ -26,7 +26,12 @@ import {
useState,
} from 'react';
import { t } from '@apache-superset/core/translation';
import { getExtensionsRegistry, QueryData, VizType } from '@superset-ui/core';
import {
getExtensionsRegistry,
JsonObject,
QueryData,
VizType,
} from '@superset-ui/core';
import {
css,
styled,
@@ -58,6 +63,7 @@ type SliceHeaderProps = SliceHeaderControlsProps & {
filters: object;
handleToggleFullSize: () => void;
formData: object;
ownState?: JsonObject;
width: number;
height: number;
queriedDttm?: string | null;
@@ -174,6 +180,7 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
height,
exportPivotExcel = () => ({}),
chartHolderRef,
ownState,
},
ref,
) => {
@@ -381,6 +388,7 @@ const SliceHeader = forwardRef<HTMLDivElement, SliceHeaderProps>(
crossFiltersEnabled={isCrossFiltersEnabled}
exportPivotExcel={exportPivotExcel}
chartHolderRef={chartHolderRef}
ownState={ownState}
/>
)}
</>

View File

@@ -36,6 +36,7 @@ import {
getChartMetadataRegistry,
VizType,
BinaryQueryObjectFilterClause,
JsonObject,
QueryFormData,
} from '@superset-ui/core';
import { css, useTheme, styled } from '@apache-superset/core/theme';
@@ -140,6 +141,8 @@ export interface SliceHeaderControlsProps {
supersetCanDownload?: boolean;
crossFiltersEnabled?: boolean;
ownState?: JsonObject;
}
type SliceHeaderControlsPropsWithRouter = SliceHeaderControlsProps &
RouteComponentProps;
@@ -486,7 +489,12 @@ const SliceHeaderControls = (
<div data-test="view-query-menu-item">{t('View query')}</div>
}
modalTitle={t('View query')}
modalBody={<ViewQueryModal latestQueryFormData={props.formData} />}
modalBody={
<ViewQueryModal
latestQueryFormData={props.formData}
ownState={props.ownState}
/>
}
draggable
resizable
responsive

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import { QueryFormData } from '@superset-ui/core';
import { JsonObject, QueryFormData } from '@superset-ui/core';
export interface SliceHeaderControlsProps {
slice: {
@@ -60,4 +60,5 @@ export interface SliceHeaderControlsProps {
supersetCanDownload?: boolean;
crossFiltersEnabled?: boolean;
ownState?: JsonObject;
}

View File

@@ -480,6 +480,26 @@ const Chart = (props: ChartProps) => {
(formData as JsonObject).dashboardId = dashboardInfo.id;
// Memoize ownState so it keeps a stable reference across re-renders that
// don't change its logical value. ViewQueryModal depends on ownState; a fresh
// object on every render would refetch the query unnecessarily.
const ownState = useMemo(
() =>
createOwnStateWithChartState(
(dataMaskOwnState as JsonObject) || EMPTY_OBJECT,
{
state:
getChartStateWithFallback(
chartState as { state?: JsonObject } | undefined,
formData as JsonObject,
sliceVizType,
) ?? undefined,
},
sliceVizType,
),
[dataMaskOwnState, chartState, formData, sliceVizType],
);
const exportTable = useCallback(
async (format: string, isFullCSV: boolean, isPivot = false) => {
const logAction =
@@ -727,6 +747,7 @@ const Chart = (props: ChartProps) => {
height={getHeaderHeight()}
exportPivotExcel={exportPivotExcel as unknown as (arg0: string) => void}
chartHolderRef={props.chartHolderRef}
ownState={ownState}
/>
{/*
@@ -777,18 +798,7 @@ const Chart = (props: ChartProps) => {
formData={
formData as unknown as import('@superset-ui/core').QueryFormData
}
ownState={createOwnStateWithChartState(
(dataMask[props.id]?.ownState as JsonObject) || EMPTY_OBJECT,
{
state:
getChartStateWithFallback(
chartState as { state?: JsonObject } | undefined,
formData as JsonObject,
slice.viz_type,
) ?? undefined,
},
slice.viz_type,
)}
ownState={ownState}
queriesResponse={chart.queriesResponse ?? null}
timeout={timeout}
triggerQuery={chart.triggerQuery}

View File

@@ -19,6 +19,8 @@
import { screen, render, waitFor } from 'spec/helpers/testing-library';
import fetchMock from 'fetch-mock';
import * as chartAction from 'src/components/Chart/chartAction';
import type { ChartDataRequestResponse } from 'src/components/Chart/chartAction';
import ViewQueryModal from './ViewQueryModal';
const mockFormData = {
@@ -26,9 +28,19 @@ const mockFormData = {
viz_type: 'table',
};
// Minimal, type-correct response that satisfies ChartDataRequestResponse.
// A real Response instance avoids the 16 required Response fields that an
// empty object ({}) fails to overlap. The assertions only inspect the call
// arguments, never the resolved value's contents.
const mockChartDataResponse: ChartDataRequestResponse = {
response: new Response(),
json: { result: [] },
};
const chartDataEndpoint = 'glob:*/api/v1/chart/data*';
afterEach(() => {
jest.restoreAllMocks();
jest.resetAllMocks();
fetchMock.clearHistory().removeRoutes();
});
@@ -117,3 +129,91 @@ test('renders both Alert and SQL query when parsing error occurs', async () => {
{ timeout: 5000 },
);
});
test('passes ownState through to getChartDataRequest', async () => {
/**
* Regression test for PR #35208 - the ViewQueryModal must forward the
* chart's ownState (e.g. table search text, order_by) to the data request
* so that the displayed SQL reflects the same filters applied to the chart.
*/
const getChartDataRequestSpy = jest
.spyOn(chartAction, 'getChartDataRequest')
.mockResolvedValue(mockChartDataResponse);
const ownState = { searchText: 'foo', order_by: [['col', 'asc']] };
render(
<ViewQueryModal latestQueryFormData={mockFormData} ownState={ownState} />,
{ useRedux: true },
);
await waitFor(() => {
expect(getChartDataRequestSpy).toHaveBeenCalledTimes(1);
});
expect(getChartDataRequestSpy).toHaveBeenCalledWith(
expect.objectContaining({
formData: mockFormData,
ownState,
}),
);
});
test('strips clientView from ownState before the query request', async () => {
/**
* clientView holds the full client-side row/column snapshot (added by
* TableChart) and is irrelevant to SQL generation. It must be stripped
* before the request - matching ExploreViewContainer and Dashboard - to
* avoid bloating the payload (or triggering 413) on large tables.
*/
const getChartDataRequestSpy = jest
.spyOn(chartAction, 'getChartDataRequest')
.mockResolvedValue(mockChartDataResponse);
const ownState = {
searchText: 'foo',
// Simulate a large client-side snapshot that TableChart writes
clientView: { rows: [{ a: 1 }, { a: 2 }], columns: ['a'] },
};
render(
<ViewQueryModal latestQueryFormData={mockFormData} ownState={ownState} />,
{ useRedux: true },
);
await waitFor(() => {
expect(getChartDataRequestSpy).toHaveBeenCalledTimes(1);
});
const calledOwnState = getChartDataRequestSpy.mock.calls[0][0].ownState;
expect(calledOwnState).not.toHaveProperty('clientView');
expect(calledOwnState).toEqual(
expect.objectContaining({ searchText: 'foo' }),
);
});
test('falls back to empty ownState when prop is omitted', async () => {
/**
* Covers the `ownState || {}` fallback branch in ViewQueryModal - when no
* ownState is provided, the data request must still be called with an empty
* object rather than undefined, matching getChartDataRequest's contract.
*/
const getChartDataRequestSpy = jest
.spyOn(chartAction, 'getChartDataRequest')
.mockResolvedValue(mockChartDataResponse);
render(<ViewQueryModal latestQueryFormData={mockFormData} />, {
useRedux: true,
});
await waitFor(() => {
expect(getChartDataRequestSpy).toHaveBeenCalledTimes(1);
});
expect(getChartDataRequestSpy).toHaveBeenCalledWith(
expect.objectContaining({
formData: mockFormData,
ownState: {},
}),
);
});

View File

@@ -16,12 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import { FC, Fragment, useEffect, useState } from 'react';
import { FC, Fragment, useCallback, useEffect, useState } from 'react';
import { omit } from 'lodash';
import { t } from '@apache-superset/core/translation';
import {
ensureIsArray,
getClientErrorObject,
JsonObject,
QueryFormData,
} from '@superset-ui/core';
import { Alert } from '@apache-superset/core/components';
@@ -33,6 +35,7 @@ import ViewQuery from 'src/explore/components/controls/ViewQuery';
interface Props {
latestQueryFormData: QueryFormData;
ownState?: JsonObject;
}
type Result = {
@@ -48,38 +51,47 @@ const ViewQueryModalContainer = styled.div`
gap: ${({ theme }) => theme.sizeUnit * 4}px;
`;
const ViewQueryModal: FC<Props> = ({ latestQueryFormData }) => {
const ViewQueryModal: FC<Props> = ({ latestQueryFormData, ownState }) => {
const [result, setResult] = useState<Result[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const loadChartData = (resultType: string) => {
setIsLoading(true);
getChartDataRequest({
formData: latestQueryFormData,
resultFormat: 'json',
resultType,
})
.then(({ json }) => {
setResult(ensureIsArray(json.result) as Result[]);
setIsLoading(false);
setError(null);
const loadChartData = useCallback(
(resultType: string) => {
setIsLoading(true);
// Strip clientView (client-side row/column snapshot) from ownState before
// requesting the query, matching the chart query path in ExploreViewContainer
// and Dashboard's activeAllDashboardFilters. clientView is irrelevant to SQL
// generation and can bloat the payload (or trigger 413) on large tables.
const ownStateForQuery = omit(ownState, ['clientView']) || {};
getChartDataRequest({
formData: latestQueryFormData,
resultFormat: 'json',
resultType,
ownState: ownStateForQuery,
})
.catch(response => {
getClientErrorObject(response).then(({ error, message }) => {
setError(
error ||
message ||
response.statusText ||
t('Sorry, An error occurred'),
);
.then(({ json }) => {
setResult(ensureIsArray(json.result) as Result[]);
setIsLoading(false);
setError(null);
})
.catch(response => {
getClientErrorObject(response).then(({ error, message }) => {
setError(
error ||
message ||
response.statusText ||
t('Sorry, An error occurred'),
);
setIsLoading(false);
});
});
});
};
},
[latestQueryFormData, ownState],
);
useEffect(() => {
loadChartData('query');
}, [JSON.stringify(latestQueryFormData)]);
}, [loadChartData]);
if (isLoading) {
return <Loading />;

View File

@@ -1067,6 +1067,7 @@ export const useExploreAdditionalActionsMenu = (
modalBody={
<ViewQueryModal
latestQueryFormData={latestQueryFormData as QueryFormData}
ownState={ownState}
/>
}
draggable