Compare commits

...

6 Commits

Author SHA1 Message Date
Elizabeth Thompson
98212189b8 fix(docker): bind webpack dev server to all interfaces
Change WEBPACK_DEVSERVER_HOST from 127.0.0.1 to 0.0.0.0 to make the
frontend accessible from outside the Docker container. This fixes an
issue where the frontend was only accessible from inside the container.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-23 17:03:49 -07:00
SBIN2010
1e4bc6ee78 fix: bug in tooltip timeseries chart in calculated total with annotation layer (#35179) 2025-09-19 10:30:42 -07:00
Pat Buxton
db178cf527 fix: Bump pandas to 2.1.4 for python 3.12 (#34999) 2025-09-19 09:18:00 -07:00
Alexandru Soare
5901320933 feat(database): Adding per-user caching option in Security tab (#34842) 2025-09-19 19:15:31 +03:00
SBIN2010
23bb4f88c0 fix(Funnel): onInit overridden row_limit to default value on save chart (#35076) 2025-09-19 09:13:45 -07:00
Levis Mbote
4130b92966 fix(gantt-chart): fix Y-axis label visibility in dark theme (#35189) 2025-09-19 12:33:53 +03:00
14 changed files with 113 additions and 61 deletions

View File

@@ -163,7 +163,7 @@ services:
# configuring the dev-server to use the host.docker.internal to connect to the backend
superset: "http://superset-light:8088"
# Webpack dev server configuration
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-127.0.0.1}"
WEBPACK_DEVSERVER_HOST: "${WEBPACK_DEVSERVER_HOST:-0.0.0.0}"
WEBPACK_DEVSERVER_PORT: "${WEBPACK_DEVSERVER_PORT:-9000}"
ports:
- "${NODE_PORT:-9001}:9000" # Parameterized port, accessible on all interfaces

View File

@@ -76,7 +76,7 @@ dependencies = [
"packaging",
# --------------------------
# pandas and related (wanting pandas[performance] without numba as it's 100+MB and not needed)
"pandas[excel]>=2.0.3, <2.1",
"pandas[excel]>=2.0.3, <2.2",
"bottleneck", # recommended performance dependency for pandas, see https://pandas.pydata.org/docs/getting_started/install.html#performance-dependencies-recommended
# --------------------------
"parsedatetime",

View File

@@ -160,6 +160,7 @@ greenlet==3.1.1
# via
# apache-superset (pyproject.toml)
# shillelagh
# sqlalchemy
gunicorn==23.0.0
# via apache-superset (pyproject.toml)
h11==0.16.0
@@ -266,7 +267,7 @@ packaging==25.0
# limits
# marshmallow
# shillelagh
pandas==2.0.3
pandas==2.1.4
# via apache-superset (pyproject.toml)
paramiko==3.5.1
# via

View File

@@ -331,6 +331,7 @@ greenlet==3.1.1
# apache-superset
# gevent
# shillelagh
# sqlalchemy
grpcio==1.71.0
# via
# apache-superset
@@ -536,7 +537,7 @@ packaging==25.0
# pytest
# shillelagh
# sqlalchemy-bigquery
pandas==2.0.3
pandas==2.1.4
# via
# -c requirements/base-constraint.txt
# apache-superset

View File

@@ -19,7 +19,6 @@
import { t } from '@superset-ui/core';
import {
ControlPanelConfig,
ControlStateMapping,
ControlSubSectionHeader,
D3_FORMAT_DOCS,
D3_FORMAT_OPTIONS,
@@ -197,15 +196,6 @@ const config: ControlPanelConfig = {
],
},
],
onInit(state: ControlStateMapping) {
return {
...state,
row_limit: {
...state.row_limit,
value: state.row_limit.default,
},
};
},
formDataOverrides: formData => ({
...formData,
metric: getStandardizedControls().shiftMetric(),

View File

@@ -325,6 +325,7 @@ export default function transformProps(chartProps: EchartsGanttChartProps) {
show: true,
position: 'start',
formatter: '{b}',
color: theme.colorText,
},
data: categoryLines,
},

View File

@@ -47,7 +47,10 @@ import {
isDerivedSeries,
} from '@superset-ui/chart-controls';
import type { EChartsCoreOption } from 'echarts/core';
import type { LineStyleOption } from 'echarts/types/src/util/types';
import type {
LineStyleOption,
CallbackDataParams,
} from 'echarts/types/src/util/types';
import type { SeriesOption } from 'echarts';
import {
EchartsTimeseriesChartProps,
@@ -575,16 +578,31 @@ export default function transformProps(
const xValue: number = richTooltip
? params[0].value[xIndex]
: params.value[xIndex];
const forecastValue: any[] = richTooltip ? params : [params];
const forecastValue: CallbackDataParams[] = richTooltip
? params
: [params];
const sortedKeys = extractTooltipKeys(
forecastValue,
yIndex,
richTooltip,
tooltipSortByMetric,
);
const filteredForecastValue = forecastValue.filter(
(item: CallbackDataParams) =>
!annotationLayers.some(
(annotation: AnnotationLayer) =>
item.seriesName === annotation.name,
),
);
const forecastValues: Record<string, ForecastValue> =
extractForecastValuesFromTooltipParams(forecastValue, isHorizontal);
const filteredForecastValues: Record<string, ForecastValue> =
extractForecastValuesFromTooltipParams(
filteredForecastValue,
isHorizontal,
);
const isForecast = Object.values(forecastValues).some(
value =>
value.forecastTrend || value.forecastLower || value.forecastUpper,
@@ -595,7 +613,7 @@ export default function transformProps(
: (getCustomFormatter(customFormatters, metrics) ?? defaultFormatter);
const rows: string[][] = [];
const total = Object.values(forecastValues).reduce(
const total = Object.values(filteredForecastValues).reduce(
(acc, value) =>
value.observation !== undefined ? acc + value.observation : acc,
0,
@@ -617,7 +635,16 @@ export default function transformProps(
seriesName: key,
formatter,
});
if (showPercentage && value.observation !== undefined) {
const annotationRow = annotationLayers.some(
item => item.name === key,
);
if (
showPercentage &&
value.observation !== undefined &&
!annotationRow
) {
row.push(
percentFormatter.format(value.observation / (total || 1)),
);

View File

@@ -257,6 +257,7 @@ describe('Gantt transformProps', () => {
show: true,
position: 'start',
formatter: '{b}',
color: 'rgba(0,0,0,0.88)',
},
lineStyle: expect.objectContaining({
color: '#00000000',

View File

@@ -460,48 +460,26 @@ const ExtraOptions = ({
),
children: (
<>
<StyledInputContainer>
<div className="control-label">{t('Secure extra')}</div>
<StyledInputContainer
css={!isFileUploadSupportedByEngine ? no_margin_bottom : {}}
>
<div className="input-container">
<StyledJsonEditor
name="masked_encrypted_extra"
value={db?.masked_encrypted_extra || ''}
placeholder={t('Secure extra')}
onChange={(json: string) =>
onEditorChange({ json, name: 'masked_encrypted_extra' })
}
width="100%"
height="160px"
annotations={secureExtraAnnotations}
/>
</div>
<div className="helper">
<div>
{t(
'JSON string containing additional connection configuration. ' +
'This is used to provide connection information for systems ' +
'like Hive, Presto and BigQuery which do not conform to the ' +
'username:password syntax normally used by SQLAlchemy.',
<Checkbox
id="per_user_caching"
name="per_user_caching"
indeterminate={false}
checked={!!extraJson?.per_user_caching}
onChange={onExtraInputChange}
>
{t('Per user caching')}
</Checkbox>
<InfoTooltip
tooltip={t(
'Cache data separately for each user based on their data access roles and permissions. ' +
'When disabled, a single cache will be used for all users.',
)}
</div>
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="control-label">{t('Root certificate')}</div>
<div className="input-container">
<Input.TextArea
name="server_cert"
value={db?.server_cert || ''}
placeholder={t('Enter CA_BUNDLE')}
onChange={onTextChange}
/>
</div>
<div className="helper">
{t(
'Optional CA_BUNDLE contents to validate HTTPS requests. Only ' +
'available on certain database engines.',
)}
</div>
</StyledInputContainer>
<StyledInputContainer
css={!isFileUploadSupportedByEngine ? no_margin_bottom : {}}
@@ -569,6 +547,49 @@ const ExtraOptions = ({
</div>
</StyledInputContainer>
)}
<StyledInputContainer>
<div className="control-label">{t('Secure extra')}</div>
<div className="input-container">
<StyledJsonEditor
name="masked_encrypted_extra"
value={db?.masked_encrypted_extra || ''}
placeholder={t('Secure extra')}
onChange={(json: string) =>
onEditorChange({ json, name: 'masked_encrypted_extra' })
}
width="100%"
height="160px"
annotations={secureExtraAnnotations}
/>
</div>
<div className="helper">
<div>
{t(
'JSON string containing additional connection configuration. ' +
'This is used to provide connection information for systems ' +
'like Hive, Presto and BigQuery which do not conform to the ' +
'username:password syntax normally used by SQLAlchemy.',
)}
</div>
</div>
</StyledInputContainer>
<StyledInputContainer>
<div className="control-label">{t('Root certificate')}</div>
<div className="input-container">
<Input.TextArea
name="server_cert"
value={db?.server_cert || ''}
placeholder={t('Enter CA_BUNDLE')}
onChange={onTextChange}
/>
</div>
<div className="helper">
{t(
'Optional CA_BUNDLE contents to validate HTTPS requests. Only ' +
'available on certain database engines.',
)}
</div>
</StyledInputContainer>
</>
),
},

View File

@@ -246,6 +246,7 @@ export interface ExtraJson {
disable_data_preview?: boolean; // in SQL Lab
disable_drill_to_detail?: boolean;
allow_multi_catalog?: boolean;
per_user_caching?: boolean; // in Security
engine_params?: {
catalog?: Record<string, string>;
connect_args?: {

View File

@@ -72,7 +72,7 @@ class ExcelReader(BaseDataReader):
"na_values": self._options.get("null_values")
if self._options.get("null_values") # None if an empty list
else None,
"parse_dates": self._options.get("column_dates"),
"parse_dates": self._options.get("column_dates") or False,
"skiprows": self._options.get("skip_rows", 0),
"sheet_name": self._options.get("sheet_name", 0),
"nrows": self._options.get("rows_to_read"),

View File

@@ -454,13 +454,19 @@ class QueryObject: # pylint: disable=too-many-instance-attributes
cache_dict["annotation_layers"] = annotation_layers
# Add an impersonation key to cache if impersonation is enabled on the db
# or if the CACHE_QUERY_BY_USER flag is on
# or if the CACHE_QUERY_BY_USER flag is on or per_user_caching is enabled on
# the database
try:
database = self.datasource.database # type: ignore
extra = json.loads(database.extra or "{}")
if (
feature_flag_manager.is_feature_enabled("CACHE_IMPERSONATION")
and database.impersonate_user
) or feature_flag_manager.is_feature_enabled("CACHE_QUERY_BY_USER"):
(
feature_flag_manager.is_feature_enabled("CACHE_IMPERSONATION")
and database.impersonate_user
)
or feature_flag_manager.is_feature_enabled("CACHE_QUERY_BY_USER")
or extra.get("per_user_caching", False)
):
if key := database.db_engine_spec.get_impersonation_key(
getattr(g, "user", None)
):

View File

@@ -831,6 +831,7 @@ class ImportV1DatabaseExtraSchema(Schema):
disable_data_preview = fields.Boolean(required=False)
disable_drill_to_detail = fields.Boolean(required=False)
allow_multi_catalog = fields.Boolean(required=False)
per_user_caching = fields.Boolean(required=False)
version = fields.String(required=False, allow_none=True)
schema_options = fields.Dict(keys=fields.Str(), values=fields.Raw())

View File

@@ -105,6 +105,8 @@ def data_loader(
pandas_loader_configuration: PandasLoaderConfigurations,
table_to_df_convertor: TableToDfConvertor,
) -> DataLoader:
if example_db_engine.dialect.name == PRESTO:
example_db_engine.dialect.get_view_names = Mock(return_value=[])
return PandasDataLoader(
example_db_engine, pandas_loader_configuration, table_to_df_convertor
)