mirror of
https://github.com/apache/superset.git
synced 2026-04-30 13:34:20 +00:00
Compare commits
4 Commits
fix-explor
...
dnd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4342ff4df6 | ||
|
|
9042601f5c | ||
|
|
1bebbcf067 | ||
|
|
58ac91c059 |
@@ -112,6 +112,8 @@ services:
|
||||
superset-init-light:
|
||||
condition: service_completed_successfully
|
||||
volumes: *superset-volumes
|
||||
ports:
|
||||
- "${SUPERSET_PORT:-8088}:8088"
|
||||
environment:
|
||||
DATABASE_HOST: db-light
|
||||
DATABASE_DB: superset_light
|
||||
@@ -157,7 +159,7 @@ services:
|
||||
environment:
|
||||
# set this to false if you have perf issues running the npm i; npm run dev in-docker
|
||||
# if you do so, you have to run this manually on the host, which should perform better!
|
||||
BUILD_SUPERSET_FRONTEND_IN_DOCKER: true
|
||||
BUILD_SUPERSET_FRONTEND_IN_DOCKER: false
|
||||
NPM_RUN_PRUNE: false
|
||||
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
||||
# configuring the dev-server to use the host.docker.internal to connect to the backend
|
||||
|
||||
3225
superset-frontend/package-lock.json
generated
3225
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -102,6 +102,9 @@
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@fontsource/fira-code": "^5.2.7",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@react-spring/web": "^10.0.3",
|
||||
"@reduxjs/toolkit": "^1.9.3",
|
||||
"@rjsf/core": "^5.24.13",
|
||||
"@rjsf/utils": "^5.24.3",
|
||||
@@ -144,6 +147,7 @@
|
||||
"chrono-node": "^2.7.8",
|
||||
"classnames": "^2.2.5",
|
||||
"content-disposition": "^1.0.1",
|
||||
"currencyformatter.js": "^2.2.0",
|
||||
"d3-color": "^3.1.0",
|
||||
"d3-scale": "^2.1.2",
|
||||
"dayjs": "^1.11.19",
|
||||
@@ -160,6 +164,7 @@
|
||||
"geostyler-openlayers-parser": "^4.3.0",
|
||||
"geostyler-style": "7.5.0",
|
||||
"geostyler-wfs-parser": "^2.0.3",
|
||||
"global-box": "^2.0.2",
|
||||
"googleapis": "^169.0.0",
|
||||
"immer": "^11.0.1",
|
||||
"interweave": "^13.1.1",
|
||||
@@ -178,6 +183,7 @@
|
||||
"nanoid": "^5.1.6",
|
||||
"ol": "^7.5.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^6.14.1",
|
||||
"re-resizable": "^6.11.2",
|
||||
"react": "^17.0.2",
|
||||
"react-checkbox-tree": "^1.8.0",
|
||||
|
||||
@@ -33,6 +33,14 @@ export enum Behavior {
|
||||
*/
|
||||
DrillToDetail = 'DRILL_TO_DETAIL',
|
||||
DrillBy = 'DRILL_BY',
|
||||
|
||||
/**
|
||||
* Include `ALLOWS_EMPTY_RESULTS` behavior if the chart handles empty/no data
|
||||
* gracefully (e.g., showing a drop zone for drag-and-drop configuration).
|
||||
* Charts with this behavior will receive empty data instead of seeing
|
||||
* the "No results" message.
|
||||
*/
|
||||
AllowsEmptyResults = 'ALLOWS_EMPTY_RESULTS',
|
||||
}
|
||||
|
||||
export interface ContextMenuFilters {
|
||||
|
||||
@@ -31,13 +31,14 @@
|
||||
"@deck.gl/layers": "~9.2.5",
|
||||
"@deck.gl/mesh-layers": "~9.2.2",
|
||||
"@deck.gl/react": "~9.2.5",
|
||||
"@deck.gl/widgets": "~9.2.2",
|
||||
"@luma.gl/constants": "~9.2.4",
|
||||
"@luma.gl/core": "~9.2.2",
|
||||
"@luma.gl/engine": "~9.2.4",
|
||||
"@luma.gl/shadertools": "~9.2.2",
|
||||
"@luma.gl/webgl": "~9.2.2",
|
||||
"@mapbox/tiny-sdf": "^2.0.7",
|
||||
"@mapbox/geojson-extent": "^1.0.1",
|
||||
"@mapbox/tiny-sdf": "^2.0.7",
|
||||
"@math.gl/web-mercator": "^4.1.0",
|
||||
"@types/d3-array": "^3.2.2",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
|
||||
14
superset-frontend/plugins/plugin-chart-glyph/package.json
Normal file
14
superset-frontend/plugins/plugin-chart-glyph/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@superset-ui/plugin-chart-glyph",
|
||||
"version": "0.0.1",
|
||||
"description": "Glyph semantic charts for Superset",
|
||||
"main": "src/index.ts",
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^17 || ^18 || ^19"
|
||||
},
|
||||
"dependencies": {
|
||||
"glyph": "file:../../../../glyph"
|
||||
}
|
||||
}
|
||||
21
superset-frontend/plugins/plugin-chart-glyph/src/index.ts
Normal file
21
superset-frontend/plugins/plugin-chart-glyph/src/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ChartPlugin, ChartMetadata, buildQueryContext } from '@superset-ui/core';
|
||||
import { BigNumber, FancyBigNumber, LineChart, makeChartPlugin } from 'glyph';
|
||||
import thumbnail from './thumbnail.png';
|
||||
|
||||
export const GlyphBigNumberChartPlugin = makeChartPlugin(
|
||||
BigNumber,
|
||||
{ ChartPlugin, ChartMetadata, buildQueryContext },
|
||||
{ thumbnail },
|
||||
);
|
||||
|
||||
export const GlyphFancyBigNumberChartPlugin = makeChartPlugin(
|
||||
FancyBigNumber,
|
||||
{ ChartPlugin, ChartMetadata, buildQueryContext },
|
||||
{ thumbnail },
|
||||
);
|
||||
|
||||
export const GlyphLineChartPlugin = makeChartPlugin(
|
||||
LineChart,
|
||||
{ ChartPlugin, ChartMetadata, buildQueryContext },
|
||||
{ thumbnail },
|
||||
);
|
||||
BIN
superset-frontend/plugins/plugin-chart-glyph/src/thumbnail.png
Normal file
BIN
superset-frontend/plugins/plugin-chart-glyph/src/thumbnail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -365,9 +365,15 @@ class ChartRenderer extends Component {
|
||||
ownState?.agGridFilterModel &&
|
||||
Object.keys(ownState.agGridFilterModel).length > 0;
|
||||
|
||||
// Check if chart allows empty results (e.g., for drag-and-drop configuration)
|
||||
const chartMetadata = getChartMetadataRegistry().get(vizType);
|
||||
const allowsEmptyResults = chartMetadata?.behaviors?.includes(
|
||||
Behavior.AllowsEmptyResults,
|
||||
);
|
||||
|
||||
const bypassNoResult = !(
|
||||
formData?.server_pagination &&
|
||||
(hasSearchText || hasAgGridFilters)
|
||||
(formData?.server_pagination && (hasSearchText || hasAgGridFilters)) ||
|
||||
allowsEmptyResults
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useState, useEffect, useCallback, useMemo, ReactNode } from 'react';
|
||||
import Split from 'react-split';
|
||||
import { t } from '@apache-superset/core';
|
||||
import {
|
||||
Behavior,
|
||||
DatasourceType,
|
||||
ensureIsArray,
|
||||
isFeatureEnabled,
|
||||
@@ -168,16 +169,33 @@ const ExploreChartPanel = ({
|
||||
const [showDatasetModal, setShowDatasetModal] = useState(false);
|
||||
|
||||
const metaDataRegistry = getChartMetadataRegistry();
|
||||
const { useLegacyApi } = metaDataRegistry.get(vizType) ?? {};
|
||||
const chartMetadata = metaDataRegistry.get(vizType);
|
||||
const { useLegacyApi } = chartMetadata ?? {};
|
||||
const vizTypeNeedsDataset =
|
||||
useLegacyApi && datasource.type !== DatasourceType.Table;
|
||||
|
||||
// Check if chart allows empty results (for drag-and-drop configuration)
|
||||
const allowsEmptyResults = chartMetadata?.behaviors?.includes(
|
||||
Behavior.AllowsEmptyResults,
|
||||
);
|
||||
// Check if query returned no actual data rows
|
||||
const hasNoDataRows =
|
||||
ensureIsArray(chart.queriesResponse).length > 0 &&
|
||||
chart.queriesResponse?.every(
|
||||
response => !response?.data || response.data.length === 0,
|
||||
);
|
||||
// Suppress stale warning for AllowsEmptyResults charts with no data
|
||||
// (they're in initial unconfigured state)
|
||||
const isUnconfiguredEmptyChart = allowsEmptyResults && hasNoDataRows;
|
||||
|
||||
// added boolean column to below show boolean so that the errors aren't overlapping
|
||||
const showAlertBanner =
|
||||
!chartAlert &&
|
||||
chartIsStale &&
|
||||
!vizTypeNeedsDataset &&
|
||||
chart.chartStatus !== 'failed' &&
|
||||
ensureIsArray(chart.queriesResponse).length > 0;
|
||||
ensureIsArray(chart.queriesResponse).length > 0 &&
|
||||
!isUnconfiguredEmptyChart;
|
||||
|
||||
const updateQueryContext = useCallback(
|
||||
async function fetchChartData() {
|
||||
|
||||
@@ -456,15 +456,27 @@ function ExploreViewContainer(props) {
|
||||
}
|
||||
}, [isDynamicPluginLoading]);
|
||||
|
||||
// Track if we've already triggered initial query
|
||||
const [hasTriggeredInitialQuery, setHasTriggeredInitialQuery] =
|
||||
useState(false);
|
||||
|
||||
// Auto-trigger query when there are no validation errors
|
||||
// This effect runs on mount and when controls change (e.g., after dynamic plugin loads)
|
||||
useEffect(() => {
|
||||
// Skip if already triggered or still loading dynamic plugin
|
||||
if (hasTriggeredInitialQuery || isDynamicPluginLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasError = Object.values(props.controls).some(
|
||||
control =>
|
||||
control.validationErrors && control.validationErrors.length > 0,
|
||||
);
|
||||
if (!hasError) {
|
||||
props.actions.triggerQuery(true, props.chart.id);
|
||||
setHasTriggeredInitialQuery(true);
|
||||
}
|
||||
}, []);
|
||||
}, [props.controls, isDynamicPluginLoading, hasTriggeredInitialQuery]);
|
||||
|
||||
const reRenderChart = useCallback(
|
||||
controlsChanged => {
|
||||
|
||||
@@ -88,6 +88,11 @@ import { HandlebarsChartPlugin } from '@superset-ui/plugin-chart-handlebars';
|
||||
import { ChartCustomizationPlugins, FilterPlugins } from 'src/constants';
|
||||
import AgGridTableChartPlugin from '@superset-ui/plugin-chart-ag-grid-table';
|
||||
import TimeTableChartPlugin from '../TimeTable';
|
||||
import {
|
||||
GlyphBigNumberChartPlugin,
|
||||
GlyphFancyBigNumberChartPlugin,
|
||||
GlyphLineChartPlugin,
|
||||
} from '@superset-ui/plugin-chart-glyph';
|
||||
|
||||
export default class MainPreset extends Preset {
|
||||
constructor() {
|
||||
@@ -211,6 +216,12 @@ export default class MainPreset extends Preset {
|
||||
}).configure({ key: VizType.Cartodiagram }),
|
||||
...experimentalPlugins,
|
||||
...agGridTablePlugin,
|
||||
// Glyph semantic charts
|
||||
new GlyphBigNumberChartPlugin().configure({ key: 'glyph_big_number' }),
|
||||
new GlyphFancyBigNumberChartPlugin().configure({
|
||||
key: 'glyph_fancy_big_number',
|
||||
}),
|
||||
new GlyphLineChartPlugin().configure({ key: 'glyph_line_chart' }),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1029,6 +1029,15 @@ class ChartDataExtrasSchema(Schema):
|
||||
},
|
||||
allow_none=True,
|
||||
)
|
||||
allow_empty_query = fields.Boolean(
|
||||
metadata={
|
||||
"description": (
|
||||
"Allow queries with no metrics, columns, or groupby. "
|
||||
"Used by charts that support drag-and-drop configuration."
|
||||
)
|
||||
},
|
||||
load_default=False,
|
||||
)
|
||||
|
||||
|
||||
class AnnotationLayerSchema(Schema):
|
||||
|
||||
@@ -1149,6 +1149,26 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
|
||||
datasource types (Query, SqlaTable, etc.).
|
||||
"""
|
||||
qry_start_dttm = datetime.now()
|
||||
|
||||
# Check if this is an empty query (for drag-and-drop configured charts)
|
||||
extras = query_obj.get("extras", {}) or {}
|
||||
metrics = query_obj.get("metrics") or []
|
||||
columns = query_obj.get("columns") or []
|
||||
groupby = query_obj.get("groupby") or []
|
||||
if extras.get("allow_empty_query") and not metrics and not columns and not groupby:
|
||||
# Return empty result without executing any SQL
|
||||
return QueryResult(
|
||||
applied_template_filters=[],
|
||||
applied_filter_columns=[],
|
||||
rejected_filter_columns=[],
|
||||
status=QueryStatus.SUCCESS,
|
||||
df=pd.DataFrame(),
|
||||
duration=datetime.now() - qry_start_dttm,
|
||||
query="",
|
||||
errors=None,
|
||||
error_message=None,
|
||||
)
|
||||
|
||||
query_str_ext = self.get_query_str_extended(query_obj)
|
||||
sql = query_str_ext.sql
|
||||
status = QueryStatus.SUCCESS
|
||||
@@ -2727,7 +2747,10 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
|
||||
"and is required by this type of chart"
|
||||
)
|
||||
)
|
||||
if not metrics and not columns and not groupby:
|
||||
# Allow charts to opt-in to empty queries (for drag-and-drop configuration)
|
||||
# Note: The actual empty query handling is done in the query() method
|
||||
allow_empty = extras.get("allow_empty_query", False)
|
||||
if not metrics and not columns and not groupby and not allow_empty:
|
||||
raise QueryObjectValidationError(_("Empty query?"))
|
||||
|
||||
metrics_exprs: list[ColumnElement] = []
|
||||
|
||||
Reference in New Issue
Block a user