mirror of
https://github.com/apache/superset.git
synced 2024-09-18 19:49:37 -04:00
feat(table): enable table filter and better typing (#344)
This commit is contained in:
parent
aa2cdcaad5
commit
9b009e7198
@ -62,7 +62,7 @@
|
|||||||
"@types/enzyme": "^3.10.3",
|
"@types/enzyme": "^3.10.3",
|
||||||
"@types/jest": "^25.1.1",
|
"@types/jest": "^25.1.1",
|
||||||
"@types/jsdom": "^12.2.4",
|
"@types/jsdom": "^12.2.4",
|
||||||
"@types/react-test-renderer": "^16.9.0",
|
"@types/react-test-renderer": "^16.9.2",
|
||||||
"enzyme": "^3.10.0",
|
"enzyme": "^3.10.0",
|
||||||
"enzyme-adapter-react-16": "^1.15.1",
|
"enzyme-adapter-react-16": "^1.15.1",
|
||||||
"enzyme-to-json": "^3.4.3",
|
"enzyme-to-json": "^3.4.3",
|
||||||
@ -73,9 +73,9 @@
|
|||||||
"jest-mock-console": "^1.0.0",
|
"jest-mock-console": "^1.0.0",
|
||||||
"lerna": "^3.15.0",
|
"lerna": "^3.15.0",
|
||||||
"lint-staged": "^10.0.3",
|
"lint-staged": "^10.0.3",
|
||||||
"react": "^16.9.0",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.9.0",
|
"react-dom": "^16.13.1",
|
||||||
"react-test-renderer": "^16.9.0"
|
"react-test-renderer": "^16.13.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.10.0",
|
"node": ">=10.10.0",
|
||||||
|
@ -26,12 +26,12 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "^16.7.17",
|
"@types/react": "^16.9.34",
|
||||||
"@vx/responsive": "^0.0.195",
|
"@vx/responsive": "^0.0.195",
|
||||||
"csstype": "^2.6.4"
|
"csstype": "^2.6.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@superset-ui/core": "^0.12.0",
|
"@superset-ui/core": "^0.12.0",
|
||||||
"react": "^16.7.17"
|
"react": "^16.13.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "^16.7.17",
|
"@types/react": "^16.9.34",
|
||||||
"@types/react-loadable": "^5.4.2",
|
"@types/react-loadable": "^5.4.2",
|
||||||
"@vx/responsive": "^0.0.195",
|
"@vx/responsive": "^0.0.195",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
@ -45,6 +45,6 @@
|
|||||||
"@superset-ui/core": "^0.12.0",
|
"@superset-ui/core": "^0.12.0",
|
||||||
"@superset-ui/dimension": "^0.12.0",
|
"@superset-ui/dimension": "^0.12.0",
|
||||||
"@superset-ui/query": "^0.12.0",
|
"@superset-ui/query": "^0.12.0",
|
||||||
"react": "^16.7.17"
|
"react": "^16.13.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import { QueryFormData, Datasource } from '@superset-ui/query';
|
import { QueryFormData, Datasource } from '@superset-ui/query';
|
||||||
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
|
import getChartBuildQueryRegistry from '../registries/ChartBuildQueryRegistrySingleton';
|
||||||
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
|
import getChartMetadataRegistry from '../registries/ChartMetadataRegistrySingleton';
|
||||||
import { QueryData } from '../models/ChartProps';
|
import { QueryData } from '../types/QueryResponse';
|
||||||
import { AnnotationLayerMetadata } from '../types/Annotation';
|
import { AnnotationLayerMetadata } from '../types/Annotation';
|
||||||
import { PlainObject } from '../types/Base';
|
import { PlainObject } from '../types/Base';
|
||||||
|
|
||||||
@ -94,7 +94,10 @@ export default class ChartClient {
|
|||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
} as RequestConfig)
|
} as RequestConfig)
|
||||||
.then(response => response.json as Json);
|
.then(response => {
|
||||||
|
// let's assume response.json always has the shape of QueryData
|
||||||
|
return response.json as QueryData;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(new Error(`Unknown chart type: ${visType}`));
|
return Promise.reject(new Error(`Unknown chart type: ${visType}`));
|
||||||
|
@ -3,7 +3,7 @@ import React, { ReactNode } from 'react';
|
|||||||
import { SupersetClientInterface, RequestConfig } from '@superset-ui/connection';
|
import { SupersetClientInterface, RequestConfig } from '@superset-ui/connection';
|
||||||
import { QueryFormData, Datasource } from '@superset-ui/query';
|
import { QueryFormData, Datasource } from '@superset-ui/query';
|
||||||
import ChartClient, { SliceIdAndOrFormData } from '../clients/ChartClient';
|
import ChartClient, { SliceIdAndOrFormData } from '../clients/ChartClient';
|
||||||
import { QueryData } from '../models/ChartProps';
|
import { QueryData } from '../types/QueryResponse';
|
||||||
|
|
||||||
interface Payload {
|
interface Payload {
|
||||||
formData: Partial<QueryFormData>;
|
formData: Partial<QueryFormData>;
|
||||||
|
@ -16,3 +16,4 @@ export { default as getChartTransformPropsRegistry } from './registries/ChartTra
|
|||||||
export { default as ChartDataProvider } from './components/ChartDataProvider';
|
export { default as ChartDataProvider } from './components/ChartDataProvider';
|
||||||
|
|
||||||
export * from './types/TransformFunction';
|
export * from './types/TransformFunction';
|
||||||
|
export * from './types/QueryResponse';
|
||||||
|
@ -1,30 +1,32 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { convertKeysToCamelCase } from '@superset-ui/core';
|
import { convertKeysToCamelCase } from '@superset-ui/core';
|
||||||
|
import { Datasource } from '@superset-ui/query';
|
||||||
import { HandlerFunction, PlainObject } from '../types/Base';
|
import { HandlerFunction, PlainObject } from '../types/Base';
|
||||||
|
import { QueryData, DataRecordFilters } from '../types/QueryResponse';
|
||||||
|
|
||||||
// TODO: more specific typing for these fields of ChartProps
|
// TODO: more specific typing for these fields of ChartProps
|
||||||
type AnnotationData = PlainObject;
|
type AnnotationData = PlainObject;
|
||||||
type CamelCaseDatasource = PlainObject;
|
|
||||||
type SnakeCaseDatasource = PlainObject;
|
type SnakeCaseDatasource = PlainObject;
|
||||||
type CamelCaseFormData = PlainObject;
|
type CamelCaseFormData = PlainObject;
|
||||||
type SnakeCaseFormData = PlainObject;
|
type SnakeCaseFormData = PlainObject;
|
||||||
export type QueryData = PlainObject;
|
type RawFormData = CamelCaseFormData | SnakeCaseFormData;
|
||||||
/** Initial values for the visualizations, currently used by only filter_box and table */
|
|
||||||
type InitialValues = PlainObject;
|
|
||||||
type ChartPropsSelector = (c: ChartPropsConfig) => ChartProps;
|
type ChartPropsSelector = (c: ChartPropsConfig) => ChartProps;
|
||||||
|
|
||||||
/** Optional field for event handlers, renderers */
|
/** Optional field for event handlers, renderers */
|
||||||
type Hooks = {
|
type Hooks = {
|
||||||
/** handle adding filters */
|
/**
|
||||||
onAddFilter?: HandlerFunction;
|
* sync active filters between chart and dashboard, "add" actually
|
||||||
/** handle errors */
|
* also handles "change" and "remove".
|
||||||
|
*/
|
||||||
|
onAddFilter?: (newFilters: DataRecordFilters, merge?: boolean) => void;
|
||||||
|
/** handle errors */
|
||||||
onError?: HandlerFunction;
|
onError?: HandlerFunction;
|
||||||
/** use the vis as control to update state */
|
/** use the vis as control to update state */
|
||||||
setControlValue?: HandlerFunction;
|
setControlValue?: HandlerFunction;
|
||||||
/** handle tooltip */
|
/** handle tooltip */
|
||||||
setTooltip?: HandlerFunction;
|
setTooltip?: HandlerFunction;
|
||||||
[key: string]: any;
|
} & PlainObject;
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferred format for ChartProps config
|
* Preferred format for ChartProps config
|
||||||
@ -37,9 +39,9 @@ export interface ChartPropsConfig {
|
|||||||
* Formerly called "filters", which was misleading because it is actually
|
* Formerly called "filters", which was misleading because it is actually
|
||||||
* initial values of the filter_box and table vis
|
* initial values of the filter_box and table vis
|
||||||
*/
|
*/
|
||||||
initialValues?: InitialValues;
|
initialValues?: DataRecordFilters;
|
||||||
/** Main configuration of the chart */
|
/** Main configuration of the chart */
|
||||||
formData?: SnakeCaseFormData;
|
formData?: RawFormData;
|
||||||
/** Chart height */
|
/** Chart height */
|
||||||
height?: number;
|
height?: number;
|
||||||
/** Programmatic overrides such as event handlers, renderers */
|
/** Programmatic overrides such as event handlers, renderers */
|
||||||
@ -53,22 +55,20 @@ export interface ChartPropsConfig {
|
|||||||
const DEFAULT_WIDTH = 800;
|
const DEFAULT_WIDTH = 800;
|
||||||
const DEFAULT_HEIGHT = 600;
|
const DEFAULT_HEIGHT = 600;
|
||||||
|
|
||||||
export default class ChartProps<
|
export default class ChartProps {
|
||||||
FormDataType extends CamelCaseFormData | SnakeCaseFormData = CamelCaseFormData
|
|
||||||
> {
|
|
||||||
static createSelector: () => ChartPropsSelector;
|
static createSelector: () => ChartPropsSelector;
|
||||||
|
|
||||||
annotationData: AnnotationData;
|
annotationData: AnnotationData;
|
||||||
|
|
||||||
datasource: CamelCaseDatasource;
|
datasource: Datasource;
|
||||||
|
|
||||||
rawDatasource: SnakeCaseDatasource;
|
rawDatasource: SnakeCaseDatasource;
|
||||||
|
|
||||||
initialValues: InitialValues;
|
initialValues: DataRecordFilters;
|
||||||
|
|
||||||
formData: CamelCaseFormData;
|
formData: CamelCaseFormData;
|
||||||
|
|
||||||
rawFormData: SnakeCaseFormData | CamelCaseFormData;
|
rawFormData: RawFormData;
|
||||||
|
|
||||||
height: number;
|
height: number;
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ export default class ChartProps<
|
|||||||
const {
|
const {
|
||||||
annotationData = {},
|
annotationData = {},
|
||||||
datasource = {},
|
datasource = {},
|
||||||
formData = {} as FormDataType,
|
formData = {},
|
||||||
hooks = {},
|
hooks = {},
|
||||||
initialValues = {},
|
initialValues = {},
|
||||||
queryData = {},
|
queryData = {},
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Types for query response
|
||||||
|
*/
|
||||||
|
import { PlainObject } from './Base';
|
||||||
|
|
||||||
|
export type DataRecordValue = number | string | boolean | Date | null;
|
||||||
|
|
||||||
|
export interface DataRecord {
|
||||||
|
[key: string]: DataRecordValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// data record value filters from FilterBox
|
||||||
|
export interface DataRecordFilters {
|
||||||
|
[key: string]: DataRecordValue[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// the response json from query API
|
||||||
|
export type QueryData = PlainObject;
|
@ -5,10 +5,10 @@ const RAW_FORM_DATA = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const RAW_DATASOURCE = {
|
const RAW_DATASOURCE = {
|
||||||
another_field: 2,
|
column_formats: { test: '%s' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const QUERY_DATA = {};
|
const QUERY_DATA = { data: {} };
|
||||||
|
|
||||||
describe('ChartProps', () => {
|
describe('ChartProps', () => {
|
||||||
it('exists', () => {
|
it('exists', () => {
|
||||||
@ -33,7 +33,7 @@ describe('ChartProps', () => {
|
|||||||
queryData: QUERY_DATA,
|
queryData: QUERY_DATA,
|
||||||
});
|
});
|
||||||
expect(props.formData.someField).toEqual(1);
|
expect(props.formData.someField).toEqual(1);
|
||||||
expect(props.datasource.anotherField).toEqual(2);
|
expect(props.datasource.columnFormats).toEqual(RAW_DATASOURCE.column_formats);
|
||||||
expect(props.rawFormData).toEqual(RAW_FORM_DATA);
|
expect(props.rawFormData).toEqual(RAW_FORM_DATA);
|
||||||
expect(props.rawDatasource).toEqual(RAW_DATASOURCE);
|
expect(props.rawDatasource).toEqual(RAW_DATASOURCE);
|
||||||
});
|
});
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
"core-js": "3.6.5",
|
"core-js": "3.6.5",
|
||||||
"gh-pages": "^2.2.0",
|
"gh-pages": "^2.2.0",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"react": "^16.6.0",
|
"react": "^16.13.1",
|
||||||
"storybook-addon-jsx": "^7.1.0"
|
"storybook-addon-jsx": "^7.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -190,7 +190,7 @@
|
|||||||
"fetch_values_predicate": null,
|
"fetch_values_predicate": null,
|
||||||
"template_params": null
|
"template_params": null
|
||||||
},
|
},
|
||||||
"initialValues": {},
|
"activeFilters": {},
|
||||||
"formData": {
|
"formData": {
|
||||||
"datasource": "3__table",
|
"datasource": "3__table",
|
||||||
"viz_type": "table",
|
"viz_type": "table",
|
||||||
@ -300,7 +300,7 @@
|
|||||||
"table_timestamp_format": "%Y-%m-%d %H:%M:%S",
|
"table_timestamp_format": "%Y-%m-%d %H:%M:%S",
|
||||||
"page_length": 0,
|
"page_length": 0,
|
||||||
"include_search": true,
|
"include_search": true,
|
||||||
"table_filter": false,
|
"table_filter": true,
|
||||||
"align_pn": false,
|
"align_pn": false,
|
||||||
"color_pn": true
|
"color_pn": true
|
||||||
},
|
},
|
||||||
@ -419,7 +419,7 @@
|
|||||||
"table_timestamp_format": "%Y-%m-%d %H:%M:%S",
|
"table_timestamp_format": "%Y-%m-%d %H:%M:%S",
|
||||||
"page_length": 0,
|
"page_length": 0,
|
||||||
"include_search": true,
|
"include_search": true,
|
||||||
"table_filter": false,
|
"table_filter": true,
|
||||||
"align_pn": false,
|
"align_pn": false,
|
||||||
"color_pn": true,
|
"color_pn": true,
|
||||||
"where": "",
|
"where": "",
|
||||||
|
@ -10,8 +10,15 @@ export enum DatasourceType {
|
|||||||
export interface Datasource {
|
export interface Datasource {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
|
||||||
type: DatasourceType;
|
type: DatasourceType;
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
metrics: QueryObjectMetric[];
|
metrics: QueryObjectMetric[];
|
||||||
|
description?: string;
|
||||||
|
// key is column names (labels)
|
||||||
|
columnFormats?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
verboseMap?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ export type QueryObjectFilterClause = {
|
|||||||
|
|
||||||
export type QueryObjectMetric = {
|
export type QueryObjectMetric = {
|
||||||
label: string;
|
label: string;
|
||||||
|
metric_name?: string;
|
||||||
|
d3format?: string;
|
||||||
} & Partial<AdhocMetric>;
|
} & Partial<AdhocMetric>;
|
||||||
|
|
||||||
export type QueryObjectExtras = Partial<{
|
export type QueryObjectExtras = Partial<{
|
||||||
|
@ -19,25 +19,56 @@
|
|||||||
import * as color from 'd3-color';
|
import * as color from 'd3-color';
|
||||||
import { getNumberFormatter, NumberFormats } from '@superset-ui/number-format';
|
import { getNumberFormatter, NumberFormats } from '@superset-ui/number-format';
|
||||||
import { ChartProps } from '@superset-ui/chart';
|
import { ChartProps } from '@superset-ui/chart';
|
||||||
import getTimeFormatterForGranularity from '../utils/getTimeFormatterForGranularity';
|
import getTimeFormatterForGranularity, {
|
||||||
|
TimeGranularity,
|
||||||
|
} from '../utils/getTimeFormatterForGranularity';
|
||||||
|
|
||||||
const TIME_COLUMN = '__timestamp';
|
const TIME_COLUMN = '__timestamp';
|
||||||
const formatPercentChange = getNumberFormatter(NumberFormats.PERCENT_SIGNED_1_POINT);
|
const formatPercentChange = getNumberFormatter(NumberFormats.PERCENT_SIGNED_1_POINT);
|
||||||
|
|
||||||
|
export interface DatasourceMetric {
|
||||||
|
label: string;
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
metric_name?: string;
|
||||||
|
d3format?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// we trust both the x (time) and y (big number) to be numeric
|
// we trust both the x (time) and y (big number) to be numeric
|
||||||
type BigNumberDatum = {
|
export interface BigNumberDatum {
|
||||||
[TIME_COLUMN]: number;
|
|
||||||
[key: string]: number | null;
|
[key: string]: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BigNumberFormData = {
|
||||||
|
colorPicker?: {
|
||||||
|
r: number;
|
||||||
|
g: number;
|
||||||
|
b: number;
|
||||||
|
};
|
||||||
|
metric?:
|
||||||
|
| {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
| string;
|
||||||
|
compareLag?: string | number;
|
||||||
|
yAxisFormat?: string;
|
||||||
|
timeGrainSqla?: TimeGranularity;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function transformProps(chartProps: ChartProps) {
|
export type BignumberChartProps = ChartProps & {
|
||||||
const { width, height, formData, queryData } = chartProps;
|
formData: BigNumberFormData;
|
||||||
|
queryData: ChartProps['queryData'] & {
|
||||||
|
data?: BigNumberDatum[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function transformProps(chartProps: BignumberChartProps) {
|
||||||
|
const { width, height, queryData, formData } = chartProps;
|
||||||
const {
|
const {
|
||||||
colorPicker,
|
colorPicker,
|
||||||
compareLag: compareLagInput,
|
compareLag: compareLag_,
|
||||||
compareSuffix = '',
|
compareSuffix = '',
|
||||||
headerFontSize,
|
headerFontSize,
|
||||||
metric,
|
metric = 'value',
|
||||||
showTrendLine,
|
showTrendLine,
|
||||||
startYAxisAtZero,
|
startYAxisAtZero,
|
||||||
subheader = '',
|
subheader = '',
|
||||||
@ -47,9 +78,9 @@ export default function transformProps(chartProps: ChartProps) {
|
|||||||
timeRangeFixed = false,
|
timeRangeFixed = false,
|
||||||
} = formData;
|
} = formData;
|
||||||
let { yAxisFormat } = formData;
|
let { yAxisFormat } = formData;
|
||||||
const { data, from_dttm: fromDatetime, to_dttm: toDatetime } = queryData;
|
const { data = [], from_dttm: fromDatetime, to_dttm: toDatetime } = queryData;
|
||||||
const metricName = metric?.label ? metric.label : metric;
|
const metricName = typeof metric === 'string' ? metric : metric.label;
|
||||||
const compareLag = Number(compareLagInput) || 0;
|
const compareLag = Number(compareLag_) || 0;
|
||||||
const supportTrendLine = vizType === 'big_number';
|
const supportTrendLine = vizType === 'big_number';
|
||||||
const supportAndShowTrendLine = supportTrendLine && showTrendLine;
|
const supportAndShowTrendLine = supportTrendLine && showTrendLine;
|
||||||
let formattedSubheader = subheader;
|
let formattedSubheader = subheader;
|
||||||
@ -68,7 +99,8 @@ export default function transformProps(chartProps: ChartProps) {
|
|||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
const sortedData = (data as BigNumberDatum[])
|
const sortedData = (data as BigNumberDatum[])
|
||||||
.map(d => ({ x: d[TIME_COLUMN], y: d[metricName] }))
|
.map(d => ({ x: d[TIME_COLUMN], y: d[metricName] }))
|
||||||
.sort((a, b) => b.x - a.x); // sort in time descending order
|
// sort in time descending order
|
||||||
|
.sort((a, b) => (a.x !== null && b.x !== null ? b.x - a.x : 0));
|
||||||
|
|
||||||
bigNumber = sortedData[0].y;
|
bigNumber = sortedData[0].y;
|
||||||
if (bigNumber === null) {
|
if (bigNumber === null) {
|
||||||
@ -103,14 +135,11 @@ export default function transformProps(chartProps: ChartProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!yAxisFormat && chartProps.datasource && chartProps.datasource.metrics) {
|
if (!yAxisFormat && chartProps.datasource && chartProps.datasource.metrics) {
|
||||||
chartProps.datasource.metrics.forEach(
|
chartProps.datasource.metrics.forEach((metricEntry: DatasourceMetric) => {
|
||||||
// eslint-disable-next-line camelcase
|
if (metricEntry.metric_name === metric && metricEntry.d3format) {
|
||||||
(metricEntry: { metric_name?: string; d3format: string }) => {
|
yAxisFormat = metricEntry.d3format;
|
||||||
if (metricEntry.metric_name === metric && metricEntry.d3format) {
|
}
|
||||||
yAxisFormat = metricEntry.d3format;
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatNumber = getNumberFormatter(yAxisFormat);
|
const formatNumber = getNumberFormatter(yAxisFormat);
|
||||||
|
@ -46,25 +46,10 @@ const formats = {
|
|||||||
'P1W/1970-01-04T00:00:00Z': MONDAY_BASED_WEEK, // 'week_ending_sunday'
|
'P1W/1970-01-04T00:00:00Z': MONDAY_BASED_WEEK, // 'week_ending_sunday'
|
||||||
};
|
};
|
||||||
|
|
||||||
type TimeGranularity =
|
export type TimeGranularity = keyof typeof formats;
|
||||||
| 'date'
|
|
||||||
| 'PT1S'
|
|
||||||
| 'PT1M'
|
|
||||||
| 'PT5M'
|
|
||||||
| 'PT10M'
|
|
||||||
| 'PT15M'
|
|
||||||
| 'PT0.5H'
|
|
||||||
| 'PT1H'
|
|
||||||
| 'P1D'
|
|
||||||
| 'P1W'
|
|
||||||
| 'P0.25Y'
|
|
||||||
| 'P1Y'
|
|
||||||
| '1969-12-28T00:00:00Z/P1W'
|
|
||||||
| '1969-12-29T00:00:00Z/P1W'
|
|
||||||
| 'P1W/1970-01-03T00:00:00Z';
|
|
||||||
|
|
||||||
export default function getTimeFormatterForGranularity(granularity: TimeGranularity) {
|
export default function getTimeFormatterForGranularity(granularity: TimeGranularity | undefined) {
|
||||||
return granularity in formats
|
return granularity && granularity in formats
|
||||||
? getTimeFormatter(formats[granularity])
|
? getTimeFormatter(formats[granularity])
|
||||||
: smartDateVerboseFormatter;
|
: smartDateVerboseFormatter;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,12 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import transformProps from '../BigNumber/transformProps';
|
import { DatasourceType } from '@superset-ui/query';
|
||||||
|
import transformProps, {
|
||||||
|
BignumberChartProps,
|
||||||
|
BigNumberDatum,
|
||||||
|
} from '../src/BigNumber/transformProps';
|
||||||
|
import { TimeGranularity } from '../src/utils/getTimeFormatterForGranularity';
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
metric: 'value',
|
metric: 'value',
|
||||||
@ -27,18 +32,27 @@ const formData = {
|
|||||||
a: 1,
|
a: 1,
|
||||||
},
|
},
|
||||||
compareLag: 1,
|
compareLag: 1,
|
||||||
timeGrainSqla: 'P0.25Y',
|
timeGrainSqla: 'P0.25Y' as TimeGranularity,
|
||||||
compareSuffix: 'over last quarter',
|
compareSuffix: 'over last quarter',
|
||||||
vizType: 'big_number',
|
vizType: 'big_number',
|
||||||
yAxisFormat: '.3s',
|
yAxisFormat: '.3s',
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateProps(data: object[], extraFormData = {}, extraQueryData = {}) {
|
function generateProps(
|
||||||
|
data: BigNumberDatum[],
|
||||||
|
extraFormData = {},
|
||||||
|
extraQueryData = {},
|
||||||
|
): BignumberChartProps {
|
||||||
return {
|
return {
|
||||||
width: 200,
|
width: 200,
|
||||||
height: 500,
|
height: 500,
|
||||||
annotationData: {},
|
annotationData: {},
|
||||||
datasource: {
|
datasource: {
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
type: DatasourceType.Table,
|
||||||
|
columns: [],
|
||||||
|
metrics: [],
|
||||||
columnFormats: {},
|
columnFormats: {},
|
||||||
verboseMap: {},
|
verboseMap: {},
|
||||||
},
|
},
|
||||||
@ -94,8 +108,10 @@ describe('BigNumber', () => {
|
|||||||
const propsWithDatasource = {
|
const propsWithDatasource = {
|
||||||
...props,
|
...props,
|
||||||
datasource: {
|
datasource: {
|
||||||
|
...props.datasource,
|
||||||
metrics: [
|
metrics: [
|
||||||
{
|
{
|
||||||
|
label: 'value',
|
||||||
metric_name: 'value',
|
metric_name: 'value',
|
||||||
d3format: '.2f',
|
d3format: '.2f',
|
||||||
},
|
},
|
@ -42,7 +42,7 @@
|
|||||||
"@superset-ui/time-format": "^0.12.0",
|
"@superset-ui/time-format": "^0.12.0",
|
||||||
"@superset-ui/translation": "^0.12.0",
|
"@superset-ui/translation": "^0.12.0",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"react": "^16.8.0",
|
"react": "^16.13.1",
|
||||||
"react-dom": "^16.8.0"
|
"react-dom": "^16.13.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import { t } from '@superset-ui/translation';
|
|||||||
import React, { useEffect, createRef } from 'react';
|
import React, { useEffect, createRef } from 'react';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
import ReactDOMServer from 'react-dom/server';
|
||||||
import { formatNumber, NumberFormats } from '@superset-ui/number-format';
|
import { formatNumber, NumberFormats } from '@superset-ui/number-format';
|
||||||
|
import { DataRecordValue } from '@superset-ui/chart';
|
||||||
import { getTimeFormatter } from '@superset-ui/time-format';
|
import { getTimeFormatter } from '@superset-ui/time-format';
|
||||||
import { filterXSS } from 'xss';
|
import { filterXSS } from 'xss';
|
||||||
|
|
||||||
@ -41,6 +42,10 @@ if (!dt.$) {
|
|||||||
const { PERCENT_3_POINT } = NumberFormats;
|
const { PERCENT_3_POINT } = NumberFormats;
|
||||||
const isProbablyHTML = (text: string) => /<[^>]+>/.test(text);
|
const isProbablyHTML = (text: string) => /<[^>]+>/.test(text);
|
||||||
|
|
||||||
|
function isTimeColumn(key: string) {
|
||||||
|
return key === '__timestamp';
|
||||||
|
}
|
||||||
|
|
||||||
export default function ReactDataTable(props: DataTableProps) {
|
export default function ReactDataTable(props: DataTableProps) {
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@ -54,23 +59,19 @@ export default function ReactDataTable(props: DataTableProps) {
|
|||||||
percentMetrics,
|
percentMetrics,
|
||||||
showCellBars = true,
|
showCellBars = true,
|
||||||
tableTimestampFormat,
|
tableTimestampFormat,
|
||||||
// orderDesc,
|
emitFilter = false,
|
||||||
// TODO: add back the broken dashboard filters feature
|
onChangeFilter = () => {},
|
||||||
// filters = {},
|
filters = {},
|
||||||
// onAddFilter = NOOP,
|
|
||||||
// onRemoveFilter = NOOP,
|
|
||||||
// tableFilter,
|
|
||||||
// timeseriesLimitMetric,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const formatTimestamp = getTimeFormatter(tableTimestampFormat);
|
const formatTimestamp = getTimeFormatter(tableTimestampFormat);
|
||||||
const metrics = (aggMetrics || [])
|
const metrics = (aggMetrics || [])
|
||||||
.concat(percentMetrics || [])
|
.concat(percentMetrics || [])
|
||||||
// actual records must be of numeric types as well
|
// actual records must be of numeric types as well
|
||||||
.filter(m => data[0] && typeof data[0][m] === 'number');
|
.filter(m => data.length > 0 && typeof data[0][m] === 'number');
|
||||||
|
|
||||||
// check whethere a key is a metric
|
// check whethere a key is a metric
|
||||||
const metricsSet = new Set(aggMetrics);
|
const aggMetricsSet = new Set(aggMetrics);
|
||||||
const percentMetricsSet = new Set(percentMetrics);
|
const percentMetricsSet = new Set(percentMetrics);
|
||||||
|
|
||||||
// collect min/max for rendering bars
|
// collect min/max for rendering bars
|
||||||
@ -109,8 +110,8 @@ export default function ReactDataTable(props: DataTableProps) {
|
|||||||
/**
|
/**
|
||||||
* Format text for cell value
|
* Format text for cell value
|
||||||
*/
|
*/
|
||||||
function cellText(key: string, format: string | undefined, val: any) {
|
function cellText(key: string, format: string | undefined, val: DataRecordValue) {
|
||||||
if (key === '__timestamp') {
|
if (isTimeColumn(key)) {
|
||||||
let value = val;
|
let value = val;
|
||||||
if (typeof val === 'string') {
|
if (typeof val === 'string') {
|
||||||
// force UTC time zone if is an ISO timestamp without timezone
|
// force UTC time zone if is an ISO timestamp without timezone
|
||||||
@ -118,7 +119,7 @@ export default function ReactDataTable(props: DataTableProps) {
|
|||||||
value = val.match(/T(\d{2}:){2}\d{2}$/) ? `${val}Z` : val;
|
value = val.match(/T(\d{2}:){2}\d{2}$/) ? `${val}Z` : val;
|
||||||
value = new Date(value);
|
value = new Date(value);
|
||||||
}
|
}
|
||||||
return formatTimestamp(value) as string;
|
return formatTimestamp(value as Date | number | null) as string;
|
||||||
}
|
}
|
||||||
if (typeof val === 'string') {
|
if (typeof val === 'string') {
|
||||||
return filterXSS(val, { stripIgnoreTag: true });
|
return filterXSS(val, { stripIgnoreTag: true });
|
||||||
@ -127,11 +128,14 @@ export default function ReactDataTable(props: DataTableProps) {
|
|||||||
// in case percent metric can specify percent format in the future
|
// in case percent metric can specify percent format in the future
|
||||||
return formatNumber(format || PERCENT_3_POINT, val as number);
|
return formatNumber(format || PERCENT_3_POINT, val as number);
|
||||||
}
|
}
|
||||||
if (metricsSet.has(key)) {
|
if (aggMetricsSet.has(key)) {
|
||||||
// default format '' will return human readable numbers (e.g. 50M, 33k)
|
// default format '' will return human readable numbers (e.g. 50M, 33k)
|
||||||
return formatNumber(format, val as number);
|
return formatNumber(format, val as number);
|
||||||
}
|
}
|
||||||
return String(val);
|
if (val === null) {
|
||||||
|
return 'N/A';
|
||||||
|
}
|
||||||
|
return val.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,6 +166,15 @@ export default function ReactDataTable(props: DataTableProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isFilterColumn(key: string) {
|
||||||
|
// anything that is not a metric column is a filter column
|
||||||
|
return !(aggMetricsSet.has(key) || percentMetricsSet.has(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActiveFilterValue(key: string, val: DataRecordValue) {
|
||||||
|
return filters[key]?.includes(val);
|
||||||
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
aaSorting: [], // initial sorting order, reset to [] to use backend ordering
|
aaSorting: [], // initial sorting order, reset to [] to use backend ordering
|
||||||
autoWidth: false,
|
autoWidth: false,
|
||||||
@ -191,6 +204,19 @@ export default function ReactDataTable(props: DataTableProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const $root = $(rootElem.current as HTMLElement);
|
const $root = $(rootElem.current as HTMLElement);
|
||||||
const dataTable = $root.find('table').DataTable(options);
|
const dataTable = $root.find('table').DataTable(options);
|
||||||
|
const CSS_FILTER_ACTIVE = 'dt-is-active-filter';
|
||||||
|
|
||||||
|
function toggleFilter(key: string, val: DataRecordValue) {
|
||||||
|
const cellSelector = `td[data-key="${key}"][data-sort="${val}"]`;
|
||||||
|
if (isActiveFilterValue(key, val)) {
|
||||||
|
filters[key] = filters[key].filter((x: DataRecordValue) => x !== val);
|
||||||
|
$root.find(cellSelector).removeClass(CSS_FILTER_ACTIVE);
|
||||||
|
} else {
|
||||||
|
filters[key] = [...(filters[key] || []), val];
|
||||||
|
$root.find(cellSelector).addClass(CSS_FILTER_ACTIVE);
|
||||||
|
}
|
||||||
|
onChangeFilter({ ...filters });
|
||||||
|
}
|
||||||
|
|
||||||
// adjust table height
|
// adjust table height
|
||||||
const scrollHeadHeight = $root.find('.dataTables_scrollHead').height() || 0;
|
const scrollHeadHeight = $root.find('.dataTables_scrollHead').height() || 0;
|
||||||
@ -198,8 +224,17 @@ export default function ReactDataTable(props: DataTableProps) {
|
|||||||
const searchBarHeight =
|
const searchBarHeight =
|
||||||
$root.find('.dataTables_length,.dataTables_filter').closest('.row').height() || 0;
|
$root.find('.dataTables_length,.dataTables_filter').closest('.row').height() || 0;
|
||||||
const scrollBodyHeight = viewportHeight - scrollHeadHeight - paginationHeight - searchBarHeight;
|
const scrollBodyHeight = viewportHeight - scrollHeadHeight - paginationHeight - searchBarHeight;
|
||||||
|
|
||||||
$root.find('.dataTables_scrollBody').css('max-height', scrollBodyHeight);
|
$root.find('.dataTables_scrollBody').css('max-height', scrollBodyHeight);
|
||||||
|
|
||||||
|
if (emitFilter) {
|
||||||
|
$root.find('tbody').on('click', 'td.dt-is-filter', function onClickCell(this: HTMLElement) {
|
||||||
|
const { row, column } = dataTable.cell(this).index();
|
||||||
|
const { key } = columns[column];
|
||||||
|
toggleFilter(key, data[row][key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// there may be weird lifecycle issues, so put destroy in try/catch
|
// there may be weird lifecycle issues, so put destroy in try/catch
|
||||||
try {
|
try {
|
||||||
@ -234,21 +269,31 @@ export default function ReactDataTable(props: DataTableProps) {
|
|||||||
>
|
>
|
||||||
{columns.map(({ key, format }) => {
|
{columns.map(({ key, format }) => {
|
||||||
const val = record[key];
|
const val = record[key];
|
||||||
const keyIsMetric = metricsSet.has(key);
|
const keyIsAggMetric = aggMetricsSet.has(key);
|
||||||
const text = cellText(key, format, val);
|
const text = cellText(key, format, val);
|
||||||
const isHtml = !keyIsMetric && isProbablyHTML(text);
|
const isHtml = !keyIsAggMetric && isProbablyHTML(text);
|
||||||
const showCellBar = keyIsMetric && showCellBars;
|
const showCellBar = keyIsAggMetric && showCellBars;
|
||||||
|
let className = '';
|
||||||
|
if (keyIsAggMetric) {
|
||||||
|
className += ' dt-metric';
|
||||||
|
} else if (isFilterColumn(key) && emitFilter) {
|
||||||
|
className += ' dt-is-filter';
|
||||||
|
if (isActiveFilterValue(key, val)) {
|
||||||
|
className += ' dt-is-active-filter';
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<td
|
<td
|
||||||
key={key}
|
key={key}
|
||||||
// only set innerHTML for actual html content, this saves time
|
// only set innerHTML for actual html content, this saves time
|
||||||
dangerouslySetInnerHTML={isHtml ? { __html: text } : undefined}
|
dangerouslySetInnerHTML={isHtml ? { __html: text } : undefined}
|
||||||
|
data-key={key}
|
||||||
data-sort={val}
|
data-sort={val}
|
||||||
className={keyIsMetric ? 'text-right' : ''}
|
className={className}
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: showCellBar ? cellBar(key, val as number) : undefined,
|
backgroundImage: showCellBar ? cellBar(key, val as number) : undefined,
|
||||||
}}
|
}}
|
||||||
title={keyIsMetric || percentMetricsSet.has(key) ? String(val) : ''}
|
title={keyIsAggMetric || percentMetricsSet.has(key) ? String(val) : ''}
|
||||||
>
|
>
|
||||||
{isHtml ? null : text}
|
{isHtml ? null : text}
|
||||||
</td>
|
</td>
|
||||||
|
@ -25,6 +25,16 @@
|
|||||||
.superset-legacy-chart-table .dt-metric {
|
.superset-legacy-chart-table .dt-metric {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.superset-legacy-chart-table td.dt-is-filter {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.superset-legacy-chart-table td.dt-is-filter:hover {
|
||||||
|
background-color: linen;
|
||||||
|
}
|
||||||
|
.superset-legacy-chart-table td.dt-is-active-filter,
|
||||||
|
.superset-legacy-chart-table td.dt-is-active-filter:hover {
|
||||||
|
background-color: lightcyan;
|
||||||
|
}
|
||||||
.superset-legacy-chart-table div.dataTables_wrapper div.dataTables_paginate {
|
.superset-legacy-chart-table div.dataTables_wrapper div.dataTables_paginate {
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,9 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { ChartProps } from '@superset-ui/chart';
|
import { ChartProps, DataRecord, DataRecordFilters } from '@superset-ui/chart';
|
||||||
import { QueryFormDataMetric } from '@superset-ui/query';
|
import { QueryFormDataMetric } from '@superset-ui/query';
|
||||||
|
|
||||||
interface DataRecord {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DataColumnMeta {
|
interface DataColumnMeta {
|
||||||
// `key` is what is called `label` in the input props
|
// `key` is what is called `label` in the input props
|
||||||
key: string;
|
key: string;
|
||||||
@ -31,39 +27,51 @@ interface DataColumnMeta {
|
|||||||
format?: string;
|
format?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TableChartData {
|
||||||
|
records: DataRecord[];
|
||||||
|
columns: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TableChartFormData {
|
||||||
|
alignPn?: boolean;
|
||||||
|
colorPn?: boolean;
|
||||||
|
includeSearch?: boolean;
|
||||||
|
orderDesc?: boolean;
|
||||||
|
pageLength?: string | number;
|
||||||
|
metrics?: QueryFormDataMetric[] | null;
|
||||||
|
percentMetrics?: QueryFormDataMetric[] | null;
|
||||||
|
showCellBars?: boolean;
|
||||||
|
tableTimestampFormat?: string;
|
||||||
|
tableFilter?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DataTableProps {
|
export interface DataTableProps {
|
||||||
// Each object is { field1: value1, field2: value2 }
|
// Each object is { field1: value1, field2: value2 }
|
||||||
data: DataRecord[];
|
|
||||||
height: number;
|
|
||||||
alignPositiveNegative: boolean;
|
alignPositiveNegative: boolean;
|
||||||
colorPositiveNegative: boolean;
|
colorPositiveNegative: boolean;
|
||||||
columns: DataColumnMeta[];
|
columns: DataColumnMeta[];
|
||||||
showCellBars: boolean;
|
data: DataRecord[];
|
||||||
metrics: string[];
|
height: number;
|
||||||
percentMetrics: string[];
|
|
||||||
includeSearch: boolean;
|
includeSearch: boolean;
|
||||||
|
metrics: string[];
|
||||||
orderDesc: boolean;
|
orderDesc: boolean;
|
||||||
pageLength: number;
|
pageLength: number;
|
||||||
|
percentMetrics: string[];
|
||||||
|
showCellBars: boolean;
|
||||||
tableTimestampFormat?: string;
|
tableTimestampFormat?: string;
|
||||||
// TODO: add filters back or clean up
|
|
||||||
// filters: object;
|
|
||||||
// onAddFilter?: (key: string, value: number[]) => void;
|
|
||||||
// onRemoveFilter?: (key: string, value: number[]) => void;
|
|
||||||
// tableFilter: boolean;
|
|
||||||
// timeseriesLimitMetric: string | object;
|
// timeseriesLimitMetric: string | object;
|
||||||
|
// These are dashboard filters, don't be confused with in-chart search filter
|
||||||
|
filters: DataRecordFilters;
|
||||||
|
emitFilter: boolean;
|
||||||
|
onChangeFilter: ChartProps['hooks']['onAddFilter'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableChartFormData {
|
export type TableChartProps = ChartProps & {
|
||||||
alignPn?: boolean;
|
formData: TableChartFormData;
|
||||||
colorPn?: boolean;
|
queryData: ChartProps['queryData'] & {
|
||||||
showCellBars?: boolean;
|
data?: TableChartData;
|
||||||
includeSearch?: boolean;
|
};
|
||||||
orderDesc?: boolean;
|
};
|
||||||
pageLength?: string;
|
|
||||||
metrics?: QueryFormDataMetric[];
|
|
||||||
percentMetrics?: QueryFormDataMetric[];
|
|
||||||
tableTimestampFormat?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consolidate list of metrics to string, identified by its unique identifier
|
* Consolidate list of metrics to string, identified by its unique identifier
|
||||||
@ -76,9 +84,15 @@ const consolidateMetricShape = (metric: QueryFormDataMetric) => {
|
|||||||
return metric.label || 'NOT_LABLED';
|
return metric.label || 'NOT_LABLED';
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function transformProps(chartProps: ChartProps): DataTableProps {
|
export default function transformProps(chartProps: TableChartProps): DataTableProps {
|
||||||
const { height, datasource, formData, queryData } = chartProps;
|
const {
|
||||||
|
height,
|
||||||
|
datasource,
|
||||||
|
formData,
|
||||||
|
queryData,
|
||||||
|
initialValues: filters = {},
|
||||||
|
hooks: { onAddFilter: onChangeFilter = () => {} },
|
||||||
|
} = chartProps;
|
||||||
const {
|
const {
|
||||||
alignPn = true,
|
alignPn = true,
|
||||||
colorPn = true,
|
colorPn = true,
|
||||||
@ -89,19 +103,19 @@ export default function transformProps(chartProps: ChartProps): DataTableProps {
|
|||||||
metrics: metrics_ = [],
|
metrics: metrics_ = [],
|
||||||
percentMetrics: percentMetrics_ = [],
|
percentMetrics: percentMetrics_ = [],
|
||||||
tableTimestampFormat,
|
tableTimestampFormat,
|
||||||
} = formData as TableChartFormData;
|
tableFilter,
|
||||||
|
} = formData;
|
||||||
const { columnFormats, verboseMap } = datasource;
|
const { columnFormats, verboseMap } = datasource;
|
||||||
const {
|
const { records, columns: columns_ } = queryData.data || { records: [], columns: [] };
|
||||||
records,
|
|
||||||
columns: columns_,
|
|
||||||
}: { records: DataRecord[]; columns: string[] } = queryData.data;
|
|
||||||
const metrics = (metrics_ ?? []).map(consolidateMetricShape);
|
const metrics = (metrics_ ?? []).map(consolidateMetricShape);
|
||||||
// percent metrics always starts with a '%' sign.
|
// percent metrics always starts with a '%' sign.
|
||||||
const percentMetrics = (percentMetrics_ ?? [])
|
const percentMetrics = (percentMetrics_ ?? [])
|
||||||
.map(consolidateMetricShape)
|
.map(consolidateMetricShape)
|
||||||
.map((x: string) => `%${x}`);
|
.map((x: string) => `%${x}`);
|
||||||
|
|
||||||
const columns = columns_.map((key: string) => {
|
const columns = columns_.map((key: string) => {
|
||||||
let label = verboseMap[key] || key;
|
let label = verboseMap?.[key] || key;
|
||||||
// make sure there is a " " after "%" for percent metrics
|
// make sure there is a " " after "%" for percent metrics
|
||||||
if (label[0] === '%' && label[1] !== ' ') {
|
if (label[0] === '%' && label[1] !== ' ') {
|
||||||
label = `% ${label.slice(1)}`;
|
label = `% ${label.slice(1)}`;
|
||||||
@ -124,7 +138,10 @@ export default function transformProps(chartProps: ChartProps): DataTableProps {
|
|||||||
showCellBars,
|
showCellBars,
|
||||||
includeSearch,
|
includeSearch,
|
||||||
orderDesc,
|
orderDesc,
|
||||||
pageLength: pageLength ? parseInt(pageLength, 10) : 0,
|
pageLength: typeof pageLength === 'string' ? parseInt(pageLength, 10) || 0 : 0,
|
||||||
tableTimestampFormat,
|
tableTimestampFormat,
|
||||||
|
filters,
|
||||||
|
emitFilter: tableFilter === true,
|
||||||
|
onChangeFilter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { ChartProps } from '@superset-ui/chart';
|
import { ChartProps } from '@superset-ui/chart';
|
||||||
|
import { DatasourceType } from '@superset-ui/query';
|
||||||
|
import { TableChartProps } from '../src/transformProps';
|
||||||
|
|
||||||
const basicFormData = {
|
const basicFormData = {
|
||||||
alignPn: false,
|
alignPn: false,
|
||||||
@ -37,6 +39,11 @@ const basicChartProps = {
|
|||||||
height: 500,
|
height: 500,
|
||||||
annotationData: {},
|
annotationData: {},
|
||||||
datasource: {
|
datasource: {
|
||||||
|
id: 0,
|
||||||
|
name: '',
|
||||||
|
type: DatasourceType.Table,
|
||||||
|
columns: [],
|
||||||
|
metrics: [],
|
||||||
columnFormats: {},
|
columnFormats: {},
|
||||||
verboseMap: {},
|
verboseMap: {},
|
||||||
},
|
},
|
||||||
@ -56,7 +63,7 @@ const basicChartProps = {
|
|||||||
/**
|
/**
|
||||||
* Basic data input
|
* Basic data input
|
||||||
*/
|
*/
|
||||||
const basic: ChartProps = {
|
const basic: TableChartProps = {
|
||||||
...basicChartProps,
|
...basicChartProps,
|
||||||
queryData: {
|
queryData: {
|
||||||
data: {
|
data: {
|
||||||
@ -87,7 +94,7 @@ const basic: ChartProps = {
|
|||||||
const advanced: ChartProps = {
|
const advanced: ChartProps = {
|
||||||
...basic,
|
...basic,
|
||||||
datasource: {
|
datasource: {
|
||||||
columnFormats: {},
|
...basic.datasource,
|
||||||
verboseMap: {
|
verboseMap: {
|
||||||
sum__num: 'Sum of Num',
|
sum__num: 'Sum of Num',
|
||||||
},
|
},
|
||||||
@ -100,7 +107,7 @@ const advanced: ChartProps = {
|
|||||||
queryData: {
|
queryData: {
|
||||||
data: {
|
data: {
|
||||||
columns: ['name', 'sum__num', '%pct_nice'],
|
columns: ['name', 'sum__num', '%pct_nice'],
|
||||||
records: [...basic.queryData.data.records],
|
records: [...(basic.queryData.data?.records || [])],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -19,10 +19,9 @@ const run = cmd => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (glob) {
|
if (glob) {
|
||||||
run(`nimbus prettier plugins/${glob}/{src,test}/**/*.{js,jsx,ts,tsx,css}"`);
|
|
||||||
// lint is slow, so not turning it on by default
|
// lint is slow, so not turning it on by default
|
||||||
if (extraArgs.includes('--lint')) {
|
if (extraArgs.includes('--lint')) {
|
||||||
run(`nimbus eslint plugins/${glob}/{src,test}`);
|
run(`nimbus eslint {packages,plugins}/${glob}/{src,test}`);
|
||||||
}
|
}
|
||||||
run(`nimbus babel --clean --workspaces="@superset-ui/${glob}"`);
|
run(`nimbus babel --clean --workspaces="@superset-ui/${glob}"`);
|
||||||
run(`nimbus babel --clean --workspaces="@superset-ui/${glob}" --esm`);
|
run(`nimbus babel --clean --workspaces="@superset-ui/${glob}" --esm`);
|
||||||
|
@ -3683,7 +3683,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react-test-renderer@^16.9.0":
|
"@types/react-test-renderer@^16.9.2":
|
||||||
version "16.9.2"
|
version "16.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5"
|
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5"
|
||||||
integrity sha512-4eJr1JFLIAlWhzDkBCkhrOIWOvOxcCAfQh+jiKg7l/nNZcCIL2MHl2dZhogIFKyHzedVWHaVP1Yydq/Ruu4agw==
|
integrity sha512-4eJr1JFLIAlWhzDkBCkhrOIWOvOxcCAfQh+jiKg7l/nNZcCIL2MHl2dZhogIFKyHzedVWHaVP1Yydq/Ruu4agw==
|
||||||
@ -3697,7 +3697,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^16.3.0", "@types/react@^16.7.17":
|
"@types/react@*", "@types/react@^16.3.0", "@types/react@^16.9.34":
|
||||||
version "16.9.34"
|
version "16.9.34"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349"
|
||||||
integrity sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==
|
integrity sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==
|
||||||
@ -14183,7 +14183,7 @@ react-docgen@^5.0.0:
|
|||||||
node-dir "^0.1.10"
|
node-dir "^0.1.10"
|
||||||
strip-indent "^3.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
react-dom@^16.8.3, react-dom@^16.9.0:
|
react-dom@^16.13.1, react-dom@^16.8.3:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f"
|
||||||
integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==
|
integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==
|
||||||
@ -14366,7 +14366,7 @@ react-syntax-highlighter@^11.0.2:
|
|||||||
prismjs "^1.8.4"
|
prismjs "^1.8.4"
|
||||||
refractor "^2.4.1"
|
refractor "^2.4.1"
|
||||||
|
|
||||||
react-test-renderer@^16.0.0-0, react-test-renderer@^16.9.0:
|
react-test-renderer@^16.0.0-0, react-test-renderer@^16.13.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"
|
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"
|
||||||
integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==
|
integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==
|
||||||
@ -14399,7 +14399,7 @@ react-virtualized-auto-sizer@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"
|
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"
|
||||||
integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==
|
integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==
|
||||||
|
|
||||||
react@^16.6.0, react@^16.8.3, react@^16.9.0:
|
react@^16.13.1, react@^16.8.3:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
|
||||||
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
|
integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==
|
||||||
|
Loading…
Reference in New Issue
Block a user