mirror of
https://github.com/apache/superset.git
synced 2026-08-20 23:21:16 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c60b0a59c | ||
|
|
f046ef030c | ||
|
|
7c06c3a23f | ||
|
|
dc0b64754a | ||
|
|
8b22d466e3 | ||
|
|
148ffaff50 | ||
|
|
83c6ea4e03 | ||
|
|
1ca5e61f54 | ||
|
|
1ef12580a7 | ||
|
|
c0884c0f0c | ||
|
|
2ff79bd495 | ||
|
|
e7dccd44a7 | ||
|
|
fdd3945dde | ||
|
|
8b67fb4d30 | ||
|
|
b288db91f9 | ||
|
|
80bba12d0f | ||
|
|
5d4358a149 | ||
|
|
f9cedf84e2 |
@@ -1,6 +1,6 @@
|
||||
name: Bug report
|
||||
description: Report a bug to improve Superset's stability
|
||||
labels: ["bug"]
|
||||
labels: ["#bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Cosmetic Issue
|
||||
about: Describe a cosmetic issue with CSS, positioning, layout, labeling, or similar
|
||||
labels: "cosmetic-issue"
|
||||
labels: "#bug:cosmetic"
|
||||
---
|
||||
|
||||
## Screenshot
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -78,6 +78,6 @@ jobs:
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^17.10.0",
|
||||
"globals": "^17.11.0",
|
||||
"oxfmt": "^0.63.0",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.67.0",
|
||||
|
||||
+4
-4
@@ -9174,10 +9174,10 @@ globals@^14.0.0:
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e"
|
||||
integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
|
||||
|
||||
globals@^17.10.0:
|
||||
version "17.10.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-17.10.0.tgz#f9dbd847ae99e236f98b13095e2426ac3b25a45c"
|
||||
integrity sha512-V0kztuWST2k8A/VbxAY8+L+7+Rgo3fyA24IHRLrZp7HOzJjV0gHSaZUjK9lpP/IrBSNite2tZ1prhRkinRu1CA==
|
||||
globals@^17.11.0:
|
||||
version "17.11.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-17.11.0.tgz#d643485bb30220d7751e511cf4f68c73d3870d87"
|
||||
integrity sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==
|
||||
|
||||
globalthis@^1.0.4:
|
||||
version "1.0.4"
|
||||
|
||||
@@ -93,7 +93,7 @@ def find_models(module: ModuleType) -> list[type[Model]]: # noqa: C901
|
||||
# where the current model is out-of-sync with the existing table after a
|
||||
# downgrade
|
||||
sqlalchemy_uri = current_app.config["SQLALCHEMY_DATABASE_URI"]
|
||||
engine = create_engine(sqlalchemy_uri, future=True)
|
||||
engine = create_engine(sqlalchemy_uri)
|
||||
Base = automap_base() # noqa: N806
|
||||
Base.prepare(engine, reflect=True)
|
||||
seen = set()
|
||||
|
||||
Generated
+6
-15
@@ -99,7 +99,7 @@
|
||||
"geostyler-openlayers-parser": "^5.7.1",
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^11.0.1",
|
||||
"google-auth-library": "^11.0.2",
|
||||
"immer": "^11.1.16",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
@@ -20612,7 +20612,7 @@
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/expect-playwright/-/expect-playwright-0.8.0.tgz",
|
||||
"integrity": "sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==",
|
||||
"deprecated": "\u26a0\ufe0f The 'expect-playwright' package is deprecated. The Playwright core assertions (via @playwright/test) now cover the same functionality. Please migrate to built-in expect. See https://playwright.dev/docs/test-assertions for migration.",
|
||||
"deprecated": "⚠️ The 'expect-playwright' package is deprecated. The Playwright core assertions (via @playwright/test) now cover the same functionality. Please migrate to built-in expect. See https://playwright.dev/docs/test-assertions for migration.",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -22783,9 +22783,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/google-auth-library": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-11.0.1.tgz",
|
||||
"integrity": "sha512-ZqfaYduu9ASUaFuUk5dF9g9QvufdhhSj7jFiEnCrTQcH57sFPKYetM0iU4dcKkQk6CqC1xpSrVr5uQ9NhqjNOg==",
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-11.0.2.tgz",
|
||||
"integrity": "sha512-vzpgPutxrghPsnjrjpzLX2bdv8IOL719Rh0oEjGnQu8YCIbnbMuTTQ5zU9LcKvLdOPgCxBwppbvnhgW90Qna5Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.0",
|
||||
@@ -26023,7 +26023,7 @@
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.4.0.tgz",
|
||||
"integrity": "sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw==",
|
||||
"deprecated": "\u26a0\ufe0f The 'jest-process-manager' package is deprecated. Please migrate to Playwright's built-in test runner (@playwright/test) which now includes full Jest-style features and parallel testing. See https://playwright.dev/docs/intro for details.",
|
||||
"deprecated": "⚠️ The 'jest-process-manager' package is deprecated. Please migrate to Playwright's built-in test runner (@playwright/test) which now includes full Jest-style features and parallel testing. See https://playwright.dev/docs/intro for details.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -43073,15 +43073,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"packages/superset-ui-core/node_modules/dompurify": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
|
||||
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"packages/superset-ui-core/node_modules/react-ace": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-ace/-/react-ace-14.0.1.tgz",
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
"geostyler-openlayers-parser": "^5.7.1",
|
||||
"geostyler-style": "11.0.2",
|
||||
"geostyler-wfs-parser": "^3.0.1",
|
||||
"google-auth-library": "^11.0.1",
|
||||
"google-auth-library": "^11.0.2",
|
||||
"immer": "^11.1.16",
|
||||
"interweave": "^13.1.1",
|
||||
"jquery": "^4.0.0",
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
LegendOrientation,
|
||||
Refs,
|
||||
} from '../types';
|
||||
import { BarValueLabelPosition } from '../Timeseries/types';
|
||||
import { parseAxisBound } from '../utils/controls';
|
||||
import { safeParseEChartOptions } from '../utils/safeEChartOptionsParser';
|
||||
import {
|
||||
@@ -512,6 +513,7 @@ export default function transformProps(
|
||||
areaOpacity: opacity,
|
||||
seriesType,
|
||||
showValue,
|
||||
valueLabelPosition: BarValueLabelPosition.OutsideEnd,
|
||||
onlyTotal,
|
||||
stack: Boolean(stack),
|
||||
stackIdSuffix: '\na',
|
||||
@@ -600,6 +602,7 @@ export default function transformProps(
|
||||
areaOpacity: opacityB,
|
||||
seriesType: seriesTypeB,
|
||||
showValue: showValueB,
|
||||
valueLabelPosition: BarValueLabelPosition.OutsideEnd,
|
||||
onlyTotal: onlyTotalB,
|
||||
stack: Boolean(stackB),
|
||||
stackIdSuffix: '\nb',
|
||||
|
||||
+2
@@ -34,6 +34,7 @@ import {
|
||||
} from '../types';
|
||||
import EchartsTimeseries from './EchartsTimeseries';
|
||||
import {
|
||||
BarValueLabelPosition,
|
||||
EchartsTimeseriesSeriesType,
|
||||
OrientationType,
|
||||
type EchartsTimeseriesFormData,
|
||||
@@ -146,6 +147,7 @@ const defaultFormData: EchartsTimeseriesFormData & {
|
||||
xAxisLabelRotation: 0,
|
||||
xAxisLabelInterval: 0,
|
||||
showValue: false,
|
||||
valueLabelPosition: BarValueLabelPosition.Auto,
|
||||
onlyTotal: false,
|
||||
showExtraControls: true,
|
||||
percentageThreshold: 0,
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { LegendOrientation, LegendType } from '../types';
|
||||
import {
|
||||
BarValueLabelPosition,
|
||||
OrientationType,
|
||||
EchartsTimeseriesSeriesType,
|
||||
EchartsTimeseriesFormData,
|
||||
@@ -86,6 +87,7 @@ export const DEFAULT_FORM_DATA: EchartsTimeseriesFormData = {
|
||||
xAxisLabelInterval: defaultXAxis.xAxisLabelInterval,
|
||||
groupby: [],
|
||||
showValue: false,
|
||||
valueLabelPosition: BarValueLabelPosition.Auto,
|
||||
onlyTotal: false,
|
||||
percentageThreshold: 0,
|
||||
orientation: OrientationType.Vertical,
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
NumberFormats,
|
||||
} from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/common';
|
||||
import { isThemeDark } from '@apache-superset/core/theme';
|
||||
import {
|
||||
extractExtraMetrics,
|
||||
getOriginalSeries,
|
||||
@@ -63,6 +64,7 @@ import {
|
||||
EchartsTimeseriesChartProps,
|
||||
EchartsTimeseriesFormData,
|
||||
EchartsTimeseriesSeriesType,
|
||||
BarValueLabelPosition,
|
||||
OrientationType,
|
||||
TimeseriesChartTransformedProps,
|
||||
} from './types';
|
||||
@@ -290,6 +292,7 @@ export default function transformProps(
|
||||
seriesType,
|
||||
showLegend,
|
||||
showValue,
|
||||
valueLabelPosition,
|
||||
size,
|
||||
colorByPrimaryAxis,
|
||||
sliceId,
|
||||
@@ -327,6 +330,8 @@ export default function transformProps(
|
||||
zoomable,
|
||||
stackDimension,
|
||||
}: EchartsTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
||||
const resolvedValueLabelPosition =
|
||||
valueLabelPosition ?? BarValueLabelPosition.Auto;
|
||||
|
||||
const refs: Refs = {};
|
||||
const groupBy = ensureIsArray(groupby);
|
||||
@@ -737,6 +742,7 @@ export default function transformProps(
|
||||
labelMap?.[seriesName]?.[0],
|
||||
) ?? defaultFormatter),
|
||||
showValue,
|
||||
valueLabelPosition: resolvedValueLabelPosition,
|
||||
onlyTotal,
|
||||
totalStackedValues: sortedTotalValues,
|
||||
showValueIndexes,
|
||||
@@ -1370,6 +1376,10 @@ export default function transformProps(
|
||||
|
||||
const echartOptions: EChartsCoreOption = {
|
||||
useUTC: true,
|
||||
...(seriesType === EchartsTimeseriesSeriesType.Bar &&
|
||||
resolvedValueLabelPosition === BarValueLabelPosition.Auto
|
||||
? { darkMode: isThemeDark(theme) }
|
||||
: {}),
|
||||
grid: {
|
||||
...defaultGrid,
|
||||
...padding,
|
||||
|
||||
@@ -35,6 +35,9 @@ import type {
|
||||
CallbackDataParams,
|
||||
DefaultStatesMixin,
|
||||
ItemStyleOption,
|
||||
LabelLayoutOption,
|
||||
LabelLayoutOptionCallback,
|
||||
LabelLayoutOptionCallbackParams,
|
||||
LineStyleOption,
|
||||
OptionName,
|
||||
SeriesLabelOption,
|
||||
@@ -49,6 +52,7 @@ import type {
|
||||
import type { MarkLine1DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
|
||||
import { extractForecastSeriesContext } from '../utils/forecast';
|
||||
import {
|
||||
BarValueLabelPosition,
|
||||
EchartsTimeseriesSeriesType,
|
||||
ForecastSeriesEnum,
|
||||
LegendOrientation,
|
||||
@@ -69,6 +73,87 @@ import {
|
||||
TIMESERIES_CONSTANTS,
|
||||
} from '../constants';
|
||||
|
||||
const AUTO_LABEL_FIT_RATIO = 0.8;
|
||||
const BAR_LABEL_DISTANCE = 5;
|
||||
|
||||
type BarLabelPosition =
|
||||
| 'bottom'
|
||||
| 'inside'
|
||||
| 'insideBottom'
|
||||
| 'insideLeft'
|
||||
| 'insideRight'
|
||||
| 'insideTop'
|
||||
| 'left'
|
||||
| 'right'
|
||||
| 'top';
|
||||
|
||||
type NegativeBarLabelPosition = BarLabelPosition | 'outside';
|
||||
|
||||
/** Resolve the fixed ECharts label position for a bar value. */
|
||||
function getBarLabelPosition(
|
||||
position: BarValueLabelPosition,
|
||||
isHorizontal: boolean,
|
||||
isNegative = false,
|
||||
): BarLabelPosition {
|
||||
if (position === BarValueLabelPosition.OutsideEnd) {
|
||||
if (isHorizontal) return isNegative ? 'left' : 'right';
|
||||
return isNegative ? 'bottom' : 'top';
|
||||
}
|
||||
if (position === BarValueLabelPosition.InsideCenter) return 'inside';
|
||||
const isEnd = position !== BarValueLabelPosition.InsideBase;
|
||||
const usePositiveEnd = isEnd !== isNegative;
|
||||
if (isHorizontal) return usePositiveEnd ? 'insideRight' : 'insideLeft';
|
||||
return usePositiveEnd ? 'insideTop' : 'insideBottom';
|
||||
}
|
||||
|
||||
/** Place a horizontal bar label just beyond its value end. */
|
||||
function getHorizontalOutsideLayout(
|
||||
params: LabelLayoutOptionCallbackParams,
|
||||
isNegative: boolean,
|
||||
): LabelLayoutOption {
|
||||
return {
|
||||
x: isNegative
|
||||
? params.rect.x - BAR_LABEL_DISTANCE
|
||||
: params.rect.x + params.rect.width + BAR_LABEL_DISTANCE,
|
||||
y: params.rect.y + params.rect.height / 2,
|
||||
align: isNegative ? 'right' : 'left',
|
||||
verticalAlign: 'middle',
|
||||
};
|
||||
}
|
||||
|
||||
/** Place a vertical bar label just beyond its value end. */
|
||||
function getVerticalOutsideLayout(
|
||||
params: LabelLayoutOptionCallbackParams,
|
||||
isNegative: boolean,
|
||||
): LabelLayoutOption {
|
||||
return {
|
||||
x: params.rect.x + params.rect.width / 2,
|
||||
y: isNegative
|
||||
? params.rect.y + params.rect.height + BAR_LABEL_DISTANCE
|
||||
: params.rect.y - BAR_LABEL_DISTANCE,
|
||||
align: 'center',
|
||||
verticalAlign: isNegative ? 'top' : 'bottom',
|
||||
};
|
||||
}
|
||||
|
||||
/** Keep fitting labels inside and move oversized labels outside the bar. */
|
||||
export function getAutoBarLabelLayout(
|
||||
params: LabelLayoutOptionCallbackParams,
|
||||
isHorizontal: boolean,
|
||||
isNegative = false,
|
||||
): LabelLayoutOption {
|
||||
const fitsWidth =
|
||||
params.labelRect.width <=
|
||||
Math.abs(params.rect.width) * AUTO_LABEL_FIT_RATIO;
|
||||
const fitsHeight =
|
||||
params.labelRect.height <=
|
||||
Math.abs(params.rect.height) * AUTO_LABEL_FIT_RATIO;
|
||||
if (fitsWidth && fitsHeight) return {};
|
||||
return isHorizontal
|
||||
? getHorizontalOutsideLayout(params, isNegative)
|
||||
: getVerticalOutsideLayout(params, isNegative);
|
||||
}
|
||||
|
||||
function parseTimeShiftToMs(timeShift?: string | null): number {
|
||||
if (!timeShift) return 0;
|
||||
|
||||
@@ -167,31 +252,69 @@ export const getBaselineSeriesForStream = (
|
||||
};
|
||||
};
|
||||
|
||||
/** Identify object-form ECharts data items. */
|
||||
function isDataItemObject(
|
||||
dataItem: unknown,
|
||||
): dataItem is Record<string, unknown> {
|
||||
return (
|
||||
typeof dataItem === 'object' &&
|
||||
dataItem !== null &&
|
||||
!Array.isArray(dataItem)
|
||||
);
|
||||
}
|
||||
|
||||
/** Return whether an ECharts bar datum is negative on its value axis. */
|
||||
function isNegativeBarDataItem(
|
||||
dataItem: unknown,
|
||||
isHorizontal: boolean,
|
||||
): boolean {
|
||||
const value = isDataItemObject(dataItem) ? dataItem.value : dataItem;
|
||||
const axisValue = Array.isArray(value)
|
||||
? value[isHorizontal ? 0 : 1]
|
||||
: undefined;
|
||||
return typeof axisValue === 'number' && axisValue < 0;
|
||||
}
|
||||
|
||||
/** Create a fit-aware layout callback bound to one bar series. */
|
||||
function createAutoBarLabelLayout(
|
||||
data: unknown,
|
||||
isHorizontal: boolean,
|
||||
): LabelLayoutOptionCallback {
|
||||
return params => {
|
||||
const dataItem =
|
||||
Array.isArray(data) && params.dataIndex !== undefined
|
||||
? data[params.dataIndex]
|
||||
: undefined;
|
||||
return getAutoBarLabelLayout(
|
||||
params,
|
||||
isHorizontal,
|
||||
isNegativeBarDataItem(dataItem, isHorizontal),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/** Apply the value-end label position to a negative bar datum. */
|
||||
function transformNegativeLabel(
|
||||
dataItem: unknown,
|
||||
isHorizontal: boolean,
|
||||
negativePosition: NegativeBarLabelPosition,
|
||||
): unknown {
|
||||
if (!isNegativeBarDataItem(dataItem, isHorizontal)) return dataItem;
|
||||
const value = isDataItemObject(dataItem) ? dataItem.value : dataItem;
|
||||
const item = isDataItemObject(dataItem) ? dataItem : { value };
|
||||
const label = isDataItemObject(item.label) ? item.label : {};
|
||||
return { ...item, label: { ...label, position: negativePosition } };
|
||||
}
|
||||
|
||||
/** Adjust label positions for negative values in a bar series. */
|
||||
export function transformNegativeLabelsPosition(
|
||||
series: SeriesOption,
|
||||
isHorizontal: boolean,
|
||||
negativePosition: NegativeBarLabelPosition = 'outside',
|
||||
): TimeseriesDataRecord[] {
|
||||
/*
|
||||
* Adjusts label position for negative values in bar series
|
||||
* @param series - Array of series options
|
||||
* @param isHorizontal - Whether chart is horizontal
|
||||
* @returns data with adjusted label positions for negative values
|
||||
*/
|
||||
const transformValue = (value: any) => {
|
||||
const [xValue, yValue] = Array.isArray(value) ? value : [null, null];
|
||||
const axisValue = isHorizontal ? xValue : yValue;
|
||||
|
||||
return axisValue < 0
|
||||
? {
|
||||
value,
|
||||
label: {
|
||||
position: 'outside',
|
||||
},
|
||||
}
|
||||
: value;
|
||||
};
|
||||
|
||||
return (series.data as TimeseriesDataRecord[]).map(transformValue);
|
||||
return (series.data as unknown[]).map(dataItem =>
|
||||
transformNegativeLabel(dataItem, isHorizontal, negativePosition),
|
||||
) as TimeseriesDataRecord[];
|
||||
}
|
||||
|
||||
export function applyColorByPrimaryAxis(
|
||||
@@ -237,6 +360,7 @@ export function transformSeries(
|
||||
stackIdSuffix?: string;
|
||||
yAxisIndex?: number;
|
||||
showValue?: boolean;
|
||||
valueLabelPosition?: BarValueLabelPosition;
|
||||
onlyTotal?: boolean;
|
||||
legendState?: LegendState;
|
||||
formatter?: ValueFormatter;
|
||||
@@ -272,6 +396,7 @@ export function transformSeries(
|
||||
stackIdSuffix,
|
||||
yAxisIndex = 0,
|
||||
showValue,
|
||||
valueLabelPosition = BarValueLabelPosition.Auto,
|
||||
onlyTotal,
|
||||
formatter,
|
||||
legendState,
|
||||
@@ -392,23 +517,33 @@ export function transformSeries(
|
||||
symbol = opts.lineSymbol || (isDarkMode ? 'circle' : 'emptyCircle');
|
||||
}
|
||||
|
||||
let transformedData = data;
|
||||
if (Array.isArray(data) && colorByPrimaryAxis) {
|
||||
transformedData = applyColorByPrimaryAxis(
|
||||
series,
|
||||
colorScale,
|
||||
sliceId,
|
||||
opacity,
|
||||
isHorizontal,
|
||||
);
|
||||
}
|
||||
if (Array.isArray(transformedData) && plotType === 'bar') {
|
||||
transformedData = transformNegativeLabelsPosition(
|
||||
{ ...series, data: transformedData },
|
||||
isHorizontal,
|
||||
getBarLabelPosition(valueLabelPosition, isHorizontal, true),
|
||||
);
|
||||
}
|
||||
|
||||
const isAutoBarLabel =
|
||||
plotType === 'bar' && valueLabelPosition === BarValueLabelPosition.Auto;
|
||||
const isInsideBarLabel =
|
||||
plotType === 'bar' &&
|
||||
valueLabelPosition !== BarValueLabelPosition.OutsideEnd;
|
||||
|
||||
return {
|
||||
...series,
|
||||
...(Array.isArray(data)
|
||||
? colorByPrimaryAxis
|
||||
? {
|
||||
data: applyColorByPrimaryAxis(
|
||||
series,
|
||||
colorScale,
|
||||
sliceId,
|
||||
opacity,
|
||||
isHorizontal,
|
||||
),
|
||||
}
|
||||
: seriesType === 'bar' && !stack
|
||||
? { data: transformNegativeLabelsPosition(series, isHorizontal) }
|
||||
: null
|
||||
: null),
|
||||
...(Array.isArray(data) ? { data: transformedData } : null),
|
||||
connectNulls,
|
||||
queryIndex,
|
||||
yAxisIndex,
|
||||
@@ -441,10 +576,22 @@ export function transformSeries(
|
||||
showSymbol,
|
||||
symbol,
|
||||
symbolSize: symbolSizeFn ?? markerSize,
|
||||
...(isAutoBarLabel
|
||||
? {
|
||||
labelLayout: createAutoBarLabelLayout(transformedData, isHorizontal),
|
||||
}
|
||||
: {}),
|
||||
label: {
|
||||
show: !!showValue,
|
||||
position: isHorizontal ? 'right' : 'top',
|
||||
color: theme?.colorText,
|
||||
position:
|
||||
plotType === 'bar'
|
||||
? getBarLabelPosition(valueLabelPosition, isHorizontal)
|
||||
: isHorizontal
|
||||
? 'right'
|
||||
: 'top',
|
||||
// ECharts derives contrast from the bar fill for inside positions.
|
||||
// Auto x/y overflow clears the position, selecting its outside fill.
|
||||
...(isInsideBarLabel ? {} : { color: theme?.colorText }),
|
||||
textBorderWidth: 0,
|
||||
formatter: (params: any) => {
|
||||
// don't show confidence band value labels, as they're already visible on the tooltip
|
||||
@@ -467,6 +614,14 @@ export function transformSeries(
|
||||
return formatter(numericValue);
|
||||
}
|
||||
if (!onlyTotal) {
|
||||
// A stacked segment with no height begins and ends at the same
|
||||
// coordinate as the top of the segment beneath it, so its label is
|
||||
// drawn over that segment's label. Zero and null have no height, so
|
||||
// they carry no label. The rich tooltip omits zero observations from
|
||||
// a stacked series for the same reason.
|
||||
if (stack && !numericValue) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
numericValue >=
|
||||
(thresholdValues[dataIndex] || Number.MIN_SAFE_INTEGER)
|
||||
|
||||
@@ -52,6 +52,14 @@ export enum EchartsTimeseriesSeriesType {
|
||||
End = 'end',
|
||||
}
|
||||
|
||||
export enum BarValueLabelPosition {
|
||||
Auto = 'auto',
|
||||
InsideEnd = 'insideEnd',
|
||||
OutsideEnd = 'outsideEnd',
|
||||
InsideCenter = 'insideCenter',
|
||||
InsideBase = 'insideBase',
|
||||
}
|
||||
|
||||
export type EchartsTimeseriesFormData = QueryFormData & {
|
||||
annotationLayers: AnnotationLayer[];
|
||||
area: boolean;
|
||||
@@ -99,6 +107,7 @@ export type EchartsTimeseriesFormData = QueryFormData & {
|
||||
xAxisLabelRotation: number;
|
||||
xAxisLabelInterval: number | string;
|
||||
showValue: boolean;
|
||||
valueLabelPosition: BarValueLabelPosition;
|
||||
onlyTotal: boolean;
|
||||
showExtraControls: boolean;
|
||||
percentageThreshold: number;
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
StackControlOptionsWithoutStream,
|
||||
} from './constants';
|
||||
import { DEFAULT_FORM_DATA } from './Timeseries/constants';
|
||||
import { BarValueLabelPosition } from './Timeseries/types';
|
||||
import { defaultXAxis } from './defaults';
|
||||
|
||||
const { legendMargin, legendOrientation, legendType, showLegend } =
|
||||
@@ -140,6 +141,28 @@ export const showValueControl: ControlSetItem = {
|
||||
},
|
||||
};
|
||||
|
||||
export const valueLabelPositionControl: ControlSetItem = {
|
||||
name: 'value_label_position',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
freeForm: false,
|
||||
clearable: false,
|
||||
label: t('Value label position'),
|
||||
choices: [
|
||||
[BarValueLabelPosition.Auto, t('Auto')],
|
||||
[BarValueLabelPosition.InsideEnd, t('Inside End')],
|
||||
[BarValueLabelPosition.OutsideEnd, t('Outside End')],
|
||||
[BarValueLabelPosition.InsideCenter, t('Inside Center')],
|
||||
[BarValueLabelPosition.InsideBase, t('Inside Base')],
|
||||
],
|
||||
default: DEFAULT_FORM_DATA.valueLabelPosition,
|
||||
renderTrigger: true,
|
||||
description: t('Choose where to display values relative to the bars'),
|
||||
visibility: ({ controls }: ControlPanelsContainerProps) =>
|
||||
Boolean(controls?.show_value?.value),
|
||||
},
|
||||
};
|
||||
|
||||
export const colorByPrimaryAxisControl: ControlSetItem = {
|
||||
name: 'color_by_primary_axis',
|
||||
config: {
|
||||
@@ -235,6 +258,7 @@ export const showValueSectionWithoutStack: ControlSetRow[] = [
|
||||
|
||||
export const showValueSectionWithoutStream: ControlSetRow[] = [
|
||||
[showValueControl],
|
||||
[valueLabelPositionControl],
|
||||
[stackControlWithoutStream],
|
||||
[onlyTotalControl],
|
||||
[percentageThresholdControl],
|
||||
|
||||
+56
@@ -43,6 +43,7 @@ import {
|
||||
} from '../../src/MixedTimeseries/types';
|
||||
import { createEchartsTimeseriesTestChartProps } from '../helpers';
|
||||
import type { SeriesOption } from 'echarts';
|
||||
import type { BarSeriesOption } from 'echarts/charts';
|
||||
|
||||
type LabelFormatterParams = {
|
||||
value: [number, number];
|
||||
@@ -193,6 +194,61 @@ function formatSeriesLabel(
|
||||
});
|
||||
}
|
||||
|
||||
test('bar value labels retain their legacy outside position', () => {
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsMixedTimeseriesFormData,
|
||||
EchartsMixedTimeseriesProps
|
||||
>({
|
||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
||||
defaultQueriesData: queriesData,
|
||||
formData: { ...formData, showValueB: true },
|
||||
queriesData,
|
||||
});
|
||||
|
||||
const transformed = transformProps(chartProps);
|
||||
const barSeries = (transformed.echartOptions.series as SeriesOption[]).filter(
|
||||
(series): series is BarSeriesOption => series.type === 'bar',
|
||||
);
|
||||
|
||||
expect(barSeries).not.toHaveLength(0);
|
||||
barSeries.forEach(series => {
|
||||
expect(series.label).toMatchObject({ show: true, position: 'top' });
|
||||
expect(series.labelLayout).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
test('negative bar values retain their legacy outside position', () => {
|
||||
const negativeRows = [
|
||||
{ boy: -1, girl: -2, ds: 599616000000 },
|
||||
{ boy: -3, girl: -4, ds: 599916000000 },
|
||||
];
|
||||
const negativeQueriesData = [
|
||||
createTestQueryData(negativeRows, { label_map: defaultLabelMap }),
|
||||
createTestQueryData(negativeRows, { label_map: defaultLabelMap }),
|
||||
];
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsMixedTimeseriesFormData,
|
||||
EchartsMixedTimeseriesProps
|
||||
>({
|
||||
...MIXED_TIMESERIES_CHART_PROPS_DEFAULTS,
|
||||
defaultQueriesData: negativeQueriesData,
|
||||
formData: { ...formData, showValueB: true },
|
||||
queriesData: negativeQueriesData,
|
||||
});
|
||||
|
||||
const transformed = transformProps(chartProps);
|
||||
const barSeries = (transformed.echartOptions.series as SeriesOption[]).filter(
|
||||
(series): series is BarSeriesOption => series.type === 'bar',
|
||||
);
|
||||
|
||||
expect(barSeries).not.toHaveLength(0);
|
||||
barSeries.forEach(series => {
|
||||
expect(series.data?.[0]).toMatchObject({
|
||||
label: { position: 'bottom' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('should transform chart props for viz with showQueryIdentifiers=false', () => {
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsMixedTimeseriesFormData,
|
||||
|
||||
+39
-1
@@ -23,7 +23,10 @@ import {
|
||||
StackControlOptionsWithoutStream,
|
||||
StackControlsValue,
|
||||
} from '../../../src/constants';
|
||||
import { OrientationType } from '../../../src/Timeseries/types';
|
||||
import {
|
||||
BarValueLabelPosition,
|
||||
OrientationType,
|
||||
} from '../../../src/Timeseries/types';
|
||||
|
||||
const config = controlPanel;
|
||||
|
||||
@@ -132,6 +135,41 @@ test('should include stack control in the panel', () => {
|
||||
expect(stackControl).toBeDefined();
|
||||
});
|
||||
|
||||
test('should expose Auto and manual value label positions for Bar charts', () => {
|
||||
const valueLabelPositionControl = getControl(
|
||||
'value_label_position',
|
||||
) as unknown as {
|
||||
config: {
|
||||
choices: [BarValueLabelPosition, string][];
|
||||
default: BarValueLabelPosition;
|
||||
visibility: (props: ControlPanelsContainerProps) => boolean;
|
||||
};
|
||||
};
|
||||
|
||||
expect(valueLabelPositionControl.config.default).toBe(
|
||||
BarValueLabelPosition.Auto,
|
||||
);
|
||||
expect(
|
||||
valueLabelPositionControl.config.choices.map(([value]) => value),
|
||||
).toEqual([
|
||||
BarValueLabelPosition.Auto,
|
||||
BarValueLabelPosition.InsideEnd,
|
||||
BarValueLabelPosition.OutsideEnd,
|
||||
BarValueLabelPosition.InsideCenter,
|
||||
BarValueLabelPosition.InsideBase,
|
||||
]);
|
||||
expect(
|
||||
valueLabelPositionControl.config.visibility({
|
||||
controls: { show_value: { value: true } },
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(true);
|
||||
expect(
|
||||
valueLabelPositionControl.config.visibility({
|
||||
controls: { show_value: { value: false } },
|
||||
} as unknown as ControlPanelsContainerProps),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test('should use StackControlOptionsWithoutStream for stack control', () => {
|
||||
const stackControl: any = getControl('stack');
|
||||
expect(stackControl).toBeDefined();
|
||||
|
||||
+101
@@ -29,6 +29,7 @@ import type {
|
||||
GridComponentOption,
|
||||
LegendComponentOption,
|
||||
} from 'echarts/components';
|
||||
import type { BarSeriesOption } from 'echarts/charts';
|
||||
import {
|
||||
EchartsTimeseriesChartProps,
|
||||
LegendOrientation,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
import transformProps from '../../../src/Timeseries/transformProps';
|
||||
import { DEFAULT_FORM_DATA } from '../../../src/Timeseries/constants';
|
||||
import {
|
||||
BarValueLabelPosition,
|
||||
EchartsTimeseriesFormData,
|
||||
OrientationType,
|
||||
EchartsTimeseriesSeriesType,
|
||||
@@ -74,6 +76,105 @@ function createTestQueryData(
|
||||
};
|
||||
}
|
||||
|
||||
test('manual Bar value label position flows through transformProps', () => {
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsTimeseriesFormData,
|
||||
EchartsTimeseriesChartProps
|
||||
>({
|
||||
defaultFormData: DEFAULT_FORM_DATA,
|
||||
defaultVizType: 'echarts_timeseries_bar',
|
||||
formData: {
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
valueLabelPosition: BarValueLabelPosition.OutsideEnd,
|
||||
metrics: ['Sales'],
|
||||
xAxis: '__timestamp',
|
||||
showValue: true,
|
||||
},
|
||||
queriesData: [
|
||||
createTestQueryData([{ Sales: 100, __timestamp: 1609459200000 }], {
|
||||
colnames: ['Sales', '__timestamp'],
|
||||
coltypes: [GenericDataType.Numeric, GenericDataType.Temporal],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { echartOptions } = transformProps(chartProps);
|
||||
const [series] = echartOptions.series as BarSeriesOption[];
|
||||
|
||||
expect(series.label).toMatchObject({ position: 'top' });
|
||||
expect(series.labelLayout).toBeUndefined();
|
||||
expect(echartOptions.darkMode).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Auto Bar labels enable theme-aware ECharts contrast', () => {
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsTimeseriesFormData,
|
||||
EchartsTimeseriesChartProps
|
||||
>({
|
||||
defaultFormData: DEFAULT_FORM_DATA,
|
||||
defaultVizType: 'echarts_timeseries_bar',
|
||||
formData: {
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
metrics: ['Sales'],
|
||||
xAxis: '__timestamp',
|
||||
showValue: true,
|
||||
},
|
||||
queriesData: [
|
||||
createTestQueryData([{ Sales: 100, __timestamp: 1609459200000 }], {
|
||||
colnames: ['Sales', '__timestamp'],
|
||||
coltypes: [GenericDataType.Numeric, GenericDataType.Temporal],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const { echartOptions } = transformProps(chartProps);
|
||||
const [series] = echartOptions.series as BarSeriesOption[];
|
||||
|
||||
expect(typeof series.labelLayout).toBe('function');
|
||||
expect(echartOptions.darkMode).toBe(false);
|
||||
});
|
||||
|
||||
test('legacy Bar labels without a saved position enable Auto contrast', () => {
|
||||
const legacyFormData: Partial<EchartsTimeseriesFormData> = {
|
||||
...DEFAULT_FORM_DATA,
|
||||
};
|
||||
delete legacyFormData.valueLabelPosition;
|
||||
const chartProps = createEchartsTimeseriesTestChartProps<
|
||||
EchartsTimeseriesFormData,
|
||||
EchartsTimeseriesChartProps
|
||||
>({
|
||||
defaultFormData: legacyFormData as EchartsTimeseriesFormData,
|
||||
defaultVizType: 'echarts_timeseries_bar',
|
||||
formData: {
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
metrics: ['Sales'],
|
||||
xAxis: '__timestamp',
|
||||
showValue: true,
|
||||
},
|
||||
queriesData: [
|
||||
createTestQueryData([{ Sales: 100, __timestamp: 1609459200000 }], {
|
||||
colnames: ['Sales', '__timestamp'],
|
||||
coltypes: [GenericDataType.Numeric, GenericDataType.Temporal],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(chartProps.formData).not.toHaveProperty('valueLabelPosition');
|
||||
const { echartOptions } = transformProps(chartProps);
|
||||
const [series] = echartOptions.series as BarSeriesOption[];
|
||||
|
||||
expect(typeof series.labelLayout).toBe('function');
|
||||
expect(echartOptions.darkMode).toBe(false);
|
||||
|
||||
Reflect.set(chartProps.formData, 'valueLabelPosition', undefined);
|
||||
const undefinedPositionOptions = transformProps(chartProps).echartOptions;
|
||||
const [undefinedPositionSeries] =
|
||||
undefinedPositionOptions.series as BarSeriesOption[];
|
||||
|
||||
expect(typeof undefinedPositionSeries.labelLayout).toBe('function');
|
||||
expect(undefinedPositionOptions.darkMode).toBe(false);
|
||||
});
|
||||
|
||||
describe('Bar Chart X-axis Time Formatting', () => {
|
||||
const baseFormData: SqlaFormData = {
|
||||
...DEFAULT_FORM_DATA,
|
||||
|
||||
+372
-4
@@ -19,14 +19,19 @@
|
||||
import {
|
||||
CategoricalColorScale,
|
||||
ChartProps,
|
||||
getNumberFormatter,
|
||||
TimeGranularity,
|
||||
} from '@superset-ui/core';
|
||||
import { GenericDataType } from '@apache-superset/core/common';
|
||||
import { supersetTheme } from '@apache-superset/core/theme';
|
||||
import type { SeriesOption } from 'echarts';
|
||||
import type { ScatterSeriesOption } from 'echarts/charts';
|
||||
import { EchartsTimeseriesSeriesType } from '../../src';
|
||||
import { TIMESERIES_CONSTANTS } from '../../src/constants';
|
||||
import { init, type SeriesOption } from 'echarts';
|
||||
import type {
|
||||
BarSeriesOption,
|
||||
LineSeriesOption,
|
||||
ScatterSeriesOption,
|
||||
} from 'echarts/charts';
|
||||
import { BarValueLabelPosition, EchartsTimeseriesSeriesType } from '../../src';
|
||||
import { StackControlsValue, TIMESERIES_CONSTANTS } from '../../src/constants';
|
||||
import {
|
||||
LegendOrientation,
|
||||
EchartsTimeseriesChartProps,
|
||||
@@ -160,6 +165,302 @@ describe('transformSeries', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Auto bar labels move outside narrow stacked segments', () => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[2026, 1]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
stack: StackControlsValue.Stack,
|
||||
showValue: true,
|
||||
},
|
||||
) as BarSeriesOption;
|
||||
const { labelLayout } = result;
|
||||
|
||||
expect(result.label).toMatchObject({
|
||||
show: true,
|
||||
position: 'insideTop',
|
||||
});
|
||||
expect((result.label as { color?: string }).color).toBeUndefined();
|
||||
expect(typeof labelLayout).toBe('function');
|
||||
if (typeof labelLayout !== 'function') return;
|
||||
|
||||
expect(
|
||||
labelLayout({
|
||||
dataIndex: 0,
|
||||
seriesIndex: 0,
|
||||
text: '1,000',
|
||||
align: 'center',
|
||||
verticalAlign: 'middle',
|
||||
rect: { x: 10, y: 20, width: 12, height: 20 },
|
||||
labelRect: { x: 1, y: 22, width: 30, height: 14 },
|
||||
}),
|
||||
).toEqual({
|
||||
x: 16,
|
||||
y: 15,
|
||||
align: 'center',
|
||||
verticalAlign: 'bottom',
|
||||
});
|
||||
});
|
||||
|
||||
test('Auto labels stay inside when both dimensions fit within 80% of the bar', () => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[2026, 1]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{ seriesType: EchartsTimeseriesSeriesType.Bar },
|
||||
) as BarSeriesOption;
|
||||
const { labelLayout } = result;
|
||||
|
||||
expect(typeof labelLayout).toBe('function');
|
||||
if (typeof labelLayout !== 'function') return;
|
||||
|
||||
expect(
|
||||
labelLayout({
|
||||
dataIndex: 0,
|
||||
seriesIndex: 0,
|
||||
text: '1,000',
|
||||
align: 'center',
|
||||
verticalAlign: 'top',
|
||||
rect: { x: 10, y: 20, width: 50, height: 40 },
|
||||
labelRect: { x: 19, y: 25, width: 32, height: 14 },
|
||||
}),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
test('Auto moves wide labels outside tall narrow vertical bars', () => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[2026, 100]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{ seriesType: EchartsTimeseriesSeriesType.Bar },
|
||||
) as BarSeriesOption;
|
||||
const { labelLayout } = result;
|
||||
|
||||
expect(typeof labelLayout).toBe('function');
|
||||
if (typeof labelLayout !== 'function') return;
|
||||
|
||||
expect(
|
||||
labelLayout({
|
||||
dataIndex: 0,
|
||||
seriesIndex: 0,
|
||||
text: '1,000',
|
||||
align: 'center',
|
||||
verticalAlign: 'top',
|
||||
rect: { x: 10, y: 20, width: 12, height: 200 },
|
||||
labelRect: { x: 1, y: 25, width: 30, height: 14 },
|
||||
}),
|
||||
).toEqual({
|
||||
x: 16,
|
||||
y: 15,
|
||||
align: 'center',
|
||||
verticalAlign: 'bottom',
|
||||
});
|
||||
});
|
||||
|
||||
test('Auto overflow uses ECharts outside-label text color', () => {
|
||||
const darkBarColorScale = jest.fn(() => '#111111');
|
||||
const series = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[0, 123456789]] },
|
||||
darkBarColorScale as unknown as CategoricalColorScale,
|
||||
'test-key',
|
||||
{
|
||||
formatter: getNumberFormatter('d'),
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
showValue: true,
|
||||
},
|
||||
) as BarSeriesOption;
|
||||
const chart = init(null, null, {
|
||||
renderer: 'svg',
|
||||
ssr: true,
|
||||
width: 300,
|
||||
height: 220,
|
||||
});
|
||||
|
||||
chart.setOption({
|
||||
animation: false,
|
||||
darkMode: false,
|
||||
xAxis: { type: 'category', data: ['A'], show: false },
|
||||
yAxis: { type: 'value', max: 1_000_000_000, show: false },
|
||||
series: [series],
|
||||
});
|
||||
|
||||
expect(chart.renderToSVGString()).toMatch(
|
||||
/fill="#333"[^>]*>123456789<\/text>/,
|
||||
);
|
||||
chart.dispose();
|
||||
});
|
||||
|
||||
test('Auto bar labels use horizontal bar length and move to the value end', () => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[1, 2026]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{ seriesType: EchartsTimeseriesSeriesType.Bar, isHorizontal: true },
|
||||
) as BarSeriesOption;
|
||||
const { labelLayout } = result;
|
||||
|
||||
expect(typeof labelLayout).toBe('function');
|
||||
if (typeof labelLayout !== 'function') return;
|
||||
|
||||
expect(
|
||||
labelLayout({
|
||||
dataIndex: 0,
|
||||
seriesIndex: 0,
|
||||
text: '1,000',
|
||||
align: 'right',
|
||||
verticalAlign: 'middle',
|
||||
rect: { x: 10, y: 20, width: 20, height: 12 },
|
||||
labelRect: { x: 0, y: 19, width: 30, height: 14 },
|
||||
}),
|
||||
).toEqual({
|
||||
x: 35,
|
||||
y: 26,
|
||||
align: 'left',
|
||||
verticalAlign: 'middle',
|
||||
});
|
||||
});
|
||||
|
||||
test.each([
|
||||
[BarValueLabelPosition.InsideEnd, 'insideTop'],
|
||||
[BarValueLabelPosition.OutsideEnd, 'top'],
|
||||
[BarValueLabelPosition.InsideCenter, 'inside'],
|
||||
[BarValueLabelPosition.InsideBase, 'insideBottom'],
|
||||
] as const)(
|
||||
'manual %s bar labels use fixed position %s',
|
||||
(position, expected) => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[2026, 1]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
valueLabelPosition: position,
|
||||
theme: supersetTheme,
|
||||
},
|
||||
) as BarSeriesOption;
|
||||
|
||||
expect(result.labelLayout).toBeUndefined();
|
||||
expect(result.label).toMatchObject({ position: expected });
|
||||
if (position === BarValueLabelPosition.OutsideEnd) {
|
||||
expect(result.label).toMatchObject({ color: supersetTheme.colorText });
|
||||
} else {
|
||||
expect(result.label).not.toHaveProperty('color');
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test('manual Outside End positions negative stacked segments below the bar', () => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[2026, -1]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
stack: StackControlsValue.Stack,
|
||||
valueLabelPosition: BarValueLabelPosition.OutsideEnd,
|
||||
},
|
||||
) as BarSeriesOption;
|
||||
|
||||
expect(result.data).toEqual([
|
||||
{
|
||||
value: [2026, -1],
|
||||
label: { position: 'bottom' },
|
||||
},
|
||||
]);
|
||||
expect(result.labelLayout).toBeUndefined();
|
||||
});
|
||||
|
||||
test('Auto positions negative stacked segments at their inside end', () => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[2026, -1]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
stack: StackControlsValue.Stack,
|
||||
},
|
||||
) as BarSeriesOption;
|
||||
|
||||
expect(result.data).toEqual([
|
||||
{
|
||||
value: [2026, -1],
|
||||
label: { position: 'insideBottom' },
|
||||
},
|
||||
]);
|
||||
expect(typeof result.labelLayout).toBe('function');
|
||||
if (typeof result.labelLayout !== 'function') return;
|
||||
expect(
|
||||
result.labelLayout({
|
||||
dataIndex: 0,
|
||||
seriesIndex: 0,
|
||||
text: '-1,000',
|
||||
align: 'center',
|
||||
verticalAlign: 'bottom',
|
||||
rect: { x: 10, y: 20, width: 12, height: 10 },
|
||||
labelRect: { x: 1, y: 15, width: 30, height: 14 },
|
||||
}),
|
||||
).toEqual({
|
||||
x: 16,
|
||||
y: 35,
|
||||
align: 'center',
|
||||
verticalAlign: 'top',
|
||||
});
|
||||
});
|
||||
|
||||
test('Auto moves horizontal negative labels beyond their value end', () => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'bar', data: [[-1, 2026]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{ seriesType: EchartsTimeseriesSeriesType.Bar, isHorizontal: true },
|
||||
) as BarSeriesOption;
|
||||
|
||||
expect(result.data).toEqual([
|
||||
{
|
||||
value: [-1, 2026],
|
||||
label: { position: 'insideLeft' },
|
||||
},
|
||||
]);
|
||||
expect(typeof result.labelLayout).toBe('function');
|
||||
if (typeof result.labelLayout !== 'function') return;
|
||||
expect(
|
||||
result.labelLayout({
|
||||
dataIndex: 0,
|
||||
seriesIndex: 0,
|
||||
text: '-1,000',
|
||||
align: 'left',
|
||||
verticalAlign: 'middle',
|
||||
rect: { x: 10, y: 20, width: 20, height: 12 },
|
||||
labelRect: { x: 10, y: 19, width: 30, height: 14 },
|
||||
}),
|
||||
).toEqual({
|
||||
x: 5,
|
||||
y: 26,
|
||||
align: 'right',
|
||||
verticalAlign: 'middle',
|
||||
});
|
||||
});
|
||||
|
||||
test('Auto label layout does not change non-Bar series', () => {
|
||||
const result = transformSeries(
|
||||
{ name: 'test-series', type: 'line', data: [[2026, 1]] },
|
||||
mockColorScale,
|
||||
'test-key',
|
||||
{
|
||||
seriesType: EchartsTimeseriesSeriesType.Line,
|
||||
theme: supersetTheme,
|
||||
},
|
||||
) as LineSeriesOption;
|
||||
|
||||
expect(result).not.toHaveProperty('labelLayout');
|
||||
expect(result.label).toMatchObject({
|
||||
position: 'top',
|
||||
color: supersetTheme.colorText,
|
||||
});
|
||||
});
|
||||
|
||||
describe('transformNegativeLabelsPosition', () => {
|
||||
test('label position bottom of negative value no Horizontal', () => {
|
||||
const isHorizontal = false;
|
||||
@@ -566,3 +867,70 @@ test('getPadding should handle Left position with zero margin correctly', () =>
|
||||
getChartPaddingSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* #42702: a stacked segment with no height starts and ends at the same
|
||||
* coordinate as the top of the segment beneath it, so a value label on it is
|
||||
* drawn over that segment's label. `percentage_threshold` does not filter these
|
||||
* out: it defaults to 0, and `thresholdValues[dataIndex] || MIN_SAFE_INTEGER`
|
||||
* turns a 0 threshold into "no filtering", which is intentional.
|
||||
*/
|
||||
const stackedLabel = (
|
||||
numericValue: number | null,
|
||||
opts: Record<string, unknown> = {},
|
||||
) => {
|
||||
const series = transformSeries(
|
||||
{ id: 'B', name: 'B', data: [[1, numericValue]] } as SeriesOption,
|
||||
mockColorScale,
|
||||
'B',
|
||||
{
|
||||
seriesType: EchartsTimeseriesSeriesType.Bar,
|
||||
stack: StackControlsValue.Stack,
|
||||
showValue: true,
|
||||
onlyTotal: false,
|
||||
formatter: getNumberFormatter(),
|
||||
thresholdValues: [0],
|
||||
...opts,
|
||||
},
|
||||
) as SeriesOption & {
|
||||
label: { formatter: (params: unknown) => string };
|
||||
};
|
||||
return series.label.formatter({
|
||||
value: [1, numericValue],
|
||||
dataIndex: 0,
|
||||
seriesIndex: 1,
|
||||
seriesName: 'B',
|
||||
});
|
||||
};
|
||||
|
||||
test('stacked value labels are omitted for a zero-height segment', () => {
|
||||
expect(stackedLabel(0)).toBe('');
|
||||
expect(stackedLabel(null)).toBe('');
|
||||
});
|
||||
|
||||
test('stacked value labels are kept for segments that have height', () => {
|
||||
expect(stackedLabel(32)).toBe('32');
|
||||
expect(stackedLabel(-5)).toBe('-5');
|
||||
});
|
||||
|
||||
test('a zero value keeps its label when the series is not stacked', () => {
|
||||
// Without a stack the label sits on the bar itself, so there is nothing for
|
||||
// it to collide with.
|
||||
expect(stackedLabel(0, { stack: undefined })).toBe('0');
|
||||
});
|
||||
|
||||
test('percentage_threshold still filters values below the threshold', () => {
|
||||
// 10% of a 100 total. The zero-height guard must not swallow this rule.
|
||||
expect(stackedLabel(5, { thresholdValues: [10] })).toBe('');
|
||||
expect(stackedLabel(50, { thresholdValues: [10] })).toBe('50');
|
||||
});
|
||||
|
||||
test('only-total labels are unaffected by the zero-height guard', () => {
|
||||
expect(
|
||||
stackedLabel(0, {
|
||||
onlyTotal: true,
|
||||
showValueIndexes: [1],
|
||||
totalStackedValues: [32],
|
||||
}),
|
||||
).toBe('32');
|
||||
});
|
||||
|
||||
@@ -223,6 +223,11 @@ export default function chartReducer(
|
||||
}
|
||||
|
||||
if (action.type in actionHandlers) {
|
||||
// ADD_CHART creates the entry, so it runs without prior state; every other
|
||||
// handler reads state that is absent once the chart has been removed
|
||||
if (action.type !== actions.ADD_CHART && !charts[action.key]) {
|
||||
return charts;
|
||||
}
|
||||
return {
|
||||
...charts,
|
||||
[action.key]: actionHandlers[action.type](charts[action.key]),
|
||||
|
||||
@@ -91,4 +91,20 @@ describe('chart reducers', () => {
|
||||
expect(newState[chartKey].chartUpdateEndTime).toBeGreaterThan(0);
|
||||
expect(newState[chartKey].chartStatus).toEqual('failed');
|
||||
});
|
||||
|
||||
test('ignores an action for a chart that is no longer in state', () => {
|
||||
const action = actions.chartUpdateStopped(999, new AbortController());
|
||||
expect(() => chartReducer(charts, action)).not.toThrow();
|
||||
expect(chartReducer(charts, action)).toEqual(charts);
|
||||
});
|
||||
|
||||
test('still adds a chart that is not yet in state', () => {
|
||||
const newChartKey = 2;
|
||||
const newState = chartReducer(
|
||||
charts,
|
||||
actions.addChart({ ...chart, id: newChartKey }, newChartKey),
|
||||
);
|
||||
expect(newState[newChartKey].id).toEqual(newChartKey);
|
||||
expect(newState[chartKey]).toEqual(testChart);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -105,10 +105,17 @@ export const SamplesPane = ({
|
||||
1,
|
||||
)
|
||||
.then(response => {
|
||||
setData(ensureIsArray(response.data));
|
||||
setColnames(ensureIsArray(response.colnames));
|
||||
setColtypes(ensureIsArray(response.coltypes));
|
||||
setRowCount(response.rowcount);
|
||||
// A 200 that carries no `result` payload resolves to undefined here.
|
||||
// Read through it so the pane falls back to its empty state instead
|
||||
// of throwing a TypeError that surfaces as an internal error message.
|
||||
const rows = ensureIsArray(response?.data);
|
||||
setData(rows);
|
||||
setColnames(ensureIsArray(response?.colnames));
|
||||
setColtypes(ensureIsArray(response?.coltypes));
|
||||
// Fall back to the rows actually returned rather than to zero: the
|
||||
// controls only render when there are rows, and a hardcoded 0 would
|
||||
// label a populated table as "0 rows".
|
||||
setRowCount(response?.rowcount ?? rows.length);
|
||||
setResponseError('');
|
||||
cache.set(queryFormData, true);
|
||||
if (queryForce) {
|
||||
|
||||
@@ -60,6 +60,27 @@ describe('SamplesPane', () => {
|
||||
400,
|
||||
);
|
||||
|
||||
// A 200 response that carries no `result` payload, as reported in #36840.
|
||||
fetchMock.post(
|
||||
'end:/datasource/samples?force=false&datasource_type=table&datasource_id=37&per_page=100&page=1',
|
||||
{},
|
||||
);
|
||||
|
||||
// A 200 whose result carries rows but omits `rowcount`.
|
||||
fetchMock.post(
|
||||
'end:/datasource/samples?force=false&datasource_type=table&datasource_id=38&per_page=100&page=1',
|
||||
{
|
||||
result: {
|
||||
data: [
|
||||
{ __timestamp: 1230768000000, genre: 'Action' },
|
||||
{ __timestamp: 1230768000010, genre: 'Horror' },
|
||||
],
|
||||
colnames: ['__timestamp', 'genre'],
|
||||
coltypes: [2, 1],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const setForceQuery = jest.fn();
|
||||
|
||||
afterAll(() => {
|
||||
@@ -114,4 +135,29 @@ describe('SamplesPane', () => {
|
||||
expect(queryByText('Action')).toBeVisible();
|
||||
expect(queryByText('Horror')).toBeVisible();
|
||||
});
|
||||
|
||||
test('renders the empty state when the response carries no result payload', async () => {
|
||||
const props = createSamplesPaneProps({ datasourceId: 37 });
|
||||
const { findByText, queryByRole } = render(<SamplesPane {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
await findByText('No samples were returned for this dataset'),
|
||||
).toBeVisible();
|
||||
// The pane should not leak an internal TypeError through the error alert.
|
||||
expect(queryByRole('alert')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('counts the returned rows when the response omits rowcount', async () => {
|
||||
const props = createSamplesPaneProps({ datasourceId: 38 });
|
||||
const { findByText, queryByText } = render(<SamplesPane {...props} />, {
|
||||
useRedux: true,
|
||||
});
|
||||
|
||||
expect(await findByText('Action')).toBeVisible();
|
||||
// Falling back to 0 here would label a populated table as "0 rows".
|
||||
expect(queryByText('0 rows')).not.toBeInTheDocument();
|
||||
expect(queryByText('2 rows')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+4
-4
@@ -28,7 +28,7 @@
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
"eslint": "^10.8.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"globals": "^17.10.0",
|
||||
"globals": "^17.11.0",
|
||||
"oxfmt": "^0.63.0",
|
||||
"tscw-config": "^1.1.2",
|
||||
"typescript": "^6.0.3",
|
||||
@@ -2053,9 +2053,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "17.10.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.10.0.tgz",
|
||||
"integrity": "sha512-V0kztuWST2k8A/VbxAY8+L+7+Rgo3fyA24IHRLrZp7HOzJjV0gHSaZUjK9lpP/IrBSNite2tZ1prhRkinRu1CA==",
|
||||
"version": "17.11.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz",
|
||||
"integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"@typescript-eslint/parser": "^8.67.0",
|
||||
"eslint": "^10.8.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"globals": "^17.10.0",
|
||||
"globals": "^17.11.0",
|
||||
"oxfmt": "^0.63.0",
|
||||
"tscw-config": "^1.1.2",
|
||||
"typescript": "^6.0.3",
|
||||
|
||||
@@ -280,9 +280,6 @@ def test_sqlalchemy_dialect(
|
||||
"""
|
||||
Test the SQLAlchemy dialect, making sure it supports everything Superset needs.
|
||||
"""
|
||||
if "future" not in engine_kwargs:
|
||||
engine_kwargs["future"] = True
|
||||
|
||||
engine = create_engine(sqlalchemy_uri, **engine_kwargs)
|
||||
dialect = engine.dialect
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ class BaseStreamingCSVExportCommand(BaseCommand):
|
||||
delimiter = csv_export_config.get("sep", ",")
|
||||
decimal_separator = csv_export_config.get("decimal", ".")
|
||||
|
||||
with db.session(future=True) as session:
|
||||
with db.session() as session:
|
||||
# Merge database to prevent DetachedInstanceError
|
||||
merged_database = session.merge(database)
|
||||
|
||||
|
||||
@@ -291,19 +291,30 @@ class QueryContextFactory: # pylint: disable=too-few-public-methods
|
||||
),
|
||||
None,
|
||||
)
|
||||
# Replaces x-axis column values with granularity
|
||||
# Point the x-axis at the overridden Time Column (granularity).
|
||||
if x_axis_column:
|
||||
if isinstance(x_axis_column, dict):
|
||||
# Only swap the underlying expression, keeping the
|
||||
# column's original label. The temporal offset join
|
||||
# (``processing_time_offsets``), the post-processing
|
||||
# pivot ``index`` and the frontend all reference this
|
||||
# column by its label; renaming it to the granularity
|
||||
# here desynchronizes those consumers from the label
|
||||
# the saved chart still advertises, which — with a Time
|
||||
# Comparison offset — collapses the series into a single
|
||||
# point.
|
||||
x_axis_column["sqlExpression"] = granularity
|
||||
x_axis_column["label"] = granularity
|
||||
else:
|
||||
# A bare string x-axis has no distinct label, so it is
|
||||
# replaced wholesale and the pivot ``index`` must be
|
||||
# realigned to the overridden column.
|
||||
query_object.columns = [
|
||||
granularity if column == x_axis_column else column
|
||||
for column in query_object.columns
|
||||
]
|
||||
for post_processing in query_object.post_processing:
|
||||
if post_processing.get("operation") == "pivot":
|
||||
post_processing["options"]["index"] = [granularity]
|
||||
for post_processing in query_object.post_processing:
|
||||
if post_processing.get("operation") == "pivot":
|
||||
post_processing["options"]["index"] = [granularity]
|
||||
|
||||
# If no temporal x-axis, then get the default temporal filter
|
||||
if not filter_to_remove:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# pylint: disable=invalid-name
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pprint import pformat
|
||||
@@ -205,8 +206,86 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
|
||||
def _set_post_processing(
|
||||
self, post_processing: list[dict[str, Any] | None] | None
|
||||
) -> None:
|
||||
post_processing = post_processing or []
|
||||
self.post_processing = [post_proc for post_proc in post_processing if post_proc]
|
||||
self.post_processing = [
|
||||
self._drop_unsupported_options(post_proc)
|
||||
for post_proc in post_processing or []
|
||||
if post_proc
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def _drop_unsupported_options(post_proc: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Drop options that the post-processing operation no longer accepts.
|
||||
|
||||
A chart's ``query_context`` is written when the chart is saved and is
|
||||
never rewritten afterwards, while Explore rebuilds the query from
|
||||
``form_data`` at every render. A chart saved by an older version of
|
||||
Superset can therefore reference an option that has since been removed
|
||||
from the operation. ``exec_post_processing`` passes the stored options
|
||||
as keyword arguments, so that option raises a bare ``TypeError`` on
|
||||
every path that replays the stored ``query_context`` -- the chart data
|
||||
endpoint, alerts and reports, thumbnails, CSV export -- while the same
|
||||
chart still renders correctly in Explore.
|
||||
|
||||
Comparing against the signature avoids a hard-coded list of removed
|
||||
option names, which would need extending at each release.
|
||||
"""
|
||||
operation = post_proc.get("operation")
|
||||
function = (
|
||||
getattr(pandas_postprocessing, operation, None)
|
||||
if isinstance(operation, str)
|
||||
else None
|
||||
)
|
||||
if function is None:
|
||||
# A missing or unknown operation is left untouched, so that
|
||||
# exec_post_processing reports it as InvalidPostProcessingError.
|
||||
return post_proc
|
||||
|
||||
parameters = inspect.signature(function).parameters
|
||||
if any(
|
||||
parameter.kind is inspect.Parameter.VAR_KEYWORD
|
||||
for parameter in parameters.values()
|
||||
):
|
||||
return post_proc
|
||||
|
||||
# `exec_post_processing` calls the operation as `operation(df, **options)`,
|
||||
# so an option can only reach a parameter that a caller may fill by
|
||||
# keyword. That excludes the first parameter, which receives the
|
||||
# DataFrame positionally, and any positional-only or `*args` parameter.
|
||||
keyword_parameters = {
|
||||
name
|
||||
for position, (name, parameter) in enumerate(parameters.items())
|
||||
if position > 0
|
||||
and parameter.kind
|
||||
in (
|
||||
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
inspect.Parameter.KEYWORD_ONLY,
|
||||
)
|
||||
}
|
||||
|
||||
options = post_proc.get("options") or {}
|
||||
unsupported = {key for key in options if key not in keyword_parameters}
|
||||
if not unsupported:
|
||||
return post_proc
|
||||
|
||||
# Logged at info: a chart saved before the option was removed hits this
|
||||
# on every render, so a warning would repeat for as long as the chart
|
||||
# is not resaved, without anything new to report.
|
||||
logger.info(
|
||||
"Dropping unsupported option(s) %s of post-processing operation "
|
||||
"`%s`. The chart's stored query_context predates the current "
|
||||
"signature of that operation.",
|
||||
sorted(unsupported),
|
||||
operation,
|
||||
)
|
||||
return {
|
||||
**post_proc,
|
||||
"options": {
|
||||
key: value
|
||||
for key, value in options.items()
|
||||
if key in keyword_parameters
|
||||
},
|
||||
}
|
||||
|
||||
def _init_series_columns(
|
||||
self,
|
||||
|
||||
@@ -389,7 +389,6 @@ class GSheetsEngineSpec(ShillelaghEngineSpec):
|
||||
}
|
||||
}
|
||||
},
|
||||
future=True,
|
||||
)
|
||||
conn = engine.connect()
|
||||
idx = 0
|
||||
|
||||
@@ -376,7 +376,7 @@ def upgrade_catalog_perms(engines: set[str] | None = None) -> None:
|
||||
|
||||
"""
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# The Database model has an eager-loaded (``lazy="joined"``) ``ssh_tunnel``
|
||||
# backref. Eager-loading it here would SELECT every column on ``ssh_tunnels``,
|
||||
@@ -581,7 +581,7 @@ def downgrade_catalog_perms(engines: set[str] | None = None) -> None:
|
||||
WARNING: models (datasets and charts) not in the default catalog are deleted!
|
||||
"""
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# See upgrade_catalog_perms: avoid eager-loading the ``ssh_tunnel`` backref so the
|
||||
# query stays schema-safe across migration revisions.
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ class Slice(Base):
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
op.add_column("slices", sa.Column("perm", sa.String(length=2000), nullable=True))
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# Use Slice class defined here instead of models.Slice
|
||||
for slc in session.query(Slice).all():
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ def upgrade():
|
||||
)
|
||||
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# don't use models.DruidMetric
|
||||
# because it assumes the context is consistent with the application
|
||||
|
||||
@@ -94,7 +94,7 @@ class Dashboard(AuditMixin, Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
objects = session.query(Slice).all()
|
||||
objects += session.query(Dashboard).all()
|
||||
|
||||
@@ -50,7 +50,7 @@ class Slice(Base):
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
op.add_column("slices", sa.Column("datasource_id", sa.Integer()))
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
if slc.druid_datasource_id:
|
||||
@@ -63,7 +63,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
for slc in session.query(Slice).all():
|
||||
if slc.datasource_type == "druid":
|
||||
slc.druid_datasource_id = slc.datasource_id
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ class Database(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for obj in session.query(Database).all():
|
||||
obj.allow_run_sync = True
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
slices = session.query(Slice).all()
|
||||
slice_len = len(slices)
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ class Url(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
urls = session.query(Url).all()
|
||||
urls_len = len(urls)
|
||||
|
||||
@@ -45,7 +45,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(Slice.viz_type.like("deck_%")):
|
||||
params = json.loads(slc.params)
|
||||
|
||||
@@ -45,7 +45,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(
|
||||
or_(Slice.viz_type.like("line"), Slice.viz_type.like("bar"))
|
||||
@@ -75,7 +75,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(
|
||||
or_(Slice.viz_type.like("line"), Slice.viz_type.like("bar"))
|
||||
|
||||
@@ -46,7 +46,7 @@ class Dashboard(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = session.query(Dashboard).all()
|
||||
for i, dashboard in enumerate(dashboards):
|
||||
@@ -68,7 +68,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = session.query(Dashboard).all()
|
||||
for i, dashboard in enumerate(dashboards):
|
||||
|
||||
@@ -57,7 +57,7 @@ def upgrade():
|
||||
),
|
||||
)
|
||||
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# Use Slice class defined here instead of models.Slice
|
||||
for tbl in session.query(Table).all():
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
slices = session.query(Slice).filter_by(viz_type="cal_heatmap").all()
|
||||
slice_len = len(slices)
|
||||
|
||||
@@ -45,7 +45,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
@@ -63,7 +63,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
|
||||
@@ -45,7 +45,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
|
||||
+2
-2
@@ -68,7 +68,7 @@ class Database(Base):
|
||||
|
||||
|
||||
def replace(source, target):
|
||||
with db.Session(bind=op.get_bind(), future=True) as session:
|
||||
with db.Session(bind=op.get_bind()) as session:
|
||||
with session.begin():
|
||||
query = (
|
||||
session.query(Slice, Database)
|
||||
@@ -80,7 +80,7 @@ def replace(source, target):
|
||||
|
||||
for slc, database in query:
|
||||
try:
|
||||
engine = create_engine(database.sqlalchemy_uri, future=True)
|
||||
engine = create_engine(database.sqlalchemy_uri)
|
||||
|
||||
if engine.dialect.identifier_preparer._double_percents:
|
||||
params = json.loads(slc.params)
|
||||
|
||||
@@ -50,7 +50,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
@@ -66,7 +66,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(Slice.viz_type == "pie").all():
|
||||
try:
|
||||
@@ -68,7 +68,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(Slice.viz_type == "pie").all():
|
||||
try:
|
||||
|
||||
+2
-2
@@ -134,7 +134,7 @@ def compute_time_compare(granularity, periods):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for chart in session.query(Slice):
|
||||
params = json.loads(chart.params or "{}")
|
||||
@@ -163,7 +163,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for chart in session.query(Slice):
|
||||
params = json.loads(chart.params or "{}")
|
||||
|
||||
@@ -159,7 +159,7 @@ class TableColumn(BaseColumnMixin, Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
tables = [
|
||||
Annotation,
|
||||
|
||||
@@ -59,7 +59,7 @@ class TableColumn(BaseColumnMixin, Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# Delete the orphaned columns records.
|
||||
for record in session.query(DruidColumn).all():
|
||||
|
||||
@@ -59,7 +59,7 @@ class SqlMetric(BaseMetricMixin, Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# Delete the orphaned metrics records.
|
||||
for record in session.query(DruidMetric).all():
|
||||
|
||||
+1
-1
@@ -579,7 +579,7 @@ def scan_dashboard_positions_data(positions):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = session.query(Dashboard).all()
|
||||
for i, dashboard in enumerate(dashboards):
|
||||
|
||||
@@ -55,7 +55,7 @@ def is_v2_dash(positions):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = session.query(Dashboard).all()
|
||||
for i, dashboard in enumerate(dashboards): # noqa: B007
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ class Dashboard(Base):
|
||||
|
||||
def upgrade(): # noqa: C901
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = session.query(Dashboard).all()
|
||||
for i, dashboard in enumerate(dashboards):
|
||||
|
||||
@@ -75,7 +75,7 @@ def upgrade_slice(slc):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
filter_box_slices = session.query(Slice).filter_by(viz_type="filter_box")
|
||||
for slc in filter_box_slices.all():
|
||||
@@ -90,7 +90,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
filter_box_slices = session.query(Slice).filter_by(viz_type="filter_box")
|
||||
for slc in filter_box_slices.all():
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ def upgrade():
|
||||
|
||||
bind = op.get_bind()
|
||||
insp = sa.engine.reflection.Inspector.from_engine(bind)
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
tables = session.query(SqlaTable).all()
|
||||
for table in tables:
|
||||
|
||||
@@ -159,7 +159,7 @@ class TableColumn(BaseColumnMixin, Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
tables = [
|
||||
Annotation,
|
||||
|
||||
+2
-2
@@ -62,7 +62,7 @@ def add_parent_ids(node, layout):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = session.query(Dashboard).all()
|
||||
for i, dashboard in enumerate(dashboards):
|
||||
@@ -88,7 +88,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = session.query(Dashboard).all()
|
||||
for i, dashboard in enumerate(dashboards):
|
||||
|
||||
@@ -47,7 +47,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
@@ -94,7 +94,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ class DashboardSlices(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# find dup records in dashboard_slices tbl
|
||||
dup_records = (
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ def upgrade():
|
||||
op.add_column("tables", Column("schema_perm", String(length=1000), nullable=True))
|
||||
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
for t in session.query(Sqlatable).all():
|
||||
db_name = (
|
||||
t.database.verbose_name
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@ down_revision = "11c737c17cc6"
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
tables = [
|
||||
Annotation,
|
||||
|
||||
@@ -48,7 +48,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
@@ -63,7 +63,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
if slc.params:
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ class Dashboard(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = session.query(Dashboard).all()
|
||||
for i, dashboard in enumerate(dashboards):
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ def upgrade():
|
||||
"""
|
||||
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# Visualization types which support time granularity (hence negate).
|
||||
viz_types = [
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ def duration_by_name(database: Database):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
query = (
|
||||
session.query(Slice, Database)
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ def create_new_markdown_component(chart_position, url):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dash_to_migrate = defaultdict(list)
|
||||
iframe_urls = defaultdict(list)
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ def upgrade(): # noqa: C901
|
||||
"""
|
||||
|
||||
bind = op.get_bind()
|
||||
session = orm.Session(bind=bind, future=True)
|
||||
session = orm.Session(bind=bind)
|
||||
|
||||
faulty_view_menus = (
|
||||
session.query(ViewMenu)
|
||||
|
||||
+2
-2
@@ -119,7 +119,7 @@ def update_dashboards(session, uuid_map):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for table_name, model in models.items():
|
||||
with op.batch_alter_table(table_name) as batch_op:
|
||||
@@ -152,7 +152,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# remove uuid from position_json
|
||||
update_dashboards(session, {})
|
||||
|
||||
+2
-2
@@ -135,7 +135,7 @@ def upgrade():
|
||||
),
|
||||
)
|
||||
# Migrate data
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
alerts = session.query(Alert).all()
|
||||
for a in alerts:
|
||||
if a.sql_observer:
|
||||
@@ -224,7 +224,7 @@ def downgrade():
|
||||
)
|
||||
|
||||
# Migrate data
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
alerts = session.query(Alert).all()
|
||||
for a in alerts:
|
||||
if a.sql:
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ default_batch_size = int(os.environ.get("BATCH_SIZE", 200))
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# Add uuid column
|
||||
try:
|
||||
@@ -86,7 +86,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True) # noqa: F841
|
||||
session = db.Session(bind=bind) # noqa: F841
|
||||
|
||||
# Remove uuid column
|
||||
with op.batch_alter_table("saved_query") as batch_op:
|
||||
|
||||
+2
-2
@@ -93,7 +93,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -107,7 +107,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -65,7 +65,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -79,7 +79,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -77,7 +77,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -91,7 +91,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -101,7 +101,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -115,7 +115,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -101,7 +101,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -101,7 +101,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -115,7 +115,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -89,7 +89,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -103,7 +103,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -80,7 +80,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -94,7 +94,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -55,7 +55,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -69,7 +69,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -61,7 +61,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
x_dateunit_in_since = DateRangeMigration.x_dateunit_in_since
|
||||
x_dateunit_in_until = DateRangeMigration.x_dateunit_in_until
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ def upgrade():
|
||||
- If no dttm columns exist in the dataset, don't change the chart.
|
||||
"""
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
slices_changed = 0
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
slices = (
|
||||
session.query(Slice)
|
||||
@@ -75,7 +75,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
slices = (
|
||||
session.query(Slice)
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(Slice.viz_type.like("directed_force")):
|
||||
params = json.loads(slc.params)
|
||||
@@ -75,7 +75,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(Slice.viz_type.like("graph_chart")):
|
||||
params = json.loads(slc.params)
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ def has_uuid_column(table_name, bind):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for table_name, model in models.items():
|
||||
# this script adds missing uuid columns
|
||||
|
||||
+2
-2
@@ -46,7 +46,7 @@ class Dashboard(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = (
|
||||
session.query(Dashboard)
|
||||
@@ -74,7 +74,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = (
|
||||
session.query(Dashboard)
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ class SqlaTable(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for datasource in session.query(SqlaTable):
|
||||
if datasource.extra:
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ def upgrade():
|
||||
Convert all country names to lowercase
|
||||
"""
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(Slice.viz_type == "country_map").all():
|
||||
try:
|
||||
@@ -69,7 +69,7 @@ def downgrade():
|
||||
Convert all country names to sentence case
|
||||
"""
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).filter(Slice.viz_type == "country_map").all():
|
||||
try:
|
||||
|
||||
+2
-2
@@ -172,7 +172,7 @@ def downgrade_filter_set(filter_set: dict[str, Any]) -> int:
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = (
|
||||
session.query(Dashboard)
|
||||
@@ -208,7 +208,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = (
|
||||
session.query(Dashboard)
|
||||
|
||||
+2
-2
@@ -94,7 +94,7 @@ def upgrade_dashboard(dashboard: dict[str, Any]) -> tuple[int, int]:
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = (
|
||||
session.query(Dashboard)
|
||||
@@ -136,7 +136,7 @@ def downgrade_dashboard(dashboard: dict[str, Any]) -> tuple[int, int]:
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
dashboards = (
|
||||
session.query(Dashboard)
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ def upgrade():
|
||||
Fix databases with ``schemas_allowed_for_csv_upload`` stored as string.
|
||||
"""
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for database in session.query(Database).all():
|
||||
try:
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ VALID_RENDERERS = (
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
slices = (
|
||||
session.query(Slice)
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ PVM_MAP = {
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the new permissions on the migration itself
|
||||
add_pvms(session, NEW_PVMS)
|
||||
@@ -61,7 +61,7 @@ def upgrade():
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind, future=True)
|
||||
session = Session(bind=bind)
|
||||
|
||||
# Add the old permissions on the migration itself
|
||||
add_pvms(session, get_reversed_new_pvms(PVM_MAP))
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ def upgrade():
|
||||
|
||||
def remove_value_if_too_long():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
# it will be easier for users to notice that their field has been deleted rather than truncated # noqa: E501
|
||||
# so just remove it if it won't fit back into the 1000 string length column
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ class Dashboard(Base):
|
||||
def upgrade():
|
||||
logger.info("[AddTypeToNativeFilter] Starting upgrade")
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for dashboard in session.query(Dashboard).all():
|
||||
logger.info("[AddTypeToNativeFilter] Updating Dashboard<pk:%s> ", dashboard.id)
|
||||
@@ -87,7 +87,7 @@ def upgrade():
|
||||
def downgrade():
|
||||
logger.info("[RemoveTypeToNativeFilter] Starting downgrade")
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for dashboard in session.query(Dashboard).all():
|
||||
logger.info(
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ class Slice(Base):
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
where_clause = and_(
|
||||
Slice.viz_type == "pivot_table_v2",
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ class Slice(Base):
|
||||
|
||||
def migrate(mapping: dict[str, str]) -> None:
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind, future=True)
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for slc in session.query(Slice).all():
|
||||
try:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user