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
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { interceptChart } from 'cypress/utils';
|
||||||
|
|
||||||
describe('Visualization > Big Number with Trendline', () => {
|
describe('Visualization > Big Number with Trendline', () => {
|
||||||
const BIG_NUMBER_FORM_DATA = {
|
const BIG_NUMBER_FORM_DATA = {
|
||||||
datasource: '2__table',
|
datasource: '2__table',
|
||||||
@ -42,21 +44,21 @@ describe('Visualization > Big Number with Trendline', () => {
|
|||||||
function verify(formData) {
|
function verify(formData) {
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(JSON.stringify(formData));
|
||||||
cy.verifySliceSuccess({
|
cy.verifySliceSuccess({
|
||||||
waitAlias: '@getJson',
|
waitAlias: '@chartData',
|
||||||
chartSelector: '.superset-legacy-chart-big-number',
|
chartSelector: '.superset-legacy-chart-big-number',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.login();
|
cy.login();
|
||||||
cy.intercept('POST', '/superset/explore_json/**').as('getJson');
|
interceptChart({ legacy: false }).as('chartData');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work', () => {
|
it('should work', () => {
|
||||||
verify(BIG_NUMBER_FORM_DATA);
|
verify(BIG_NUMBER_FORM_DATA);
|
||||||
cy.get('.chart-container .header-line');
|
cy.get('.chart-container .header-line');
|
||||||
cy.get('.chart-container .subheader-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', () => {
|
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 .header-line');
|
||||||
cy.get('.chart-container .subheader-line').should('not.exist');
|
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', () => {
|
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"] .header-line');
|
||||||
cy.get('[data-test="chart-container"] .subheader-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
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import { interceptChart } from 'cypress/utils';
|
||||||
import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper';
|
import { FORM_DATA_DEFAULTS, NUM_METRIC } from './shared.helper';
|
||||||
|
|
||||||
describe('Visualization > Big Number Total', () => {
|
describe('Visualization > Big Number Total', () => {
|
||||||
@ -26,15 +27,15 @@ describe('Visualization > Big Number Total', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.login();
|
cy.login();
|
||||||
cy.intercept('POST', '/superset/explore_json/**').as('getJson');
|
interceptChart({ legacy: false }).as('chartData');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Test big number chart with adhoc metric', () => {
|
it('Test big number chart with adhoc metric', () => {
|
||||||
const formData = { ...BIG_NUMBER_DEFAULTS, metric: NUM_METRIC };
|
const formData = { ...BIG_NUMBER_DEFAULTS, metric: NUM_METRIC };
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(formData);
|
||||||
cy.verifySliceSuccess({
|
cy.verifySliceSuccess({
|
||||||
waitAlias: '@getJson',
|
waitAlias: '@chartData',
|
||||||
querySubstring: NUM_METRIC.label,
|
querySubstring: NUM_METRIC.label,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -58,8 +59,8 @@ describe('Visualization > Big Number Total', () => {
|
|||||||
adhoc_filters: filters,
|
adhoc_filters: filters,
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(formData);
|
||||||
cy.verifySliceSuccess({ waitAlias: '@getJson' });
|
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Test big number chart ignores groupby', () => {
|
it('Test big number chart ignores groupby', () => {
|
||||||
@ -69,11 +70,11 @@ describe('Visualization > Big Number Total', () => {
|
|||||||
groupby: ['state'],
|
groupby: ['state'],
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.visitChartByParams(JSON.stringify(formData));
|
cy.visitChartByParams(formData);
|
||||||
cy.wait(['@getJson']).then(async ({ response }) => {
|
cy.wait(['@chartData']).then(async ({ response }) => {
|
||||||
cy.verifySliceContainer();
|
cy.verifySliceContainer();
|
||||||
const responseBody = response?.body;
|
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',
|
'word_cloud',
|
||||||
'pie',
|
'pie',
|
||||||
'table',
|
'table',
|
||||||
|
'big_number',
|
||||||
|
'big_number_total',
|
||||||
];
|
];
|
||||||
export const DASHBOARD_CHART_ALIAS_PREFIX = 'getChartData_';
|
export const DASHBOARD_CHART_ALIAS_PREFIX = 'getChartData_';
|
||||||
|
|
||||||
|
27
superset-frontend/package-lock.json
generated
@ -41,7 +41,6 @@
|
|||||||
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.25",
|
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.25",
|
||||||
"@superset-ui/legacy-plugin-chart-treemap": "^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-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-deckgl": "^0.4.13",
|
||||||
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.25",
|
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.25",
|
||||||
"@superset-ui/plugin-chart-echarts": "^0.18.25",
|
"@superset-ui/plugin-chart-echarts": "^0.18.25",
|
||||||
@ -198,6 +197,7 @@
|
|||||||
"@types/redux-localstorage": "^1.0.8",
|
"@types/redux-localstorage": "^1.0.8",
|
||||||
"@types/redux-mock-store": "^1.0.2",
|
"@types/redux-mock-store": "^1.0.2",
|
||||||
"@types/rison": "0.0.6",
|
"@types/rison": "0.0.6",
|
||||||
|
"@types/shortid": "^0.0.29",
|
||||||
"@types/sinon": "^9.0.5",
|
"@types/sinon": "^9.0.5",
|
||||||
"@types/yargs": "12 - 15",
|
"@types/yargs": "12 - 15",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||||
@ -21125,10 +21125,6 @@
|
|||||||
"resolved": "plugins/legacy-plugin-chart-world-map",
|
"resolved": "plugins/legacy-plugin-chart-world-map",
|
||||||
"link": true
|
"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": {
|
"node_modules/@superset-ui/legacy-preset-chart-deckgl": {
|
||||||
"resolved": "plugins/legacy-preset-chart-deckgl",
|
"resolved": "plugins/legacy-preset-chart-deckgl",
|
||||||
"link": true
|
"link": true
|
||||||
@ -22433,7 +22429,8 @@
|
|||||||
"node_modules/@types/shortid": {
|
"node_modules/@types/shortid": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
|
"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": {
|
"node_modules/@types/sinon": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
@ -60726,7 +60723,6 @@
|
|||||||
"@superset-ui/legacy-plugin-chart-time-table": "0.18.25",
|
"@superset-ui/legacy-plugin-chart-time-table": "0.18.25",
|
||||||
"@superset-ui/legacy-plugin-chart-treemap": "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-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-deckgl": "^0.4.13",
|
||||||
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
||||||
"@superset-ui/plugin-chart-echarts": "0.18.25",
|
"@superset-ui/plugin-chart-echarts": "0.18.25",
|
||||||
@ -61502,6 +61498,7 @@
|
|||||||
"plugins/legacy-preset-chart-big-number": {
|
"plugins/legacy-preset-chart-big-number": {
|
||||||
"name": "@superset-ui/legacy-preset-chart-big-number",
|
"name": "@superset-ui/legacy-preset-chart-big-number",
|
||||||
"version": "0.18.25",
|
"version": "0.18.25",
|
||||||
|
"extraneous": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@data-ui/xy-chart": "^0.0.84",
|
"@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-time-table": "0.18.25",
|
||||||
"@superset-ui/legacy-plugin-chart-treemap": "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-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-deckgl": "^0.4.13",
|
||||||
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
||||||
"@superset-ui/plugin-chart-echarts": "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": {
|
"@superset-ui/legacy-preset-chart-deckgl": {
|
||||||
"version": "file:plugins/legacy-preset-chart-deckgl",
|
"version": "file:plugins/legacy-preset-chart-deckgl",
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -79940,7 +79924,8 @@
|
|||||||
"@types/shortid": {
|
"@types/shortid": {
|
||||||
"version": "0.0.29",
|
"version": "0.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz",
|
||||||
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps="
|
"integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/sinon": {
|
"@types/sinon": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
|
@ -101,7 +101,6 @@
|
|||||||
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.25",
|
"@superset-ui/legacy-plugin-chart-sunburst": "^0.18.25",
|
||||||
"@superset-ui/legacy-plugin-chart-treemap": "^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-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-deckgl": "^0.4.13",
|
||||||
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.25",
|
"@superset-ui/legacy-preset-chart-nvd3": "^0.18.25",
|
||||||
"@superset-ui/plugin-chart-echarts": "^0.18.25",
|
"@superset-ui/plugin-chart-echarts": "^0.18.25",
|
||||||
@ -258,6 +257,7 @@
|
|||||||
"@types/redux-localstorage": "^1.0.8",
|
"@types/redux-localstorage": "^1.0.8",
|
||||||
"@types/redux-mock-store": "^1.0.2",
|
"@types/redux-mock-store": "^1.0.2",
|
||||||
"@types/rison": "0.0.6",
|
"@types/rison": "0.0.6",
|
||||||
|
"@types/shortid": "^0.0.29",
|
||||||
"@types/sinon": "^9.0.5",
|
"@types/sinon": "^9.0.5",
|
||||||
"@types/yargs": "12 - 15",
|
"@types/yargs": "12 - 15",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||||
|
@ -50,7 +50,7 @@ export interface ChartDataResponseResult {
|
|||||||
annotation_data: AnnotationData[] | null;
|
annotation_data: AnnotationData[] | null;
|
||||||
cache_key: string | null;
|
cache_key: string | null;
|
||||||
cache_timeout: number | null;
|
cache_timeout: number | null;
|
||||||
cache_dttm: string | null;
|
cached_dttm: string | null;
|
||||||
/**
|
/**
|
||||||
* Array of data records as dictionary
|
* Array of data records as dictionary
|
||||||
*/
|
*/
|
||||||
@ -76,6 +76,8 @@ export interface ChartDataResponseResult {
|
|||||||
| 'scheduled'
|
| 'scheduled'
|
||||||
| 'success'
|
| 'success'
|
||||||
| 'timed_out';
|
| 'timed_out';
|
||||||
|
from_dttm: number | null;
|
||||||
|
to_dttm: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeseriesChartDataResponseResult
|
export interface TimeseriesChartDataResponseResult
|
||||||
|
@ -61,7 +61,6 @@
|
|||||||
"@superset-ui/legacy-plugin-chart-time-table": "0.18.25",
|
"@superset-ui/legacy-plugin-chart-time-table": "0.18.25",
|
||||||
"@superset-ui/legacy-plugin-chart-treemap": "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-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-deckgl": "^0.4.13",
|
||||||
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
"@superset-ui/legacy-preset-chart-nvd3": "0.18.25",
|
||||||
"@superset-ui/plugin-chart-echarts": "0.18.25",
|
"@superset-ui/plugin-chart-echarts": "0.18.25",
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SuperChart } from '@superset-ui/core';
|
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';
|
import testData from './data';
|
||||||
|
|
||||||
new BigNumberChartPlugin().configure({ key: 'big-number' }).register();
|
new BigNumberChartPlugin().configure({ key: 'big-number' }).register();
|
||||||
@ -56,7 +56,7 @@ function withNulls(origData: object[], nullPosition = 3) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Legacy Chart Plugins/legacy-preset-big-number/BigNumber',
|
title: 'Legacy Chart Plugins/legacy-preset-big-number/BigNumberWithTrendline',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const basicWithTrendline = () => (
|
export const basicWithTrendline = () => (
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SuperChart } from '@superset-ui/core';
|
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';
|
import data from './data';
|
||||||
|
|
||||||
new BigNumberTotalChartPlugin()
|
new BigNumberTotalChartPlugin()
|
||||||
|
@ -25,7 +25,7 @@ import {
|
|||||||
ChartDataProvider,
|
ChartDataProvider,
|
||||||
SupersetClient,
|
SupersetClient,
|
||||||
} from '@superset-ui/core';
|
} 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 LegacySankeyPlugin from '@superset-ui/legacy-plugin-chart-sankey';
|
||||||
import LegacySunburstPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
import LegacySunburstPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
||||||
import { WordCloudChartPlugin } from '@superset-ui/plugin-chart-word-cloud';
|
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_LEGACY = wordCloudFormData.viz_type;
|
||||||
const WORD_CLOUD = 'new_word_cloud';
|
const WORD_CLOUD = 'new_word_cloud';
|
||||||
|
|
||||||
new LegacyBigNumberPlugin().configure({ key: BIG_NUMBER }).register();
|
new BigNumberChartPlugin().configure({ key: BIG_NUMBER }).register();
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
new LegacySankeyPlugin().configure({ key: SANKEY }).register();
|
new LegacySankeyPlugin().configure({ key: SANKEY }).register();
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
@ -16,5 +16,6 @@
|
|||||||
"storybook",
|
"storybook",
|
||||||
"../**/src",
|
"../**/src",
|
||||||
"../../plugins/**/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
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
declare module '@data-ui/xy-chart';
|
import { buildQueryContext, QueryFormData } from '@superset-ui/core';
|
||||||
declare module '*.png';
|
|
||||||
declare module '*.jpg';
|
export default function buildQuery(formData: QueryFormData) {
|
||||||
|
return buildQueryContext(formData, baseQueryObject => [baseQueryObject]);
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { t } from '@superset-ui/core';
|
import { smartDateFormatter, t } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
ControlPanelConfig,
|
ControlPanelConfig,
|
||||||
D3_FORMAT_DOCS,
|
D3_FORMAT_DOCS,
|
||||||
@ -27,7 +27,7 @@ import { headerFontSize, subheaderFontSize } from '../sharedControls';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
controlPanelSections: [
|
controlPanelSections: [
|
||||||
sections.legacyRegularTime,
|
sections.legacyTimeseriesTime,
|
||||||
{
|
{
|
||||||
label: t('Query'),
|
label: t('Query'),
|
||||||
expanded: true,
|
expanded: true,
|
||||||
@ -51,43 +51,44 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Chart Options'),
|
||||||
|
expanded: true,
|
||||||
|
controlSetRows: [
|
||||||
|
[headerFontSize],
|
||||||
|
[subheaderFontSize],
|
||||||
['y_axis_format'],
|
['y_axis_format'],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: 'header_format_selector',
|
name: 'time_format',
|
||||||
config: {
|
|
||||||
type: 'CheckboxControl',
|
|
||||||
label: t('Timestamp Format'),
|
|
||||||
renderTrigger: true,
|
|
||||||
default: false,
|
|
||||||
description: t('Whether to format the timestamp'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'header_timestamp_format',
|
|
||||||
config: {
|
config: {
|
||||||
type: 'SelectControl',
|
type: 'SelectControl',
|
||||||
freeForm: true,
|
freeForm: true,
|
||||||
label: t('Date format'),
|
label: t('Date format'),
|
||||||
renderTrigger: true,
|
renderTrigger: true,
|
||||||
choices: D3_TIME_FORMAT_OPTIONS,
|
choices: D3_TIME_FORMAT_OPTIONS,
|
||||||
default: '%d-%m-%Y %H:%M:%S',
|
|
||||||
description: D3_FORMAT_DOCS,
|
description: D3_FORMAT_DOCS,
|
||||||
visibility(props) {
|
default: smartDateFormatter.id,
|
||||||
const { header_format_selector } = props.form_data;
|
|
||||||
return !!header_format_selector;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
[
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: t('Chart Options'),
|
name: 'force_timestamp_formatting',
|
||||||
expanded: true,
|
config: {
|
||||||
controlSetRows: [[headerFontSize], [subheaderFontSize]],
|
type: 'CheckboxControl',
|
||||||
|
label: t('Force date format'),
|
||||||
|
renderTrigger: true,
|
||||||
|
default: false,
|
||||||
|
description: t(
|
||||||
|
'Use date formatting even when metric value is not a timestamp',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
controlOverrides: {
|
controlOverrides: {
|
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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||||
import controlPanel from './controlPanel';
|
import controlPanel from './controlPanel';
|
||||||
import transformProps, {
|
import transformProps from './transformProps';
|
||||||
BigNumberChartProps,
|
import buildQuery from './buildQuery';
|
||||||
BigNumberFormData,
|
|
||||||
} from '../BigNumber/transformProps';
|
|
||||||
import example1 from './images/BigNumber.jpg';
|
import example1 from './images/BigNumber.jpg';
|
||||||
import example2 from './images/BigNumber2.jpg';
|
import example2 from './images/BigNumber2.jpg';
|
||||||
import thumbnail from './images/thumbnail.png';
|
import thumbnail from './images/thumbnail.png';
|
||||||
|
import { BigNumberTotalChartProps, BigNumberTotalFormData } from '../types';
|
||||||
|
|
||||||
const metadata = new ChartMetadata({
|
const metadata = new ChartMetadata({
|
||||||
category: t('KPI'),
|
category: t('KPI'),
|
||||||
@ -47,17 +46,17 @@ const metadata = new ChartMetadata({
|
|||||||
t('Description'),
|
t('Description'),
|
||||||
],
|
],
|
||||||
thumbnail,
|
thumbnail,
|
||||||
useLegacyApi: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class BigNumberTotalChartPlugin extends ChartPlugin<
|
export default class BigNumberTotalChartPlugin extends ChartPlugin<
|
||||||
BigNumberFormData,
|
BigNumberTotalFormData,
|
||||||
BigNumberChartProps
|
BigNumberTotalChartProps
|
||||||
> {
|
> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
loadChart: () => import('../BigNumber/BigNumber'),
|
loadChart: () => import('../BigNumberViz'),
|
||||||
metadata,
|
metadata,
|
||||||
|
buildQuery,
|
||||||
transformProps,
|
transformProps,
|
||||||
controlPanel,
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import shortid from 'shortid';
|
|
||||||
import {
|
import {
|
||||||
t,
|
t,
|
||||||
getNumberFormatter,
|
getNumberFormatter,
|
||||||
@ -28,22 +27,12 @@ import {
|
|||||||
BRAND_COLOR,
|
BRAND_COLOR,
|
||||||
styled,
|
styled,
|
||||||
} from '@superset-ui/core';
|
} from '@superset-ui/core';
|
||||||
import {
|
import { EChartsCoreOption } from 'echarts';
|
||||||
XYChart,
|
import Echart from '../components/Echart';
|
||||||
AreaSeries,
|
import { TimeSeriesDatum } from './types';
|
||||||
CrossHair,
|
|
||||||
LinearGradient,
|
|
||||||
} from '@data-ui/xy-chart';
|
|
||||||
|
|
||||||
const defaultNumberFormatter = getNumberFormatter();
|
const defaultNumberFormatter = getNumberFormatter();
|
||||||
|
|
||||||
const CHART_MARGIN = {
|
|
||||||
top: 4,
|
|
||||||
right: 4,
|
|
||||||
bottom: 4,
|
|
||||||
left: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROPORTION = {
|
const PROPORTION = {
|
||||||
// text size: proportion of the chart container sans trendline
|
// text size: proportion of the chart container sans trendline
|
||||||
KICKER: 0.1,
|
KICKER: 0.1,
|
||||||
@ -53,32 +42,6 @@ const PROPORTION = {
|
|||||||
TRENDLINE: 0.3,
|
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 = {
|
type BigNumberVisProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
width: number;
|
width: number;
|
||||||
@ -87,8 +50,6 @@ type BigNumberVisProps = {
|
|||||||
bigNumberFallback?: TimeSeriesDatum;
|
bigNumberFallback?: TimeSeriesDatum;
|
||||||
headerFormatter: NumberFormatter | TimeFormatter;
|
headerFormatter: NumberFormatter | TimeFormatter;
|
||||||
formatTime: TimeFormatter;
|
formatTime: TimeFormatter;
|
||||||
fromDatetime?: number;
|
|
||||||
toDatetime?: number;
|
|
||||||
headerFontSize: number;
|
headerFontSize: number;
|
||||||
kickerFontSize: number;
|
kickerFontSize: number;
|
||||||
subheader: string;
|
subheader: string;
|
||||||
@ -100,11 +61,10 @@ type BigNumberVisProps = {
|
|||||||
timestamp?: number;
|
timestamp?: number;
|
||||||
trendLineData?: TimeSeriesDatum[];
|
trendLineData?: TimeSeriesDatum[];
|
||||||
mainColor: string;
|
mainColor: string;
|
||||||
|
echartOptions: EChartsCoreOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BigNumberVis extends React.PureComponent<BigNumberVisProps, {}> {
|
class BigNumberVis extends React.PureComponent<BigNumberVisProps> {
|
||||||
private gradientId: string = shortid.generate();
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
className: '',
|
className: '',
|
||||||
headerFormatter: defaultNumberFormatter,
|
headerFormatter: defaultNumberFormatter,
|
||||||
@ -146,7 +106,7 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps, {}> {
|
|||||||
role="alert"
|
role="alert"
|
||||||
title={t(
|
title={t(
|
||||||
`Last available value seen on %s`,
|
`Last available value seen on %s`,
|
||||||
formatTime(bigNumberFallback.x),
|
formatTime(bigNumberFallback[0]),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{t('Not up to date')}
|
{t('Not up to date')}
|
||||||
@ -254,79 +214,19 @@ class BigNumberVis extends React.PureComponent<BigNumberVisProps, {}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTrendline(maxHeight: number) {
|
renderTrendline(maxHeight: number) {
|
||||||
const {
|
const { width, trendLineData, echartOptions } = this.props;
|
||||||
width,
|
|
||||||
trendLineData,
|
|
||||||
mainColor,
|
|
||||||
subheader,
|
|
||||||
startYAxisAtZero,
|
|
||||||
headerFormatter,
|
|
||||||
formatTime,
|
|
||||||
fromDatetime,
|
|
||||||
timeRangeFixed,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
// if can't find any non-null values, no point rendering the trendline
|
// 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;
|
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 (
|
return (
|
||||||
<XYChart
|
<Echart
|
||||||
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,
|
|
||||||
}}
|
|
||||||
width={Math.floor(width)}
|
width={Math.floor(width)}
|
||||||
height={maxHeight}
|
height={maxHeight}
|
||||||
margin={CHART_MARGIN}
|
echartOptions={echartOptions}
|
||||||
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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { t } from '@superset-ui/core';
|
import { smartDateFormatter, t } from '@superset-ui/core';
|
||||||
import {
|
import {
|
||||||
ControlPanelConfig,
|
ControlPanelConfig,
|
||||||
D3_FORMAT_DOCS,
|
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',
|
name: 'show_timestamp',
|
||||||
@ -142,6 +128,35 @@ const config: ControlPanelConfig = {
|
|||||||
['color_picker', null],
|
['color_picker', null],
|
||||||
[headerFontSize],
|
[headerFontSize],
|
||||||
[subheaderFontSize],
|
[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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||||
import controlPanel from './controlPanel';
|
import controlPanel from './controlPanel';
|
||||||
import transformProps, {
|
import transformProps from './transformProps';
|
||||||
BigNumberChartProps,
|
import buildQuery from './buildQuery';
|
||||||
BigNumberFormData,
|
|
||||||
} from './transformProps';
|
|
||||||
import example from './images/Big_Number_Trendline.jpg';
|
import example from './images/Big_Number_Trendline.jpg';
|
||||||
import thumbnail from './images/thumbnail.png';
|
import thumbnail from './images/thumbnail.png';
|
||||||
|
import {
|
||||||
|
BigNumberWithTrendlineChartProps,
|
||||||
|
BigNumberWithTrendlineFormData,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
const metadata = new ChartMetadata({
|
const metadata = new ChartMetadata({
|
||||||
category: t('KPI'),
|
category: t('KPI'),
|
||||||
@ -43,17 +45,17 @@ const metadata = new ChartMetadata({
|
|||||||
t('Trend'),
|
t('Trend'),
|
||||||
],
|
],
|
||||||
thumbnail,
|
thumbnail,
|
||||||
useLegacyApi: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class BigNumberChartPlugin extends ChartPlugin<
|
export default class BigNumberWithTrendlineChartPlugin extends ChartPlugin<
|
||||||
BigNumberFormData,
|
BigNumberWithTrendlineFormData,
|
||||||
BigNumberChartProps
|
BigNumberWithTrendlineChartProps
|
||||||
> {
|
> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
loadChart: () => import('./BigNumber'),
|
loadChart: () => import('../BigNumberViz'),
|
||||||
metadata,
|
metadata,
|
||||||
|
buildQuery,
|
||||||
transformProps,
|
transformProps,
|
||||||
controlPanel,
|
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.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { default as BigNumberChartPlugin } from './BigNumber/index';
|
export { default as BigNumberChartPlugin } from './BigNumberWithTrendline';
|
||||||
export { default as BigNumberTotalChartPlugin } from './BigNumberTotal/index';
|
export { default as BigNumberTotalChartPlugin } from './BigNumberTotal';
|
||||||
export { default as BigNumberChartPreset } from './preset';
|
|
@ -17,7 +17,7 @@
|
|||||||
* under the License.
|
* 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 { t } from '@superset-ui/core';
|
||||||
import { CustomControlItem } from '@superset-ui/chart-controls';
|
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
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { Preset } from '@superset-ui/core';
|
|
||||||
import BigNumberChartPlugin from './BigNumber';
|
|
||||||
import BigNumberTotalChartPlugin from './BigNumberTotal';
|
|
||||||
|
|
||||||
export default class BigNumberChartPreset extends Preset {
|
import moment from 'moment';
|
||||||
constructor() {
|
import {
|
||||||
super({
|
getTimeFormatter,
|
||||||
name: 'BigNumber charts',
|
getTimeFormatterForGranularity,
|
||||||
plugins: [
|
smartDateFormatter,
|
||||||
new BigNumberChartPlugin().configure({ key: 'big_number' }),
|
TimeGranularity,
|
||||||
new BigNumberTotalChartPlugin().configure({ key: 'big_number_total' }),
|
} 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 EchartsFunnelChartPlugin } from './Funnel';
|
||||||
export { default as EchartsTreeChartPlugin } from './Tree';
|
export { default as EchartsTreeChartPlugin } from './Tree';
|
||||||
export { default as EchartsTreemapChartPlugin } from './Treemap';
|
export { default as EchartsTreemapChartPlugin } from './Treemap';
|
||||||
|
export { BigNumberChartPlugin, BigNumberTotalChartPlugin } from './BigNumber';
|
||||||
|
|
||||||
export { default as BoxPlotTransformProps } from './BoxPlot/transformProps';
|
export { default as BoxPlotTransformProps } from './BoxPlot/transformProps';
|
||||||
export { default as FunnelTransformProps } from './Funnel/transformProps';
|
export { default as FunnelTransformProps } from './Funnel/transformProps';
|
||||||
|
@ -17,10 +17,11 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { DatasourceType, TimeGranularity } from '@superset-ui/core';
|
import { DatasourceType, TimeGranularity } from '@superset-ui/core';
|
||||||
import transformProps, {
|
import transformProps from '../../src/BigNumber/BigNumberWithTrendline/transformProps';
|
||||||
BignumberChartProps,
|
import {
|
||||||
BigNumberDatum,
|
BigNumberDatum,
|
||||||
} from '../src/BigNumber/transformProps';
|
BigNumberWithTrendlineChartProps,
|
||||||
|
} from '../../src/BigNumber/types';
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
metric: 'value',
|
metric: 'value',
|
||||||
@ -33,8 +34,9 @@ const formData = {
|
|||||||
compareLag: 1,
|
compareLag: 1,
|
||||||
timeGrainSqla: 'P3M' as TimeGranularity,
|
timeGrainSqla: 'P3M' as TimeGranularity,
|
||||||
compareSuffix: 'over last quarter',
|
compareSuffix: 'over last quarter',
|
||||||
vizType: 'big_number',
|
viz_type: 'big_number',
|
||||||
yAxisFormat: '.3s',
|
yAxisFormat: '.3s',
|
||||||
|
datasource: 'test_datasource',
|
||||||
};
|
};
|
||||||
|
|
||||||
const rawFormData = {
|
const rawFormData = {
|
||||||
@ -56,7 +58,7 @@ function generateProps(
|
|||||||
data: BigNumberDatum[],
|
data: BigNumberDatum[],
|
||||||
extraFormData = {},
|
extraFormData = {},
|
||||||
extraQueryData = {},
|
extraQueryData = {},
|
||||||
): BignumberChartProps {
|
): BigNumberWithTrendlineChartProps {
|
||||||
return {
|
return {
|
||||||
width: 200,
|
width: 200,
|
||||||
height: 500,
|
height: 500,
|
||||||
@ -84,10 +86,13 @@ function generateProps(
|
|||||||
...extraQueryData,
|
...extraQueryData,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
ownState: {},
|
||||||
|
filterState: {},
|
||||||
|
behaviors: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('BigNumber', () => {
|
describe('BigNumberWithTrendline', () => {
|
||||||
const props = generateProps(
|
const props = generateProps(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@ -109,8 +114,8 @@ describe('BigNumber', () => {
|
|||||||
const lastDatum = transformed.trendLineData?.pop();
|
const lastDatum = transformed.trendLineData?.pop();
|
||||||
|
|
||||||
// should use last available value
|
// should use last available value
|
||||||
expect(lastDatum?.x).toStrictEqual(100);
|
expect(lastDatum?.[0]).toStrictEqual(100);
|
||||||
expect(lastDatum?.y).toBeNull();
|
expect(lastDatum?.[1]).toBeNull();
|
||||||
|
|
||||||
// should note this is a fallback
|
// should note this is a fallback
|
||||||
expect(transformed.bigNumber).toStrictEqual(1.2345);
|
expect(transformed.bigNumber).toStrictEqual(1.2345);
|
@ -68,7 +68,7 @@ const basicChartProps = {
|
|||||||
const basicQueryResult: ChartDataResponseResult = {
|
const basicQueryResult: ChartDataResponseResult = {
|
||||||
annotation_data: null,
|
annotation_data: null,
|
||||||
cache_key: null,
|
cache_key: null,
|
||||||
cache_dttm: null,
|
cached_dttm: null,
|
||||||
cache_timeout: null,
|
cache_timeout: null,
|
||||||
data: [],
|
data: [],
|
||||||
colnames: [],
|
colnames: [],
|
||||||
|
@ -17,10 +17,6 @@
|
|||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { isFeatureEnabled, Preset, FeatureFlag } from '@superset-ui/core';
|
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 CalendarChartPlugin from '@superset-ui/legacy-plugin-chart-calendar';
|
||||||
import ChordChartPlugin from '@superset-ui/legacy-plugin-chart-chord';
|
import ChordChartPlugin from '@superset-ui/legacy-plugin-chart-chord';
|
||||||
import CountryMapChartPlugin from '@superset-ui/legacy-plugin-chart-country-map';
|
import CountryMapChartPlugin from '@superset-ui/legacy-plugin-chart-country-map';
|
||||||
@ -54,6 +50,8 @@ import {
|
|||||||
} from '@superset-ui/legacy-preset-chart-nvd3';
|
} from '@superset-ui/legacy-preset-chart-nvd3';
|
||||||
import { DeckGLChartPreset } from '@superset-ui/legacy-preset-chart-deckgl';
|
import { DeckGLChartPreset } from '@superset-ui/legacy-preset-chart-deckgl';
|
||||||
import {
|
import {
|
||||||
|
BigNumberChartPlugin,
|
||||||
|
BigNumberTotalChartPlugin,
|
||||||
EchartsPieChartPlugin,
|
EchartsPieChartPlugin,
|
||||||
EchartsBoxPlotChartPlugin,
|
EchartsBoxPlotChartPlugin,
|
||||||
EchartsAreaChartPlugin,
|
EchartsAreaChartPlugin,
|
||||||
|
@ -1240,12 +1240,22 @@ class ChartDataResponseResult(Schema):
|
|||||||
description="Amount of rows in result set", allow_none=False,
|
description="Amount of rows in result set", allow_none=False,
|
||||||
)
|
)
|
||||||
data = fields.List(fields.Dict(), description="A list with results")
|
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(
|
applied_filters = fields.List(
|
||||||
fields.Dict(), description="A list with applied filters"
|
fields.Dict(), description="A list with applied filters"
|
||||||
)
|
)
|
||||||
rejected_filters = fields.List(
|
rejected_filters = fields.List(
|
||||||
fields.Dict(), description="A list with rejected filters"
|
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):
|
class ChartDataResponseSchema(Schema):
|
||||||
|
@ -144,6 +144,8 @@ class QueryContextProcessor:
|
|||||||
"status": cache.status,
|
"status": cache.status,
|
||||||
"stacktrace": cache.stacktrace,
|
"stacktrace": cache.stacktrace,
|
||||||
"rowcount": len(cache.df.index),
|
"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]:
|
def query_cache_key(self, query_obj: QueryObject, **kwargs: Any) -> Optional[str]:
|
||||||
@ -201,6 +203,8 @@ class QueryContextProcessor:
|
|||||||
|
|
||||||
result.df = df
|
result.df = df
|
||||||
result.query = query
|
result.query = query
|
||||||
|
result.from_dttm = query_object.from_dttm
|
||||||
|
result.to_dttm = query_object.to_dttm
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def normalize_df(self, df: pd.DataFrame, query_object: QueryObject) -> pd.DataFrame:
|
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,
|
status: str = QueryStatus.SUCCESS,
|
||||||
error_message: Optional[str] = None,
|
error_message: Optional[str] = None,
|
||||||
errors: Optional[List[Dict[str, Any]]] = None,
|
errors: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
from_dttm: Optional[datetime] = None,
|
||||||
|
to_dttm: Optional[datetime] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.df = df
|
self.df = df
|
||||||
self.query = query
|
self.query = query
|
||||||
@ -454,6 +456,8 @@ class QueryResult: # pylint: disable=too-few-public-methods
|
|||||||
self.status = status
|
self.status = status
|
||||||
self.error_message = error_message
|
self.error_message = error_message
|
||||||
self.errors = errors or []
|
self.errors = errors or []
|
||||||
|
self.from_dttm = from_dttm
|
||||||
|
self.to_dttm = to_dttm
|
||||||
|
|
||||||
|
|
||||||
class ExtraJSONMixin:
|
class ExtraJSONMixin:
|
||||||
|