mirror of
https://github.com/apache/superset.git
synced 2026-06-09 09:39:25 +00:00
feat(core): add support for empty results and refresh indicator (#1121)
* feat(core): add support for empty results and refresh indicator * add workaround for reselect limitation * fix tests, add new test and disable typing for ChartProps.ts
This commit is contained in:
committed by
Yongjie Zhao
parent
a60771f509
commit
c2c84da6f0
@@ -12,6 +12,7 @@ const defaultProps = {
|
||||
FallbackComponent: DefaultFallbackComponent,
|
||||
height: 400 as string | number,
|
||||
width: '100%' as string | number,
|
||||
enableNoResults: true,
|
||||
};
|
||||
|
||||
export type FallbackPropsWithDimension = FallbackProps & Partial<Dimension>;
|
||||
@@ -30,6 +31,8 @@ export type Props = Omit<SuperChartCoreProps, 'chartProps'> &
|
||||
disableErrorBoundary?: boolean;
|
||||
/** debounceTime to check for container resize */
|
||||
debounceTime?: number;
|
||||
/** enable "No Results" message if empty result set */
|
||||
enableNoResults?: boolean;
|
||||
/** Component to render when there are unexpected errors */
|
||||
FallbackComponent?: React.ComponentType<FallbackPropsWithDimension>;
|
||||
/** Event listener for unexpected errors from chart */
|
||||
@@ -112,6 +115,7 @@ export default class SuperChart extends React.PureComponent<Props, {}> {
|
||||
onErrorBoundary,
|
||||
Wrapper,
|
||||
queriesData,
|
||||
enableNoResults,
|
||||
...rest
|
||||
} = this.props as PropsWithDefault;
|
||||
|
||||
@@ -125,8 +129,9 @@ export default class SuperChart extends React.PureComponent<Props, {}> {
|
||||
let chart;
|
||||
// Render the no results component if the query data is null or empty
|
||||
const noResultQueries =
|
||||
!queriesData ||
|
||||
queriesData.every(({ data }) => !data || (Array.isArray(data) && data.length === 0));
|
||||
enableNoResults &&
|
||||
(!queriesData ||
|
||||
queriesData.every(({ data }) => !data || (Array.isArray(data) && data.length === 0)));
|
||||
if (noResultQueries) {
|
||||
chart = <NoResultsComponent id={id} className={className} height={height} width={width} />;
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface ChartMetadataConfig {
|
||||
credits?: string[];
|
||||
description?: string;
|
||||
datasourceCount?: number;
|
||||
enableNoResults?: boolean;
|
||||
show?: boolean;
|
||||
supportedAnnotationTypes?: string[];
|
||||
thumbnail: string;
|
||||
@@ -40,6 +41,8 @@ export default class ChartMetadata {
|
||||
|
||||
datasourceCount: number;
|
||||
|
||||
enableNoResults: boolean;
|
||||
|
||||
constructor(config: ChartMetadataConfig) {
|
||||
const {
|
||||
name,
|
||||
@@ -52,6 +55,7 @@ export default class ChartMetadata {
|
||||
useLegacyApi = false,
|
||||
behaviors = [],
|
||||
datasourceCount = 1,
|
||||
enableNoResults = true,
|
||||
} = config;
|
||||
|
||||
this.name = name;
|
||||
@@ -73,6 +77,7 @@ export default class ChartMetadata {
|
||||
this.useLegacyApi = useLegacyApi;
|
||||
this.behaviors = behaviors;
|
||||
this.datasourceCount = datasourceCount;
|
||||
this.enableNoResults = enableNoResults;
|
||||
}
|
||||
|
||||
canBeAnnotationType(type: string): boolean {
|
||||
@@ -91,6 +96,7 @@ export default class ChartMetadata {
|
||||
useLegacyApi: this.useLegacyApi,
|
||||
behaviors: this.behaviors,
|
||||
datasourceCount: this.datasourceCount,
|
||||
enableNoResults: this.enableNoResults,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/** Type checking is disabled for this file due to reselect only supporting
|
||||
* TS declarations for selectors with up to 12 arguments. */
|
||||
// @ts-nocheck
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
AppSection,
|
||||
@@ -66,6 +69,8 @@ export interface ChartPropsConfig {
|
||||
behaviors?: Behavior[];
|
||||
/** Application section of the chart on the screen (in what components/screen it placed) */
|
||||
appSection?: AppSection;
|
||||
/** is the chart refreshing its contents */
|
||||
isRefreshing?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_WIDTH = 800;
|
||||
@@ -102,6 +107,8 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
|
||||
|
||||
appSection?: AppSection;
|
||||
|
||||
isRefreshing?: boolean;
|
||||
|
||||
constructor(config: ChartPropsConfig & { formData?: FormData } = {}) {
|
||||
const {
|
||||
annotationData = {},
|
||||
@@ -116,6 +123,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
|
||||
width = DEFAULT_WIDTH,
|
||||
height = DEFAULT_HEIGHT,
|
||||
appSection,
|
||||
isRefreshing,
|
||||
} = config;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
@@ -131,6 +139,7 @@ export default class ChartProps<FormData extends RawFormData = RawFormData> {
|
||||
this.filterState = filterState;
|
||||
this.behaviors = behaviors;
|
||||
this.appSection = appSection;
|
||||
this.isRefreshing = isRefreshing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +158,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
|
||||
input => input.filterState,
|
||||
input => input.behaviors,
|
||||
input => input.appSection,
|
||||
input => input.isRefreshing,
|
||||
(
|
||||
annotationData,
|
||||
datasource,
|
||||
@@ -162,6 +172,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
|
||||
filterState,
|
||||
behaviors,
|
||||
appSection,
|
||||
isRefreshing,
|
||||
) =>
|
||||
new ChartProps({
|
||||
annotationData,
|
||||
@@ -176,6 +187,7 @@ ChartProps.createSelector = function create(): ChartPropsSelector {
|
||||
width,
|
||||
behaviors,
|
||||
appSection,
|
||||
isRefreshing,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChartProps } from '@superset-ui/core/src';
|
||||
import { Behavior, ChartProps } from '@superset-ui/core/src';
|
||||
|
||||
const RAW_FORM_DATA = {
|
||||
some_field: 1,
|
||||
@@ -10,6 +10,7 @@ const RAW_DATASOURCE = {
|
||||
|
||||
const QUERY_DATA = { data: {} };
|
||||
const QUERIES_DATA = [QUERY_DATA];
|
||||
const BEHAVIORS = [Behavior.NATIVE_FILTER, Behavior.INTERACTIVE_CHART];
|
||||
|
||||
describe('ChartProps', () => {
|
||||
it('exists', () => {
|
||||
@@ -51,6 +52,8 @@ describe('ChartProps', () => {
|
||||
datasource: RAW_DATASOURCE,
|
||||
formData: RAW_FORM_DATA,
|
||||
queriesData: QUERIES_DATA,
|
||||
behaviors: BEHAVIORS,
|
||||
isRefreshing: false,
|
||||
});
|
||||
const props2 = selector({
|
||||
width: 800,
|
||||
@@ -58,9 +61,37 @@ describe('ChartProps', () => {
|
||||
datasource: RAW_DATASOURCE,
|
||||
formData: RAW_FORM_DATA,
|
||||
queriesData: QUERIES_DATA,
|
||||
behaviors: BEHAVIORS,
|
||||
isRefreshing: false,
|
||||
});
|
||||
expect(props1).toBe(props2);
|
||||
});
|
||||
it('selector returns a new chartProps if the 13th field changes', () => {
|
||||
/** this test is here to test for selectors that exceed 12 arguments (
|
||||
* isRefreshing is the 13th argument, which is missing TS declarations).
|
||||
* See: https://github.com/reduxjs/reselect/issues/378
|
||||
*/
|
||||
|
||||
const props1 = selector({
|
||||
width: 800,
|
||||
height: 600,
|
||||
datasource: RAW_DATASOURCE,
|
||||
formData: RAW_FORM_DATA,
|
||||
queriesData: QUERIES_DATA,
|
||||
behaviors: BEHAVIORS,
|
||||
isRefreshing: false,
|
||||
});
|
||||
const props2 = selector({
|
||||
width: 800,
|
||||
height: 600,
|
||||
datasource: RAW_DATASOURCE,
|
||||
formData: RAW_FORM_DATA,
|
||||
queriesData: QUERIES_DATA,
|
||||
behaviors: BEHAVIORS,
|
||||
isRefreshing: true,
|
||||
});
|
||||
expect(props1).not.toBe(props2);
|
||||
});
|
||||
it('selector returns a new chartProps if some input fields change', () => {
|
||||
const props1 = selector({
|
||||
width: 800,
|
||||
|
||||
Reference in New Issue
Block a user