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:
Ville Brofeldt
2021-05-18 18:59:51 +03:00
committed by Yongjie Zhao
parent a60771f509
commit c2c84da6f0
4 changed files with 57 additions and 3 deletions

View File

@@ -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 {

View File

@@ -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,
});
}
}

View File

@@ -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,
}),
);
};

View File

@@ -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,