mirror of
https://github.com/apache/superset.git
synced 2026-06-26 01:49:21 +00:00
Compare commits
4 Commits
chore/ci/s
...
dnd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4342ff4df6 | ||
|
|
9042601f5c | ||
|
|
1bebbcf067 | ||
|
|
58ac91c059 |
@@ -112,6 +112,8 @@ services:
|
|||||||
superset-init-light:
|
superset-init-light:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
volumes: *superset-volumes
|
volumes: *superset-volumes
|
||||||
|
ports:
|
||||||
|
- "${SUPERSET_PORT:-8088}:8088"
|
||||||
environment:
|
environment:
|
||||||
DATABASE_HOST: db-light
|
DATABASE_HOST: db-light
|
||||||
DATABASE_DB: superset_light
|
DATABASE_DB: superset_light
|
||||||
@@ -157,7 +159,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
# set this to false if you have perf issues running the npm i; npm run dev in-docker
|
# 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!
|
# 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
|
NPM_RUN_PRUNE: false
|
||||||
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
SCARF_ANALYTICS: "${SCARF_ANALYTICS:-}"
|
||||||
# configuring the dev-server to use the host.docker.internal to connect to the backend
|
# 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/cache": "^11.4.0",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@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",
|
"@reduxjs/toolkit": "^1.9.3",
|
||||||
"@rjsf/core": "^5.24.13",
|
"@rjsf/core": "^5.24.13",
|
||||||
"@rjsf/utils": "^5.24.3",
|
"@rjsf/utils": "^5.24.3",
|
||||||
@@ -144,6 +147,7 @@
|
|||||||
"chrono-node": "^2.7.8",
|
"chrono-node": "^2.7.8",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"content-disposition": "^1.0.1",
|
"content-disposition": "^1.0.1",
|
||||||
|
"currencyformatter.js": "^2.2.0",
|
||||||
"d3-color": "^3.1.0",
|
"d3-color": "^3.1.0",
|
||||||
"d3-scale": "^2.1.2",
|
"d3-scale": "^2.1.2",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.19",
|
||||||
@@ -160,6 +164,7 @@
|
|||||||
"geostyler-openlayers-parser": "^4.3.0",
|
"geostyler-openlayers-parser": "^4.3.0",
|
||||||
"geostyler-style": "7.5.0",
|
"geostyler-style": "7.5.0",
|
||||||
"geostyler-wfs-parser": "^2.0.3",
|
"geostyler-wfs-parser": "^2.0.3",
|
||||||
|
"global-box": "^2.0.2",
|
||||||
"googleapis": "^169.0.0",
|
"googleapis": "^169.0.0",
|
||||||
"immer": "^11.0.1",
|
"immer": "^11.0.1",
|
||||||
"interweave": "^13.1.1",
|
"interweave": "^13.1.1",
|
||||||
@@ -178,6 +183,7 @@
|
|||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"ol": "^7.5.2",
|
"ol": "^7.5.2",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
|
"query-string": "^6.14.1",
|
||||||
"re-resizable": "^6.11.2",
|
"re-resizable": "^6.11.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-checkbox-tree": "^1.8.0",
|
"react-checkbox-tree": "^1.8.0",
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ export enum Behavior {
|
|||||||
*/
|
*/
|
||||||
DrillToDetail = 'DRILL_TO_DETAIL',
|
DrillToDetail = 'DRILL_TO_DETAIL',
|
||||||
DrillBy = 'DRILL_BY',
|
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 {
|
export interface ContextMenuFilters {
|
||||||
|
|||||||
@@ -31,13 +31,14 @@
|
|||||||
"@deck.gl/layers": "~9.2.5",
|
"@deck.gl/layers": "~9.2.5",
|
||||||
"@deck.gl/mesh-layers": "~9.2.2",
|
"@deck.gl/mesh-layers": "~9.2.2",
|
||||||
"@deck.gl/react": "~9.2.5",
|
"@deck.gl/react": "~9.2.5",
|
||||||
|
"@deck.gl/widgets": "~9.2.2",
|
||||||
"@luma.gl/constants": "~9.2.4",
|
"@luma.gl/constants": "~9.2.4",
|
||||||
"@luma.gl/core": "~9.2.2",
|
"@luma.gl/core": "~9.2.2",
|
||||||
"@luma.gl/engine": "~9.2.4",
|
"@luma.gl/engine": "~9.2.4",
|
||||||
"@luma.gl/shadertools": "~9.2.2",
|
"@luma.gl/shadertools": "~9.2.2",
|
||||||
"@luma.gl/webgl": "~9.2.2",
|
"@luma.gl/webgl": "~9.2.2",
|
||||||
"@mapbox/tiny-sdf": "^2.0.7",
|
|
||||||
"@mapbox/geojson-extent": "^1.0.1",
|
"@mapbox/geojson-extent": "^1.0.1",
|
||||||
|
"@mapbox/tiny-sdf": "^2.0.7",
|
||||||
"@math.gl/web-mercator": "^4.1.0",
|
"@math.gl/web-mercator": "^4.1.0",
|
||||||
"@types/d3-array": "^3.2.2",
|
"@types/d3-array": "^3.2.2",
|
||||||
"@types/geojson": "^7946.0.16",
|
"@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 &&
|
ownState?.agGridFilterModel &&
|
||||||
Object.keys(ownState.agGridFilterModel).length > 0;
|
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 = !(
|
const bypassNoResult = !(
|
||||||
formData?.server_pagination &&
|
(formData?.server_pagination && (hasSearchText || hasAgGridFilters)) ||
|
||||||
(hasSearchText || hasAgGridFilters)
|
allowsEmptyResults
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { useState, useEffect, useCallback, useMemo, ReactNode } from 'react';
|
|||||||
import Split from 'react-split';
|
import Split from 'react-split';
|
||||||
import { t } from '@apache-superset/core';
|
import { t } from '@apache-superset/core';
|
||||||
import {
|
import {
|
||||||
|
Behavior,
|
||||||
DatasourceType,
|
DatasourceType,
|
||||||
ensureIsArray,
|
ensureIsArray,
|
||||||
isFeatureEnabled,
|
isFeatureEnabled,
|
||||||
@@ -168,16 +169,33 @@ const ExploreChartPanel = ({
|
|||||||
const [showDatasetModal, setShowDatasetModal] = useState(false);
|
const [showDatasetModal, setShowDatasetModal] = useState(false);
|
||||||
|
|
||||||
const metaDataRegistry = getChartMetadataRegistry();
|
const metaDataRegistry = getChartMetadataRegistry();
|
||||||
const { useLegacyApi } = metaDataRegistry.get(vizType) ?? {};
|
const chartMetadata = metaDataRegistry.get(vizType);
|
||||||
|
const { useLegacyApi } = chartMetadata ?? {};
|
||||||
const vizTypeNeedsDataset =
|
const vizTypeNeedsDataset =
|
||||||
useLegacyApi && datasource.type !== DatasourceType.Table;
|
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
|
// added boolean column to below show boolean so that the errors aren't overlapping
|
||||||
const showAlertBanner =
|
const showAlertBanner =
|
||||||
!chartAlert &&
|
!chartAlert &&
|
||||||
chartIsStale &&
|
chartIsStale &&
|
||||||
!vizTypeNeedsDataset &&
|
!vizTypeNeedsDataset &&
|
||||||
chart.chartStatus !== 'failed' &&
|
chart.chartStatus !== 'failed' &&
|
||||||
ensureIsArray(chart.queriesResponse).length > 0;
|
ensureIsArray(chart.queriesResponse).length > 0 &&
|
||||||
|
!isUnconfiguredEmptyChart;
|
||||||
|
|
||||||
const updateQueryContext = useCallback(
|
const updateQueryContext = useCallback(
|
||||||
async function fetchChartData() {
|
async function fetchChartData() {
|
||||||
|
|||||||
@@ -456,15 +456,27 @@ function ExploreViewContainer(props) {
|
|||||||
}
|
}
|
||||||
}, [isDynamicPluginLoading]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
|
// Skip if already triggered or still loading dynamic plugin
|
||||||
|
if (hasTriggeredInitialQuery || isDynamicPluginLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const hasError = Object.values(props.controls).some(
|
const hasError = Object.values(props.controls).some(
|
||||||
control =>
|
control =>
|
||||||
control.validationErrors && control.validationErrors.length > 0,
|
control.validationErrors && control.validationErrors.length > 0,
|
||||||
);
|
);
|
||||||
if (!hasError) {
|
if (!hasError) {
|
||||||
props.actions.triggerQuery(true, props.chart.id);
|
props.actions.triggerQuery(true, props.chart.id);
|
||||||
|
setHasTriggeredInitialQuery(true);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [props.controls, isDynamicPluginLoading, hasTriggeredInitialQuery]);
|
||||||
|
|
||||||
const reRenderChart = useCallback(
|
const reRenderChart = useCallback(
|
||||||
controlsChanged => {
|
controlsChanged => {
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ import { HandlebarsChartPlugin } from '@superset-ui/plugin-chart-handlebars';
|
|||||||
import { ChartCustomizationPlugins, FilterPlugins } from 'src/constants';
|
import { ChartCustomizationPlugins, FilterPlugins } from 'src/constants';
|
||||||
import AgGridTableChartPlugin from '@superset-ui/plugin-chart-ag-grid-table';
|
import AgGridTableChartPlugin from '@superset-ui/plugin-chart-ag-grid-table';
|
||||||
import TimeTableChartPlugin from '../TimeTable';
|
import TimeTableChartPlugin from '../TimeTable';
|
||||||
|
import {
|
||||||
|
GlyphBigNumberChartPlugin,
|
||||||
|
GlyphFancyBigNumberChartPlugin,
|
||||||
|
GlyphLineChartPlugin,
|
||||||
|
} from '@superset-ui/plugin-chart-glyph';
|
||||||
|
|
||||||
export default class MainPreset extends Preset {
|
export default class MainPreset extends Preset {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -211,6 +216,12 @@ export default class MainPreset extends Preset {
|
|||||||
}).configure({ key: VizType.Cartodiagram }),
|
}).configure({ key: VizType.Cartodiagram }),
|
||||||
...experimentalPlugins,
|
...experimentalPlugins,
|
||||||
...agGridTablePlugin,
|
...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_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):
|
class AnnotationLayerSchema(Schema):
|
||||||
|
|||||||
@@ -1149,6 +1149,26 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
|
|||||||
datasource types (Query, SqlaTable, etc.).
|
datasource types (Query, SqlaTable, etc.).
|
||||||
"""
|
"""
|
||||||
qry_start_dttm = datetime.now()
|
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)
|
query_str_ext = self.get_query_str_extended(query_obj)
|
||||||
sql = query_str_ext.sql
|
sql = query_str_ext.sql
|
||||||
status = QueryStatus.SUCCESS
|
status = QueryStatus.SUCCESS
|
||||||
@@ -2727,7 +2747,10 @@ class ExploreMixin: # pylint: disable=too-many-public-methods
|
|||||||
"and is required by this type of chart"
|
"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?"))
|
raise QueryObjectValidationError(_("Empty query?"))
|
||||||
|
|
||||||
metrics_exprs: list[ColumnElement] = []
|
metrics_exprs: list[ColumnElement] = []
|
||||||
|
|||||||
Reference in New Issue
Block a user