chore(explore): Migrate BigNumber to v1 api [ID-28][ID-55] (#17587)
* chore(explore): Migrate BigNumber to v1 api * Move to echarts * Use Echarts trendline * Fix imports * Fix parsing dates as strings * Add from_dttm and to_dttm to v1 chart response * Fix post processing * Fix timeRangeFixed * Fix tests * Remove from and to dttm from cache * Cleanup date formatting * Fix storybook * Fix missing types * Fix timestamp with timezone * Add types to demo's tsconfig * bug fix * fix import * Fix cypress tests * add sort * add resample to handle missing values properly * Sync ChartDataResponseResult schema with ts interface * Lint fix * Add migration * Fix migration * Remove pass * Re-raise the exception in migration * Typo fix * Update revision Co-authored-by: Ville Brofeldt <ville.v.brofeldt@gmail.com>
|
@ -16,6 +16,8 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { interceptChart } from 'cypress/utils';
|
||||
|
||||
describe('Visualization > Big Number with Trendline', () => {
|
||||
const BIG_NUMBER_FORM_DATA = {
|
||||
datasource: '2__table',
|
||||
|
@ -42,21 +44,21 @@ describe('Visualization > Big Number with Trendline', () => {
|
|||
function verify(formData) {
|
||||
cy.visitChartByParams(JSON.stringify(formData));
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@chartData',
|
||||
chartSelector: '.superset-legacy-chart-big-number',
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.intercept('POST', '/superset/explore_json/**').as('getJson');
|
||||
interceptChart({ legacy: false }).as('chartData');
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
verify(BIG_NUMBER_FORM_DATA);
|
||||
cy.get('.chart-container .header-line');
|
||||
cy.get('.chart-container .subheader-line');
|
||||
cy.get('.chart-container svg path.vx-linepath');
|
||||
cy.get('.chart-container canvas');
|
||||
});
|
||||
|
||||
it('should work without subheader', () => {
|
||||
|
@ -66,7 +68,7 @@ describe('Visualization > Big Number with Trendline', () => {
|
|||
});
|
||||
cy.get('.chart-container .header-line');
|
||||
cy.get('.chart-container .subheader-line').should('not.exist');
|
||||
cy.get('.chart-container svg path.vx-linepath');
|
||||
cy.get('.chart-container canvas');
|
||||
});
|
||||
|
||||
it('should not render trendline when hidden', () => {
|
||||
|
@ -76,6 +78,6 @@ describe('Visualization > Big Number with Trendline', () => {
|
|||
});
|
||||
cy.get('[data-test="chart-container"] .header-line');
|
||||
cy.get('[data-test="chart-container"] .subheader-line');
|
||||
cy.get('[data-test="chart-container"] svg').should('not.exist');
|
||||
cy.get('[data-test="chart-container"] canvas').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { interceptChart } from 'cypress/utils';
|
||||
import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper';
|
||||
|
||||
describe('Visualization > Big Number Total', () => {
|
||||
|
@ -26,15 +27,15 @@ describe('Visualization > Big Number Total', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.intercept('POST', '/superset/explore_json/**').as('getJson');
|
||||
interceptChart({ legacy: false }).as('chartData');
|
||||
});
|
||||
|
||||
it('Test big number chart with adhoc metric', () => {
|
||||
const formData = { ...BIG_NUMBER_DEFAULTS, metric: NUM_METRIC };
|
||||
|
||||
cy.visitChartByParams(JSON.stringify(formData));
|
||||
cy.visitChartByParams(formData);
|
||||
cy.verifySliceSuccess({
|
||||
waitAlias: '@getJson',
|
||||
waitAlias: '@chartData',
|
||||
querySubstring: NUM_METRIC.label,
|
||||
});
|
||||
});
|
||||
|
@ -58,8 +59,8 @@ describe('Visualization > Big Number Total', () => {
|
|||
adhoc_filters: filters,
|
||||
};
|
||||
|
||||
cy.visitChartByParams(JSON.stringify(formData));
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
||||
cy.visitChartByParams(formData);
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
});
|
||||
|
||||
it('Test big number chart ignores groupby', () => {
|
||||
|
@ -69,11 +70,11 @@ describe('Visualization > Big Number Total', () => {
|
|||
groupby: ['state'],
|
||||
};
|
||||
|
||||
cy.visitChartByParams(JSON.stringify(formData));
|
||||
cy.wait(['@getJson']).then(async ({ response }) => {
|
||||
cy.visitChartByParams(formData);
|
||||
cy.wait(['@chartData']).then(async ({ response }) => {
|
||||
cy.verifySliceContainer();
|
||||
const responseBody = response?.body;
|
||||
expect(responseBody.query).not.contains(formData.groupby[0]);
|
||||
expect(responseBody.result[0].query).not.contains(formData.groupby[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,6 +40,8 @@ const V1_PLUGINS = [
|
|||
'word_cloud',
|
||||
'pie',
|
||||
'table',
|
||||
'big_number',
|
||||
'big_number_total',
|
||||
];
|
||||
export const DASHBOARD_CHART_ALIAS_PREFIX = 'getChartData_';
|
||||
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-treemap": "^0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "^0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-big-number": "^0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.13",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.25",
|
||||
"@superset-ui/plugin-chart-echarts": "^0.18.25",
|
||||
|
@ -198,6 +197,7 @@
|
|||
"@types/redux-localstorage": "^1.0.8",
|
||||
"@types/redux-mock-store": "^1.0.2",
|
||||
"@types/rison": "0.0.6",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"@types/sinon": "^9.0.5",
|
||||
"@types/yargs": "12 - 15",
|
||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||
|
@ -21125,10 +21125,6 @@
|
|||
"resolved": "plugins/legacy-plugin-chart-world-map",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-preset-chart-big-number": {
|
||||
"resolved": "plugins/legacy-preset-chart-big-number",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-preset-chart-deckgl": {
|
||||
"resolved": "plugins/legacy-preset-chart-deckgl",
|
||||
"link": true
|
||||
|
@ -22433,7 +22429,8 @@
|
|||
"node_modules/@types/shortid": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
|
||||
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps="
|
||||
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/sinon": {
|
||||
"version": "9.0.5",
|
||||
|
@ -60726,7 +60723,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-time-table": "0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-treemap": "0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-big-number": "0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.13",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
||||
"@superset-ui/plugin-chart-echarts": "0.18.25",
|
||||
|
@ -61502,6 +61498,7 @@
|
|||
"plugins/legacy-preset-chart-big-number": {
|
||||
"name": "@superset-ui/legacy-preset-chart-big-number",
|
||||
"version": "0.18.25",
|
||||
"extraneous": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@data-ui/xy-chart": "^0.0.84",
|
||||
|
@ -77854,7 +77851,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-time-table": "0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-treemap": "0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-big-number": "0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.13",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
||||
"@superset-ui/plugin-chart-echarts": "0.18.25",
|
||||
|
@ -78481,18 +78477,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@superset-ui/legacy-preset-chart-big-number": {
|
||||
"version": "file:plugins/legacy-preset-chart-big-number",
|
||||
"requires": {
|
||||
"@data-ui/xy-chart": "^0.0.84",
|
||||
"@superset-ui/chart-controls": "0.18.25",
|
||||
"@superset-ui/core": "0.18.25",
|
||||
"@types/d3-color": "^1.2.2",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"d3-color": "^1.2.3",
|
||||
"shortid": "^2.2.14"
|
||||
}
|
||||
},
|
||||
"@superset-ui/legacy-preset-chart-deckgl": {
|
||||
"version": "file:plugins/legacy-preset-chart-deckgl",
|
||||
"requires": {
|
||||
|
@ -79940,7 +79924,8 @@
|
|||
"@types/shortid": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
|
||||
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps="
|
||||
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=",
|
||||
"dev": true
|
||||
},
|
||||
"@types/sinon": {
|
||||
"version": "9.0.5",
|
||||
|
|
|
@ -101,7 +101,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-treemap": "^0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "^0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-big-number": "^0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.13",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.25",
|
||||
"@superset-ui/plugin-chart-echarts": "^0.18.25",
|
||||
|
@ -258,6 +257,7 @@
|
|||
"@types/redux-localstorage": "^1.0.8",
|
||||
"@types/redux-mock-store": "^1.0.2",
|
||||
"@types/rison": "0.0.6",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"@types/sinon": "^9.0.5",
|
||||
"@types/yargs": "12 - 15",
|
||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||
|
|
|
@ -50,7 +50,7 @@ export interface ChartDataResponseResult {
|
|||
annotation_data: AnnotationData[] | null;
|
||||
cache_key: string | null;
|
||||
cache_timeout: number | null;
|
||||
cache_dttm: string | null;
|
||||
cached_dttm: string | null;
|
||||
/**
|
||||
* Array of data records as dictionary
|
||||
*/
|
||||
|
@ -76,6 +76,8 @@ export interface ChartDataResponseResult {
|
|||
| 'scheduled'
|
||||
| 'success'
|
||||
| 'timed_out';
|
||||
from_dttm: number | null;
|
||||
to_dttm: number | null;
|
||||
}
|
||||
|
||||
export interface TimeseriesChartDataResponseResult
|
||||
|
|
|
@ -61,7 +61,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-time-table": "0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-treemap": "0.18.25",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-big-number": "0.18.25",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "^0.4.13",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
||||
"@superset-ui/plugin-chart-echarts": "0.18.25",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { SuperChart } from '@superset-ui/core';
|
||||
import { BigNumberChartPlugin } from '@superset-ui/legacy-preset-chart-big-number';
|
||||
import { BigNumberChartPlugin } from '@superset-ui/plugin-chart-echarts';
|
||||
import testData from './data';
|
||||
|
||||
new BigNumberChartPlugin().configure({ key: 'big-number' }).register();
|
||||
|
@ -56,7 +56,7 @@ function withNulls(origData: object[], nullPosition = 3) {
|
|||
}
|
||||
|
||||
export default {
|
||||
title: 'Legacy Chart Plugins/legacy-preset-big-number/BigNumber',
|
||||
title: 'Legacy Chart Plugins/legacy-preset-big-number/BigNumberWithTrendline',
|
||||
};
|
||||
|
||||
export const basicWithTrendline = () => (
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { SuperChart } from '@superset-ui/core';
|
||||
import { BigNumberTotalChartPlugin } from '@superset-ui/legacy-preset-chart-big-number';
|
||||
import { BigNumberTotalChartPlugin } from '@superset-ui/plugin-chart-echarts';
|
||||
import data from './data';
|
||||
|
||||
new BigNumberTotalChartPlugin()
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
ChartDataProvider,
|
||||
SupersetClient,
|
||||
} from '@superset-ui/core';
|
||||
import { BigNumberChartPlugin as LegacyBigNumberPlugin } from '@superset-ui/legacy-preset-chart-big-number';
|
||||
import { BigNumberChartPlugin } from '@superset-ui/plugin-chart-echarts';
|
||||
import LegacySankeyPlugin from '@superset-ui/legacy-plugin-chart-sankey';
|
||||
import LegacySunburstPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
||||
import { WordCloudChartPlugin } from '@superset-ui/plugin-chart-word-cloud';
|
||||
|
@ -46,7 +46,7 @@ const SUNBURST = sunburstFormData.viz_type;
|
|||
const WORD_CLOUD_LEGACY = wordCloudFormData.viz_type;
|
||||
const WORD_CLOUD = 'new_word_cloud';
|
||||
|
||||
new LegacyBigNumberPlugin().configure({ key: BIG_NUMBER }).register();
|
||||
new BigNumberChartPlugin().configure({ key: BIG_NUMBER }).register();
|
||||
// eslint-disable-next-line
|
||||
new LegacySankeyPlugin().configure({ key: SANKEY }).register();
|
||||
// eslint-disable-next-line
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
"storybook",
|
||||
"../**/src",
|
||||
"../../plugins/**/src",
|
||||
"../../plugins/**/types",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
|
||||
## @superset-ui/legacy-preset-chart-big-number
|
||||
|
||||
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-big-number.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-preset-chart-big-number)
|
||||
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-legacy-preset-chart-big-number&style=flat-square)](https://david-dm.org/apache-superset/superset-ui-plugins?path=plugins/superset-ui-legacy-preset-chart-big-number)
|
||||
|
||||
This plugin provides Big Number for Superset.
|
||||
|
||||
### Usage
|
||||
|
||||
Import the preset and register. This will register the `BigNumber` and `BigNumberTotal` charts with
|
||||
key `big-number` and `big-number-total`, respectively.
|
||||
|
||||
```js
|
||||
import { BigNumberChartPreset } from '@superset-ui/legacy-preset-chart-big-number';
|
||||
|
||||
new BigNumberChartPreset().register();
|
||||
```
|
||||
|
||||
or register charts one by one. Configure `key`, which can be any `string`, and register the plugin.
|
||||
This `key` will be used to lookup this chart throughout the app.
|
||||
|
||||
```js
|
||||
import {
|
||||
BigNumberChartPlugin,
|
||||
BigNumberTotalChartPlugin,
|
||||
} from '@superset-ui/legacy-preset-chart-big-number';
|
||||
|
||||
new BigNumberChartPlugin().configure({ key: 'big-number' }).register();
|
||||
new BigNumberTotalChartPlugin()
|
||||
.configure({ key: 'big-number-total' })
|
||||
.register();
|
||||
```
|
||||
|
||||
Then use it via `SuperChart`. See
|
||||
[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-big-number)
|
||||
for more details.
|
||||
|
||||
```js
|
||||
<SuperChart
|
||||
chartType="big-number"
|
||||
width={600}
|
||||
height={600}
|
||||
formData={...}
|
||||
queriesData={[{
|
||||
data: {...},
|
||||
}]}
|
||||
/>
|
||||
```
|
|
@ -1,42 +0,0 @@
|
|||
{
|
||||
"name": "@superset-ui/legacy-preset-chart-big-number",
|
||||
"version": "0.18.25",
|
||||
"description": "Superset Legacy Chart - Big Number",
|
||||
"sideEffects": [
|
||||
"*.css"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"files": [
|
||||
"esm",
|
||||
"lib"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/apache-superset/superset-ui.git"
|
||||
},
|
||||
"keywords": [
|
||||
"superset"
|
||||
],
|
||||
"author": "Superset",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache-superset/superset-ui/issues"
|
||||
},
|
||||
"homepage": "https://github.com/apache-superset/superset-ui#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@data-ui/xy-chart": "^0.0.84",
|
||||
"@superset-ui/chart-controls": "0.18.25",
|
||||
"@superset-ui/core": "0.18.25",
|
||||
"@types/d3-color": "^1.2.2",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"d3-color": "^1.2.3",
|
||||
"shortid": "^2.2.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15 || ^16"
|
||||
}
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as color from 'd3-color';
|
||||
import {
|
||||
extractTimegrain,
|
||||
getNumberFormatter,
|
||||
getTimeFormatter,
|
||||
getTimeFormatterForGranularity,
|
||||
NumberFormats,
|
||||
ChartProps,
|
||||
LegacyQueryData,
|
||||
QueryFormData,
|
||||
smartDateFormatter,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
const TIME_COLUMN = '__timestamp';
|
||||
const formatPercentChange = getNumberFormatter(
|
||||
NumberFormats.PERCENT_SIGNED_1_POINT,
|
||||
);
|
||||
|
||||
// we trust both the x (time) and y (big number) to be numeric
|
||||
export interface BigNumberDatum {
|
||||
[key: string]: number | null;
|
||||
}
|
||||
|
||||
export type BigNumberFormData = QueryFormData & {
|
||||
colorPicker?: {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
};
|
||||
metric?:
|
||||
| {
|
||||
label: string;
|
||||
}
|
||||
| string;
|
||||
compareLag?: string | number;
|
||||
yAxisFormat?: string;
|
||||
};
|
||||
|
||||
export type BigNumberChartProps = ChartProps & {
|
||||
formData: BigNumberFormData;
|
||||
queriesData: (LegacyQueryData & {
|
||||
data?: BigNumberDatum[];
|
||||
})[];
|
||||
};
|
||||
|
||||
export default function transformProps(chartProps: BigNumberChartProps) {
|
||||
const { width, height, queriesData, formData, rawFormData } = chartProps;
|
||||
const {
|
||||
colorPicker,
|
||||
compareLag: compareLag_,
|
||||
compareSuffix = '',
|
||||
timeFormat,
|
||||
headerFontSize,
|
||||
metric = 'value',
|
||||
showTimestamp,
|
||||
showTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader = '',
|
||||
subheaderFontSize,
|
||||
vizType,
|
||||
timeRangeFixed = false,
|
||||
} = formData;
|
||||
const granularity = extractTimegrain(rawFormData as QueryFormData);
|
||||
let { yAxisFormat } = formData;
|
||||
const { headerFormatSelector, headerTimestampFormat } = formData;
|
||||
const {
|
||||
data = [],
|
||||
from_dttm: fromDatetime,
|
||||
to_dttm: toDatetime,
|
||||
} = queriesData[0];
|
||||
const metricName = typeof metric === 'string' ? metric : metric.label;
|
||||
const compareLag = Number(compareLag_) || 0;
|
||||
const supportTrendLine = vizType === 'big_number';
|
||||
const supportAndShowTrendLine = supportTrendLine && showTrendLine;
|
||||
let formattedSubheader = subheader;
|
||||
|
||||
let mainColor;
|
||||
if (colorPicker) {
|
||||
const { r, g, b } = colorPicker;
|
||||
mainColor = color.rgb(r, g, b).hex();
|
||||
}
|
||||
|
||||
let trendLineData;
|
||||
let percentChange = 0;
|
||||
let bigNumber = data.length === 0 ? null : data[0][metricName];
|
||||
let timestamp = data.length === 0 ? null : data[0][TIME_COLUMN];
|
||||
let bigNumberFallback;
|
||||
|
||||
if (data.length > 0) {
|
||||
const sortedData = (data as BigNumberDatum[])
|
||||
.map(d => ({ x: d[TIME_COLUMN], y: d[metricName] }))
|
||||
// sort in time descending order
|
||||
.sort((a, b) => (a.x !== null && b.x !== null ? b.x - a.x : 0));
|
||||
|
||||
bigNumber = sortedData[0].y;
|
||||
timestamp = sortedData[0].x;
|
||||
|
||||
if (bigNumber === null) {
|
||||
bigNumberFallback = sortedData.find(d => d.y !== null);
|
||||
bigNumber = bigNumberFallback ? bigNumberFallback.y : null;
|
||||
timestamp = bigNumberFallback ? bigNumberFallback.x : null;
|
||||
}
|
||||
|
||||
if (compareLag > 0) {
|
||||
const compareIndex = compareLag;
|
||||
if (compareIndex < sortedData.length) {
|
||||
const compareValue = sortedData[compareIndex].y;
|
||||
// compare values must both be non-nulls
|
||||
if (bigNumber !== null && compareValue !== null && compareValue !== 0) {
|
||||
percentChange = (bigNumber - compareValue) / Math.abs(compareValue);
|
||||
formattedSubheader = `${formatPercentChange(
|
||||
percentChange,
|
||||
)} ${compareSuffix}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (supportTrendLine) {
|
||||
// must reverse to ascending order otherwise it confuses tooltip triggers
|
||||
sortedData.reverse();
|
||||
trendLineData = supportAndShowTrendLine ? sortedData : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let className = '';
|
||||
if (percentChange > 0) {
|
||||
className = 'positive';
|
||||
} else if (percentChange < 0) {
|
||||
className = 'negative';
|
||||
}
|
||||
|
||||
if (!yAxisFormat && chartProps.datasource && chartProps.datasource.metrics) {
|
||||
chartProps.datasource.metrics.forEach(metricEntry => {
|
||||
if (metricEntry.metric_name === metric && metricEntry.d3format) {
|
||||
yAxisFormat = metricEntry.d3format;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const headerFormatter = headerFormatSelector
|
||||
? getTimeFormatter(headerTimestampFormat)
|
||||
: getNumberFormatter(yAxisFormat);
|
||||
const formatTime =
|
||||
timeFormat === smartDateFormatter.id
|
||||
? getTimeFormatterForGranularity(granularity)
|
||||
: getTimeFormatter(timeFormat);
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
bigNumber,
|
||||
bigNumberFallback,
|
||||
className,
|
||||
headerFormatter,
|
||||
formatTime,
|
||||
headerFontSize,
|
||||
subheaderFontSize,
|
||||
mainColor,
|
||||
showTimestamp,
|
||||
showTrendLine: supportAndShowTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader: formattedSubheader,
|
||||
timestamp,
|
||||
trendLineData,
|
||||
fromDatetime,
|
||||
toDatetime,
|
||||
timeRangeFixed,
|
||||
};
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": false,
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*",
|
||||
"../types/**/*",
|
||||
"../../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": [
|
||||
"lib",
|
||||
"test"
|
||||
],
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/**/*",
|
||||
"../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/superset-ui-chart-controls"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/superset-ui-core"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
declare module '@data-ui/xy-chart';
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
import { buildQueryContext, QueryFormData } from '@superset-ui/core';
|
||||
|
||||
export default function buildQuery(formData: QueryFormData) {
|
||||
return buildQueryContext(formData, baseQueryObject => [baseQueryObject]);
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { t } from '@superset-ui/core';
|
||||
import { smartDateFormatter, t } from '@superset-ui/core';
|
||||
import {
|
||||
ControlPanelConfig,
|
||||
D3_FORMAT_DOCS,
|
||||
|
@ -27,7 +27,7 @@ import { headerFontSize, subheaderFontSize } from '../sharedControls';
|
|||
|
||||
export default {
|
||||
controlPanelSections: [
|
||||
sections.legacyRegularTime,
|
||||
sections.legacyTimeseriesTime,
|
||||
{
|
||||
label: t('Query'),
|
||||
expanded: true,
|
||||
|
@ -51,44 +51,45 @@ export default {
|
|||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
[headerFontSize],
|
||||
[subheaderFontSize],
|
||||
['y_axis_format'],
|
||||
[
|
||||
{
|
||||
name: 'header_format_selector',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Timestamp Format'),
|
||||
renderTrigger: true,
|
||||
default: false,
|
||||
description: t('Whether to format the timestamp'),
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'header_timestamp_format',
|
||||
name: 'time_format',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
freeForm: true,
|
||||
label: t('Date format'),
|
||||
renderTrigger: true,
|
||||
choices: D3_TIME_FORMAT_OPTIONS,
|
||||
default: '%d-%m-%Y %H:%M:%S',
|
||||
description: D3_FORMAT_DOCS,
|
||||
visibility(props) {
|
||||
const { header_format_selector } = props.form_data;
|
||||
return !!header_format_selector;
|
||||
},
|
||||
default: smartDateFormatter.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'force_timestamp_formatting',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Force date format'),
|
||||
renderTrigger: true,
|
||||
default: false,
|
||||
description: t(
|
||||
'Use date formatting even when metric value is not a timestamp',
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [[headerFontSize], [subheaderFontSize]],
|
||||
},
|
||||
],
|
||||
controlOverrides: {
|
||||
y_axis_format: {
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
@ -18,13 +18,12 @@
|
|||
*/
|
||||
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||
import controlPanel from './controlPanel';
|
||||
import transformProps, {
|
||||
BigNumberChartProps,
|
||||
BigNumberFormData,
|
||||
} from '../BigNumber/transformProps';
|
||||
import transformProps from './transformProps';
|
||||
import buildQuery from './buildQuery';
|
||||
import example1 from './images/BigNumber.jpg';
|
||||
import example2 from './images/BigNumber2.jpg';
|
||||
import thumbnail from './images/thumbnail.png';
|
||||
import { BigNumberTotalChartProps, BigNumberTotalFormData } from '../types';
|
||||
|
||||
const metadata = new ChartMetadata({
|
||||
category: t('KPI'),
|
||||
|
@ -47,17 +46,17 @@ const metadata = new ChartMetadata({
|
|||
t('Description'),
|
||||
],
|
||||
thumbnail,
|
||||
useLegacyApi: true,
|
||||
});
|
||||
|
||||
export default class BigNumberTotalChartPlugin extends ChartPlugin<
|
||||
BigNumberFormData,
|
||||
BigNumberChartProps
|
||||
BigNumberTotalFormData,
|
||||
BigNumberTotalChartProps
|
||||
> {
|
||||
constructor() {
|
||||
super({
|
||||
loadChart: () => import('../BigNumber/BigNumber'),
|
||||
loadChart: () => import('../BigNumberViz'),
|
||||
metadata,
|
||||
buildQuery,
|
||||
transformProps,
|
||||
controlPanel,
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
getNumberFormatter,
|
||||
GenericDataType,
|
||||
getMetricLabel,
|
||||
extractTimegrain,
|
||||
QueryFormData,
|
||||
} from '@superset-ui/core';
|
||||
import { BigNumberTotalChartProps } from '../types';
|
||||
import { getDateFormatter, parseMetricValue } from '../utils';
|
||||
|
||||
export default function transformProps(chartProps: BigNumberTotalChartProps) {
|
||||
const { width, height, queriesData, formData, rawFormData } = chartProps;
|
||||
const {
|
||||
headerFontSize,
|
||||
metric = 'value',
|
||||
subheader = '',
|
||||
subheaderFontSize,
|
||||
forceTimestampFormatting,
|
||||
timeFormat,
|
||||
yAxisFormat,
|
||||
} = formData;
|
||||
const { data = [], coltypes = [] } = queriesData[0];
|
||||
const granularity = extractTimegrain(rawFormData as QueryFormData);
|
||||
const metricName = getMetricLabel(metric);
|
||||
const formattedSubheader = subheader;
|
||||
const bigNumber =
|
||||
data.length === 0 ? null : parseMetricValue(data[0][metricName]);
|
||||
|
||||
let metricEntry;
|
||||
if (chartProps.datasource && chartProps.datasource.metrics) {
|
||||
metricEntry = chartProps.datasource.metrics.find(
|
||||
metricItem => metricItem.metric_name === metric,
|
||||
);
|
||||
}
|
||||
|
||||
const formatTime = getDateFormatter(
|
||||
timeFormat,
|
||||
granularity,
|
||||
metricEntry?.d3format,
|
||||
);
|
||||
|
||||
const headerFormatter =
|
||||
coltypes[0] === GenericDataType.TEMPORAL ||
|
||||
coltypes[0] === GenericDataType.STRING ||
|
||||
forceTimestampFormatting
|
||||
? formatTime
|
||||
: getNumberFormatter(yAxisFormat ?? metricEntry?.d3format ?? undefined);
|
||||
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
bigNumber,
|
||||
headerFormatter,
|
||||
headerFontSize,
|
||||
subheaderFontSize,
|
||||
subheader: formattedSubheader,
|
||||
};
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import shortid from 'shortid';
|
||||
import {
|
||||
t,
|
||||
getNumberFormatter,
|
||||
|
@ -28,22 +27,12 @@ import {
|
|||
BRAND_COLOR,
|
||||
styled,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
XYChart,
|
||||
AreaSeries,
|
||||
CrossHair,
|
||||
LinearGradient,
|
||||
} from '@data-ui/xy-chart';
|
||||
import { EChartsCoreOption } from 'echarts';
|
||||
import Echart from '../components/Echart';
|
||||
import { TimeSeriesDatum } from './types';
|
||||
|
||||
const defaultNumberFormatter = getNumberFormatter();
|
||||
|
||||
const CHART_MARGIN = {
|
||||
top: 4,
|
||||
right: 4,
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
};
|
||||
|
||||
const PROPORTION = {
|
||||
// text size: proportion of the chart container sans trendline
|
||||
KICKER: 0.1,
|
||||
|
@ -53,32 +42,6 @@ const PROPORTION = {
|
|||
TRENDLINE: 0.3,
|
||||
};
|
||||
|
||||
type TimeSeriesDatum = {
|
||||
x: number; // timestamp as a number
|
||||
y: number | null;
|
||||
};
|
||||
|
||||
export function renderTooltipFactory(
|
||||
formatDate = smartDateVerboseFormatter,
|
||||
formatValue = defaultNumberFormatter,
|
||||
) {
|
||||
return function renderTooltip({
|
||||
datum: { x, y },
|
||||
}: {
|
||||
datum: TimeSeriesDatum;
|
||||
}) {
|
||||
// even though `formatDate` supports timestamp as numbers, we need
|
||||
// `new Date` to pass type check
|
||||
return (
|
||||
<div style={{ padding: '4px 8px' }}>
|
||||
{formatDate(new Date(x))}
|
||||
<br />
|
||||
<strong>{y === null ? t('N/A') : formatValue(y)}</strong>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
type BigNumberVisProps = {
|
||||
className?: string;
|
||||
width: number;
|
||||
|
@ -87,8 +50,6 @@ type BigNumberVisProps = {
|
|||
bigNumberFallback?: TimeSeriesDatum;
|
||||
headerFormatter: NumberFormatter | TimeFormatter;
|
||||
formatTime: TimeFormatter;
|
||||
fromDatetime?: number;
|
||||
toDatetime?: number;
|
||||
headerFontSize: number;
|
||||
kickerFontSize: number;
|
||||
subheader: string;
|
||||
|
@ -100,11 +61,10 @@ type BigNumberVisProps = {
|
|||
timestamp?: number;
|
||||
trendLineData?: TimeSeriesDatum[];
|
||||
mainColor: string;
|
||||
echartOptions: EChartsCoreOption;
|
||||
};
|
||||
|
||||
class BigNumberVis extends React.PureComponent<BigNumberVisProps, {}> {
|
||||
private gradientId: string = shortid.generate();
|
||||
|
||||
class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
headerFormatter: defaultNumberFormatter,
|
||||
|
@ -146,7 +106,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps, {}> {
|
|||
role="alert"
|
||||
title={t(
|
||||
`Last available value seen on %s`,
|
||||
formatTime(bigNumberFallback.x),
|
||||
formatTime(bigNumberFallback[0]),
|
||||
)}
|
||||
>
|
||||
{t('Not up to date')}
|
||||
|
@ -254,79 +214,19 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps, {}> {
|
|||
}
|
||||
|
||||
renderTrendline(maxHeight: number) {
|
||||
const {
|
||||
width,
|
||||
trendLineData,
|
||||
mainColor,
|
||||
subheader,
|
||||
startYAxisAtZero,
|
||||
headerFormatter,
|
||||
formatTime,
|
||||
fromDatetime,
|
||||
timeRangeFixed,
|
||||
} = this.props;
|
||||
const { width, trendLineData, echartOptions } = this.props;
|
||||
|
||||
// if can't find any non-null values, no point rendering the trendline
|
||||
if (!trendLineData?.some(d => d.y !== null)) {
|
||||
if (!trendLineData?.some(d => d[1] !== null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Apply a fixed X range if a time range is specified.
|
||||
//
|
||||
// XYChart checks the existence of `domain` property and decide whether to
|
||||
// apply a domain or not, so it must not be `null` or `undefined`
|
||||
const xScale: { type: string; domain?: number[] } = { type: 'timeUtc' };
|
||||
const tooltipData = trendLineData && [...trendLineData];
|
||||
if (tooltipData && timeRangeFixed && fromDatetime) {
|
||||
const toDatetime = this.props.toDatetime ?? Date.now();
|
||||
if (tooltipData[0].x > fromDatetime) {
|
||||
tooltipData.unshift({
|
||||
x: fromDatetime,
|
||||
y: null,
|
||||
});
|
||||
}
|
||||
if (tooltipData[tooltipData.length - 1].x < toDatetime) {
|
||||
tooltipData.push({
|
||||
x: toDatetime,
|
||||
y: null,
|
||||
});
|
||||
}
|
||||
xScale.domain = [fromDatetime, toDatetime];
|
||||
}
|
||||
return (
|
||||
<XYChart
|
||||
snapTooltipToDataX
|
||||
ariaLabel={`Big number visualization ${subheader}`}
|
||||
// headerFormatter always NumberFormatter in BigNumber with treadline
|
||||
renderTooltip={renderTooltipFactory(
|
||||
formatTime,
|
||||
headerFormatter as NumberFormatter,
|
||||
)}
|
||||
xScale={xScale}
|
||||
yScale={{
|
||||
type: 'linear',
|
||||
includeZero: startYAxisAtZero,
|
||||
}}
|
||||
<Echart
|
||||
width={Math.floor(width)}
|
||||
height={maxHeight}
|
||||
margin={CHART_MARGIN}
|
||||
eventTrigger="container"
|
||||
>
|
||||
<LinearGradient id={this.gradientId} from={mainColor} to="#fff" />
|
||||
<AreaSeries
|
||||
data={tooltipData}
|
||||
fill={`url(#${this.gradientId})`}
|
||||
stroke={mainColor}
|
||||
/>
|
||||
<CrossHair
|
||||
fullHeight
|
||||
stroke={mainColor}
|
||||
circleFill={mainColor}
|
||||
circleStroke="#fff"
|
||||
showHorizontalLine={false}
|
||||
strokeDasharray="5,2"
|
||||
/>
|
||||
</XYChart>
|
||||
echartOptions={echartOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
buildQueryContext,
|
||||
DTTM_ALIAS,
|
||||
PostProcessingResample,
|
||||
QueryFormData,
|
||||
} from '@superset-ui/core';
|
||||
import {
|
||||
rollingWindowOperator,
|
||||
TIME_COLUMN,
|
||||
} from '@superset-ui/chart-controls';
|
||||
|
||||
const TIME_GRAIN_MAP: Record<string, string> = {
|
||||
PT1S: 'S',
|
||||
PT1M: 'min',
|
||||
PT5M: '5min',
|
||||
PT10M: '10min',
|
||||
PT15M: '15min',
|
||||
PT30M: '30min',
|
||||
PT1H: 'H',
|
||||
P1D: 'D',
|
||||
P1M: 'M',
|
||||
P3M: 'Q',
|
||||
P1Y: 'A',
|
||||
// TODO: these need to be mapped carefully, as the first day of week
|
||||
// can vary from engine to engine
|
||||
// P1W: 'W',
|
||||
// '1969-12-28T00:00:00Z/P1W': 'W',
|
||||
// '1969-12-29T00:00:00Z/P1W': 'W',
|
||||
// 'P1W/1970-01-03T00:00:00Z': 'W',
|
||||
// 'P1W/1970-01-04T00:00:00Z': 'W',
|
||||
};
|
||||
|
||||
export default function buildQuery(formData: QueryFormData) {
|
||||
return buildQueryContext(formData, baseQueryObject => {
|
||||
const rollingProc = rollingWindowOperator(formData, baseQueryObject);
|
||||
if (rollingProc) {
|
||||
rollingProc.options = { ...rollingProc.options, is_pivot_df: false };
|
||||
}
|
||||
const { time_grain_sqla } = formData;
|
||||
let resampleProc: PostProcessingResample | undefined;
|
||||
if (rollingProc && time_grain_sqla) {
|
||||
const rule = TIME_GRAIN_MAP[time_grain_sqla];
|
||||
if (rule) {
|
||||
resampleProc = {
|
||||
operation: 'resample',
|
||||
options: {
|
||||
method: 'asfreq',
|
||||
rule,
|
||||
fill_value: null,
|
||||
time_column: TIME_COLUMN,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return [
|
||||
{
|
||||
...baseQueryObject,
|
||||
is_timeseries: true,
|
||||
post_processing: [
|
||||
{
|
||||
operation: 'sort',
|
||||
options: {
|
||||
columns: {
|
||||
[DTTM_ALIAS]: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
resampleProc,
|
||||
rollingProc,
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { t } from '@superset-ui/core';
|
||||
import { smartDateFormatter, t } from '@superset-ui/core';
|
||||
import {
|
||||
ControlPanelConfig,
|
||||
D3_FORMAT_DOCS,
|
||||
|
@ -63,20 +63,6 @@ const config: ControlPanelConfig = {
|
|||
},
|
||||
},
|
||||
],
|
||||
['y_axis_format'],
|
||||
[
|
||||
{
|
||||
name: 'time_format',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
freeForm: true,
|
||||
label: t('Timestamp format'),
|
||||
renderTrigger: true,
|
||||
choices: D3_TIME_FORMAT_OPTIONS,
|
||||
description: D3_FORMAT_DOCS,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'show_timestamp',
|
||||
|
@ -142,6 +128,35 @@ const config: ControlPanelConfig = {
|
|||
['color_picker', null],
|
||||
[headerFontSize],
|
||||
[subheaderFontSize],
|
||||
['y_axis_format'],
|
||||
[
|
||||
{
|
||||
name: 'time_format',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
freeForm: true,
|
||||
label: t('Date format'),
|
||||
renderTrigger: true,
|
||||
choices: D3_TIME_FORMAT_OPTIONS,
|
||||
description: D3_FORMAT_DOCS,
|
||||
default: smartDateFormatter.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'force_timestamp_formatting',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Force date format'),
|
||||
renderTrigger: true,
|
||||
default: false,
|
||||
description: t(
|
||||
'Use date formatting even when metric value is not a timestamp',
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
@ -18,12 +18,14 @@
|
|||
*/
|
||||
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||
import controlPanel from './controlPanel';
|
||||
import transformProps, {
|
||||
BigNumberChartProps,
|
||||
BigNumberFormData,
|
||||
} from './transformProps';
|
||||
import transformProps from './transformProps';
|
||||
import buildQuery from './buildQuery';
|
||||
import example from './images/Big_Number_Trendline.jpg';
|
||||
import thumbnail from './images/thumbnail.png';
|
||||
import {
|
||||
BigNumberWithTrendlineChartProps,
|
||||
BigNumberWithTrendlineFormData,
|
||||
} from '../types';
|
||||
|
||||
const metadata = new ChartMetadata({
|
||||
category: t('KPI'),
|
||||
|
@ -43,17 +45,17 @@ const metadata = new ChartMetadata({
|
|||
t('Trend'),
|
||||
],
|
||||
thumbnail,
|
||||
useLegacyApi: true,
|
||||
});
|
||||
|
||||
export default class BigNumberChartPlugin extends ChartPlugin<
|
||||
BigNumberFormData,
|
||||
BigNumberChartProps
|
||||
export default class BigNumberWithTrendlineChartPlugin extends ChartPlugin<
|
||||
BigNumberWithTrendlineFormData,
|
||||
BigNumberWithTrendlineChartProps
|
||||
> {
|
||||
constructor() {
|
||||
super({
|
||||
loadChart: () => import('./BigNumber'),
|
||||
loadChart: () => import('../BigNumberViz'),
|
||||
metadata,
|
||||
buildQuery,
|
||||
transformProps,
|
||||
controlPanel,
|
||||
});
|
|
@ -0,0 +1,252 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
extractTimegrain,
|
||||
getNumberFormatter,
|
||||
NumberFormats,
|
||||
QueryFormData,
|
||||
GenericDataType,
|
||||
getMetricLabel,
|
||||
t,
|
||||
smartDateVerboseFormatter,
|
||||
NumberFormatter,
|
||||
TimeFormatter,
|
||||
} from '@superset-ui/core';
|
||||
import { EChartsCoreOption, graphic } from 'echarts';
|
||||
import {
|
||||
BigNumberDatum,
|
||||
BigNumberWithTrendlineChartProps,
|
||||
TimeSeriesDatum,
|
||||
} from '../types';
|
||||
import { getDateFormatter, parseMetricValue } from '../utils';
|
||||
|
||||
const defaultNumberFormatter = getNumberFormatter();
|
||||
export function renderTooltipFactory(
|
||||
formatDate: TimeFormatter = smartDateVerboseFormatter,
|
||||
formatValue: NumberFormatter | TimeFormatter = defaultNumberFormatter,
|
||||
) {
|
||||
return function renderTooltip(params: { data: TimeSeriesDatum }[]) {
|
||||
return `
|
||||
${formatDate(params[0].data[0])}
|
||||
<br />
|
||||
<strong>
|
||||
${
|
||||
params[0].data[1] === null ? t('N/A') : formatValue(params[0].data[1])
|
||||
}
|
||||
</strong>
|
||||
`;
|
||||
};
|
||||
}
|
||||
|
||||
const TIME_COLUMN = '__timestamp';
|
||||
const formatPercentChange = getNumberFormatter(
|
||||
NumberFormats.PERCENT_SIGNED_1_POINT,
|
||||
);
|
||||
|
||||
export default function transformProps(
|
||||
chartProps: BigNumberWithTrendlineChartProps,
|
||||
) {
|
||||
const { width, height, queriesData, formData, rawFormData } = chartProps;
|
||||
const {
|
||||
colorPicker,
|
||||
compareLag: compareLag_,
|
||||
compareSuffix = '',
|
||||
timeFormat,
|
||||
headerFontSize,
|
||||
metric = 'value',
|
||||
showTimestamp,
|
||||
showTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader = '',
|
||||
subheaderFontSize,
|
||||
forceTimestampFormatting,
|
||||
yAxisFormat,
|
||||
timeRangeFixed,
|
||||
} = formData;
|
||||
const granularity = extractTimegrain(rawFormData as QueryFormData);
|
||||
const {
|
||||
data = [],
|
||||
colnames = [],
|
||||
coltypes = [],
|
||||
from_dttm: fromDatetime,
|
||||
to_dttm: toDatetime,
|
||||
} = queriesData[0];
|
||||
const metricName = getMetricLabel(metric);
|
||||
const compareLag = Number(compareLag_) || 0;
|
||||
let formattedSubheader = subheader;
|
||||
|
||||
const { r, g, b } = colorPicker;
|
||||
const mainColor = `rgb(${r}, ${g}, ${b})`;
|
||||
|
||||
let trendLineData;
|
||||
let percentChange = 0;
|
||||
let bigNumber = data.length === 0 ? null : data[0][metricName];
|
||||
let timestamp = data.length === 0 ? null : data[0][TIME_COLUMN];
|
||||
let bigNumberFallback;
|
||||
|
||||
const metricColtypeIndex = colnames.findIndex(name => name === metricName);
|
||||
const metricColtype =
|
||||
metricColtypeIndex > -1 ? coltypes[metricColtypeIndex] : null;
|
||||
|
||||
if (data.length > 0) {
|
||||
const sortedData = (data as BigNumberDatum[])
|
||||
.map(d => [d[TIME_COLUMN], parseMetricValue(d[metricName])])
|
||||
// sort in time descending order
|
||||
.sort((a, b) => (a[0] !== null && b[0] !== null ? b[0] - a[0] : 0));
|
||||
|
||||
bigNumber = sortedData[0][1];
|
||||
timestamp = sortedData[0][0];
|
||||
|
||||
if (bigNumber === null) {
|
||||
bigNumberFallback = sortedData.find(d => d[1] !== null);
|
||||
bigNumber = bigNumberFallback ? bigNumberFallback[1] : null;
|
||||
timestamp = bigNumberFallback ? bigNumberFallback[0] : null;
|
||||
}
|
||||
|
||||
if (compareLag > 0) {
|
||||
const compareIndex = compareLag;
|
||||
if (compareIndex < sortedData.length) {
|
||||
const compareValue = sortedData[compareIndex][1];
|
||||
// compare values must both be non-nulls
|
||||
if (bigNumber !== null && compareValue !== null && compareValue !== 0) {
|
||||
percentChange = (bigNumber - compareValue) / Math.abs(compareValue);
|
||||
formattedSubheader = `${formatPercentChange(
|
||||
percentChange,
|
||||
)} ${compareSuffix}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
sortedData.reverse();
|
||||
trendLineData = showTrendLine ? sortedData : undefined;
|
||||
}
|
||||
|
||||
let className = '';
|
||||
if (percentChange > 0) {
|
||||
className = 'positive';
|
||||
} else if (percentChange < 0) {
|
||||
className = 'negative';
|
||||
}
|
||||
|
||||
let metricEntry;
|
||||
if (chartProps.datasource && chartProps.datasource.metrics) {
|
||||
metricEntry = chartProps.datasource.metrics.find(
|
||||
metricEntry => metricEntry.metric_name === metric,
|
||||
);
|
||||
}
|
||||
|
||||
const formatTime = getDateFormatter(
|
||||
timeFormat,
|
||||
granularity,
|
||||
metricEntry?.d3format,
|
||||
);
|
||||
|
||||
const headerFormatter =
|
||||
metricColtype === GenericDataType.TEMPORAL ||
|
||||
metricColtype === GenericDataType.STRING ||
|
||||
forceTimestampFormatting
|
||||
? formatTime
|
||||
: getNumberFormatter(yAxisFormat ?? metricEntry?.d3format ?? undefined);
|
||||
|
||||
if (trendLineData && timeRangeFixed && fromDatetime) {
|
||||
const toDatetimeOrToday = toDatetime ?? Date.now();
|
||||
if (!trendLineData[0][0] || trendLineData[0][0] > fromDatetime) {
|
||||
trendLineData.unshift([fromDatetime, null]);
|
||||
}
|
||||
if (
|
||||
!trendLineData[trendLineData.length - 1][0] ||
|
||||
trendLineData[trendLineData.length - 1][0]! < toDatetimeOrToday
|
||||
) {
|
||||
trendLineData.push([toDatetimeOrToday, null]);
|
||||
}
|
||||
}
|
||||
|
||||
const echartOptions: EChartsCoreOption = trendLineData
|
||||
? {
|
||||
series: [
|
||||
{
|
||||
data: trendLineData,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
showSymbol: false,
|
||||
color: mainColor,
|
||||
areaStyle: {
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: mainColor,
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'white',
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
],
|
||||
xAxis: {
|
||||
min: trendLineData[0][0],
|
||||
max: trendLineData[trendLineData.length - 1][0],
|
||||
show: false,
|
||||
type: 'value',
|
||||
},
|
||||
yAxis: {
|
||||
scale: !startYAxisAtZero,
|
||||
show: false,
|
||||
},
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
confine: true,
|
||||
formatter: renderTooltipFactory(formatTime, headerFormatter),
|
||||
},
|
||||
aria: {
|
||||
enabled: true,
|
||||
label: {
|
||||
description: `Big number visualization ${subheader}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {};
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
bigNumber,
|
||||
bigNumberFallback,
|
||||
className,
|
||||
headerFormatter,
|
||||
formatTime,
|
||||
headerFontSize,
|
||||
subheaderFontSize,
|
||||
mainColor,
|
||||
showTimestamp,
|
||||
showTrendLine,
|
||||
startYAxisAtZero,
|
||||
subheader: formattedSubheader,
|
||||
timestamp,
|
||||
trendLineData,
|
||||
echartOptions,
|
||||
};
|
||||
}
|
|
@ -17,6 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { default as BigNumberChartPlugin } from './BigNumber/index';
|
||||
export { default as BigNumberTotalChartPlugin } from './BigNumberTotal/index';
|
||||
export { default as BigNumberChartPreset } from './preset';
|
||||
export { default as BigNumberChartPlugin } from './BigNumberWithTrendline';
|
||||
export { default as BigNumberTotalChartPlugin } from './BigNumberTotal';
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// These are control configurations that are shared ONLY within the BigNumber viz plugin repo.
|
||||
// These are control configurations that are shared ONLY within the BigNumberWithTrendline viz plugin repo.
|
||||
import { t } from '@superset-ui/core';
|
||||
import { CustomControlItem } from '@superset-ui/chart-controls';
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ChartDataResponseResult,
|
||||
ChartProps,
|
||||
QueryFormData,
|
||||
QueryFormMetric,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
export interface BigNumberDatum {
|
||||
[key: string]: number | null;
|
||||
}
|
||||
|
||||
export type BigNumberTotalFormData = QueryFormData & {
|
||||
metric?: QueryFormMetric;
|
||||
yAxisFormat?: string;
|
||||
forceTimestampFormatting?: boolean;
|
||||
};
|
||||
|
||||
export type BigNumberWithTrendlineFormData = BigNumberTotalFormData & {
|
||||
colorPicker: {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
};
|
||||
compareLag?: string | number;
|
||||
};
|
||||
|
||||
export type BigNumberTotalChartProps = ChartProps & {
|
||||
formData: BigNumberTotalFormData;
|
||||
queriesData: (ChartDataResponseResult & {
|
||||
data?: BigNumberDatum[];
|
||||
})[];
|
||||
};
|
||||
|
||||
export type BigNumberWithTrendlineChartProps = BigNumberTotalChartProps & {
|
||||
formData: BigNumberWithTrendlineFormData;
|
||||
};
|
||||
|
||||
export type TimeSeriesDatum = [number, number | null];
|
|
@ -16,18 +16,31 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Preset } from '@superset-ui/core';
|
||||
import BigNumberChartPlugin from './BigNumber';
|
||||
import BigNumberTotalChartPlugin from './BigNumberTotal';
|
||||
|
||||
export default class BigNumberChartPreset extends Preset {
|
||||
constructor() {
|
||||
super({
|
||||
name: 'BigNumber charts',
|
||||
plugins: [
|
||||
new BigNumberChartPlugin().configure({ key: 'big_number' }),
|
||||
new BigNumberTotalChartPlugin().configure({ key: 'big_number_total' }),
|
||||
],
|
||||
});
|
||||
import moment from 'moment';
|
||||
import {
|
||||
getTimeFormatter,
|
||||
getTimeFormatterForGranularity,
|
||||
smartDateFormatter,
|
||||
TimeGranularity,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
export const parseMetricValue = (metricValue: number | string | null) => {
|
||||
if (typeof metricValue === 'string') {
|
||||
const dateObject = moment.utc(metricValue, moment.ISO_8601, true);
|
||||
if (dateObject.isValid()) {
|
||||
return dateObject.valueOf();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return metricValue;
|
||||
};
|
||||
|
||||
export const getDateFormatter = (
|
||||
timeFormat: string,
|
||||
granularity?: TimeGranularity,
|
||||
fallbackFormat?: string | null,
|
||||
) =>
|
||||
timeFormat === smartDateFormatter.id
|
||||
? getTimeFormatterForGranularity(granularity)
|
||||
: getTimeFormatter(timeFormat ?? fallbackFormat);
|
|
@ -32,6 +32,7 @@ export { default as EchartsRadarChartPlugin } from './Radar';
|
|||
export { default as EchartsFunnelChartPlugin } from './Funnel';
|
||||
export { default as EchartsTreeChartPlugin } from './Tree';
|
||||
export { default as EchartsTreemapChartPlugin } from './Treemap';
|
||||
export { BigNumberChartPlugin, BigNumberTotalChartPlugin } from './BigNumber';
|
||||
|
||||
export { default as BoxPlotTransformProps } from './BoxPlot/transformProps';
|
||||
export { default as FunnelTransformProps } from './Funnel/transformProps';
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { DatasourceType, TimeGranularity } from '@superset-ui/core';
|
||||
import transformProps, {
|
||||
BignumberChartProps,
|
||||
import transformProps from '../../src/BigNumber/BigNumberWithTrendline/transformProps';
|
||||
import {
|
||||
BigNumberDatum,
|
||||
} from '../src/BigNumber/transformProps';
|
||||
BigNumberWithTrendlineChartProps,
|
||||
} from '../../src/BigNumber/types';
|
||||
|
||||
const formData = {
|
||||
metric: 'value',
|
||||
|
@ -33,8 +34,9 @@ const formData = {
|
|||
compareLag: 1,
|
||||
timeGrainSqla: 'P3M' as TimeGranularity,
|
||||
compareSuffix: 'over last quarter',
|
||||
vizType: 'big_number',
|
||||
viz_type: 'big_number',
|
||||
yAxisFormat: '.3s',
|
||||
datasource: 'test_datasource',
|
||||
};
|
||||
|
||||
const rawFormData = {
|
||||
|
@ -56,7 +58,7 @@ function generateProps(
|
|||
data: BigNumberDatum[],
|
||||
extraFormData = {},
|
||||
extraQueryData = {},
|
||||
): BignumberChartProps {
|
||||
): BigNumberWithTrendlineChartProps {
|
||||
return {
|
||||
width: 200,
|
||||
height: 500,
|
||||
|
@ -84,10 +86,13 @@ function generateProps(
|
|||
...extraQueryData,
|
||||
},
|
||||
],
|
||||
ownState: {},
|
||||
filterState: {},
|
||||
behaviors: [],
|
||||
};
|
||||
}
|
||||
|
||||
describe('BigNumber', () => {
|
||||
describe('BigNumberWithTrendline', () => {
|
||||
const props = generateProps(
|
||||
[
|
||||
{
|
||||
|
@ -109,8 +114,8 @@ describe('BigNumber', () => {
|
|||
const lastDatum = transformed.trendLineData?.pop();
|
||||
|
||||
// should use last available value
|
||||
expect(lastDatum?.x).toStrictEqual(100);
|
||||
expect(lastDatum?.y).toBeNull();
|
||||
expect(lastDatum?.[0]).toStrictEqual(100);
|
||||
expect(lastDatum?.[1]).toBeNull();
|
||||
|
||||
// should note this is a fallback
|
||||
expect(transformed.bigNumber).toStrictEqual(1.2345);
|
|
@ -68,7 +68,7 @@ const basicChartProps = {
|
|||
const basicQueryResult: ChartDataResponseResult = {
|
||||
annotation_data: null,
|
||||
cache_key: null,
|
||||
cache_dttm: null,
|
||||
cached_dttm: null,
|
||||
cache_timeout: null,
|
||||
data: [],
|
||||
colnames: [],
|
||||
|
|
|
@ -17,10 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { isFeatureEnabled, Preset, FeatureFlag } from '@superset-ui/core';
|
||||
import {
|
||||
BigNumberChartPlugin,
|
||||
BigNumberTotalChartPlugin,
|
||||
} from '@superset-ui/legacy-preset-chart-big-number';
|
||||
import CalendarChartPlugin from '@superset-ui/legacy-plugin-chart-calendar';
|
||||
import ChordChartPlugin from '@superset-ui/legacy-plugin-chart-chord';
|
||||
import CountryMapChartPlugin from '@superset-ui/legacy-plugin-chart-country-map';
|
||||
|
@ -54,6 +50,8 @@ import {
|
|||
} from '@superset-ui/legacy-preset-chart-nvd3';
|
||||
import { DeckGLChartPreset } from '@superset-ui/legacy-preset-chart-deckgl';
|
||||
import {
|
||||
BigNumberChartPlugin,
|
||||
BigNumberTotalChartPlugin,
|
||||
EchartsPieChartPlugin,
|
||||
EchartsBoxPlotChartPlugin,
|
||||
EchartsAreaChartPlugin,
|
||||
|
|
|
@ -1240,12 +1240,22 @@ class ChartDataResponseResult(Schema):
|
|||
description="Amount of rows in result set", allow_none=False,
|
||||
)
|
||||
data = fields.List(fields.Dict(), description="A list with results")
|
||||
colnames = fields.List(fields.String(), description="A list of column names")
|
||||
coltypes = fields.List(
|
||||
fields.Integer(), description="A list of generic data types of each column"
|
||||
)
|
||||
applied_filters = fields.List(
|
||||
fields.Dict(), description="A list with applied filters"
|
||||
)
|
||||
rejected_filters = fields.List(
|
||||
fields.Dict(), description="A list with rejected filters"
|
||||
)
|
||||
from_dttm = fields.Integer(
|
||||
desciption="Start timestamp of time range", required=False, allow_none=True
|
||||
)
|
||||
to_dttm = fields.Integer(
|
||||
desciption="End timestamp of time range", required=False, allow_none=True
|
||||
)
|
||||
|
||||
|
||||
class ChartDataResponseSchema(Schema):
|
||||
|
|
|
@ -144,6 +144,8 @@ class QueryContextProcessor:
|
|||
"status": cache.status,
|
||||
"stacktrace": cache.stacktrace,
|
||||
"rowcount": len(cache.df.index),
|
||||
"from_dttm": query_obj.from_dttm,
|
||||
"to_dttm": query_obj.to_dttm,
|
||||
}
|
||||
|
||||
def query_cache_key(self, query_obj: QueryObject, **kwargs: Any) -> Optional[str]:
|
||||
|
@ -201,6 +203,8 @@ class QueryContextProcessor:
|
|||
|
||||
result.df = df
|
||||
result.query = query
|
||||
result.from_dttm = query_object.from_dttm
|
||||
result.to_dttm = query_object.to_dttm
|
||||
return result
|
||||
|
||||
def normalize_df(self, df: pd.DataFrame, query_object: QueryObject) -> pd.DataFrame:
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""rename_big_viz_total_form_data_fields
|
||||
|
||||
Revision ID: fe23025b9441
|
||||
Revises: 3ba29ecbaac5
|
||||
Create Date: 2021-12-13 14:06:24.426970
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "fe23025b9441"
|
||||
down_revision = "3ba29ecbaac5"
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
from alembic import op
|
||||
from sqlalchemy import Column, Integer, String, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from superset import db
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger = logging.getLogger("alembic")
|
||||
|
||||
|
||||
class Slice(Base):
|
||||
__tablename__ = "slices"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
params = Column(Text)
|
||||
viz_type = Column(String(250))
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
slices = session.query(Slice).filter(Slice.viz_type == "big_number_total").all()
|
||||
for slc in slices:
|
||||
try:
|
||||
params = json.loads(slc.params)
|
||||
header_format_selector = params.pop("header_format_selector", None)
|
||||
header_timestamp_format = params.pop("header_timestamp_format", None)
|
||||
if header_format_selector:
|
||||
params["force_timestamp_formatting"] = header_format_selector
|
||||
if header_timestamp_format:
|
||||
params["time_format"] = header_timestamp_format
|
||||
slc.params = json.dumps(params, sort_keys=True)
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"An error occurred: parsing params for slice {slc.id} failed."
|
||||
f"You need to fix it before upgrading your DB."
|
||||
)
|
||||
raise e
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
slices = session.query(Slice).filter(Slice.viz_type == "big_number_total").all()
|
||||
for slc in slices:
|
||||
try:
|
||||
params = json.loads(slc.params)
|
||||
time_format = params.pop("time_format", None)
|
||||
force_timestamp_formatting = params.pop("force_timestamp_formatting", None)
|
||||
if time_format:
|
||||
params["header_timestamp_format"] = time_format
|
||||
if force_timestamp_formatting:
|
||||
params["header_format_selector"] = force_timestamp_formatting
|
||||
slc.params = json.dumps(params, sort_keys=True)
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f"An error occurred: parsing params for slice {slc.id} failed. "
|
||||
"You need to fix it before downgrading your DB."
|
||||
)
|
||||
raise e
|
||||
|
||||
session.commit()
|
||||
session.close()
|
|
@ -446,6 +446,8 @@ class QueryResult: # pylint: disable=too-few-public-methods
|
|||
status: str = QueryStatus.SUCCESS,
|
||||
error_message: Optional[str] = None,
|
||||
errors: Optional[List[Dict[str, Any]]] = None,
|
||||
from_dttm: Optional[datetime] = None,
|
||||
to_dttm: Optional[datetime] = None,
|
||||
) -> None:
|
||||
self.df = df
|
||||
self.query = query
|
||||
|
@ -454,6 +456,8 @@ class QueryResult: # pylint: disable=too-few-public-methods
|
|||
self.status = status
|
||||
self.error_message = error_message
|
||||
self.errors = errors or []
|
||||
self.from_dttm = from_dttm
|
||||
self.to_dttm = to_dttm
|
||||
|
||||
|
||||
class ExtraJSONMixin:
|
||||
|
|