mirror of https://github.com/apache/superset.git
feat: Period over Period Big Number comparison chart (#26908)
Co-authored-by: Fernando <frodriguez@bytecode.io> Co-authored-by: Antonio Rivero <antonioriverocode@gmail.com>
This commit is contained in:
parent
bd9afcda99
commit
a09e5557bc
|
@ -31,6 +31,7 @@ These features are considered **unfinished** and should only be used on developm
|
||||||
- PRESTO_EXPAND_DATA
|
- PRESTO_EXPAND_DATA
|
||||||
- SHARE_QUERIES_VIA_KV_STORE
|
- SHARE_QUERIES_VIA_KV_STORE
|
||||||
- TAGGING_SYSTEM
|
- TAGGING_SYSTEM
|
||||||
|
- CHART_PLUGINS_EXPERIMENTAL
|
||||||
|
|
||||||
## In Testing
|
## In Testing
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
||||||
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
||||||
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
||||||
|
"@superset-ui/plugin-chart-period-over-period-kpi": "file:./plugins/plugin-chart-period-over-period-kpi",
|
||||||
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
||||||
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
||||||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||||
|
@ -19084,6 +19085,10 @@
|
||||||
"resolved": "plugins/plugin-chart-handlebars",
|
"resolved": "plugins/plugin-chart-handlebars",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@superset-ui/plugin-chart-period-over-period-kpi": {
|
||||||
|
"resolved": "plugins/plugin-chart-period-over-period-kpi",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@superset-ui/plugin-chart-pivot-table": {
|
"node_modules/@superset-ui/plugin-chart-pivot-table": {
|
||||||
"resolved": "plugins/plugin-chart-pivot-table",
|
"resolved": "plugins/plugin-chart-pivot-table",
|
||||||
"link": true
|
"link": true
|
||||||
|
@ -69566,6 +69571,30 @@
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"plugins/plugin-chart-period-over-period-kpi": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.30.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^26.0.4",
|
||||||
|
"jest": "^26.6.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@superset-ui/chart-controls": "*",
|
||||||
|
"@superset-ui/core": "*",
|
||||||
|
"react": "^16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins/plugin-chart-period-over-period-kpi/node_modules/moment": {
|
||||||
|
"version": "2.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"plugins/plugin-chart-pivot-table": {
|
"plugins/plugin-chart-pivot-table": {
|
||||||
"name": "@superset-ui/plugin-chart-pivot-table",
|
"name": "@superset-ui/plugin-chart-pivot-table",
|
||||||
"version": "0.18.25",
|
"version": "0.18.25",
|
||||||
|
@ -86107,6 +86136,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@superset-ui/plugin-chart-period-over-period-kpi": {
|
||||||
|
"version": "file:plugins/plugin-chart-period-over-period-kpi",
|
||||||
|
"requires": {
|
||||||
|
"@types/jest": "^26.0.4",
|
||||||
|
"jest": "^26.6.3",
|
||||||
|
"moment": "^2.30.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"moment": {
|
||||||
|
"version": "2.30.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
||||||
|
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@superset-ui/plugin-chart-pivot-table": {
|
"@superset-ui/plugin-chart-pivot-table": {
|
||||||
"version": "file:plugins/plugin-chart-pivot-table",
|
"version": "file:plugins/plugin-chart-pivot-table",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|
|
@ -111,6 +111,7 @@
|
||||||
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
||||||
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
"@superset-ui/plugin-chart-echarts": "file:./plugins/plugin-chart-echarts",
|
||||||
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
"@superset-ui/plugin-chart-handlebars": "file:./plugins/plugin-chart-handlebars",
|
||||||
|
"@superset-ui/plugin-chart-period-over-period-kpi": "file:./plugins/plugin-chart-period-over-period-kpi",
|
||||||
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
"@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table",
|
||||||
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
"@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table",
|
||||||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||||
|
|
|
@ -26,6 +26,7 @@ export enum FeatureFlag {
|
||||||
ALERT_REPORTS = 'ALERT_REPORTS',
|
ALERT_REPORTS = 'ALERT_REPORTS',
|
||||||
ALLOW_FULL_CSV_EXPORT = 'ALLOW_FULL_CSV_EXPORT',
|
ALLOW_FULL_CSV_EXPORT = 'ALLOW_FULL_CSV_EXPORT',
|
||||||
AVOID_COLORS_COLLISION = 'AVOID_COLORS_COLLISION',
|
AVOID_COLORS_COLLISION = 'AVOID_COLORS_COLLISION',
|
||||||
|
CHART_PLUGINS_EXPERIMENTAL = 'CHART_PLUGINS_EXPERIMENTAL',
|
||||||
CONFIRM_DASHBOARD_DIFF = 'CONFIRM_DASHBOARD_DIFF',
|
CONFIRM_DASHBOARD_DIFF = 'CONFIRM_DASHBOARD_DIFF',
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
DASHBOARD_CROSS_FILTERS = 'DASHBOARD_CROSS_FILTERS',
|
DASHBOARD_CROSS_FILTERS = 'DASHBOARD_CROSS_FILTERS',
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/\*\*
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
\*/
|
||||||
|
|
||||||
|
# custom-viz
|
||||||
|
|
||||||
|
This plugin provides a BigNumber visualization with period over period time comparisons
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
To build the plugin, run the following commands:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, to run the plugin in development mode (=rebuilding whenever changes are made), start the dev server with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
To add the package to Superset, go to the `superset-frontend` subdirectory in your Superset source folder (assuming both the `custom-viz` plugin and `superset` repos are in the same root directory) and run
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i -S ../../plugin-chart-period-over-period-kpi
|
||||||
|
```
|
||||||
|
|
||||||
|
If your Superset plugin exists in the `superset-frontend` directory and you wish to resolve TypeScript errors about `@superset-ui/core` not being resolved correctly, add the following to your `tsconfig.json` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../packages/superset-ui-chart-controls"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../packages/superset-ui-core"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
You may also wish to add the following to the `include` array in `tsconfig.json` to make Superset types available to your plugin:
|
||||||
|
|
||||||
|
```
|
||||||
|
"../../types/**/*"
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, if you wish to ensure your plugin `tsconfig.json` is aligned with the root Superset project, you may add the following to your `tsconfig.json` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
```
|
||||||
|
|
||||||
|
After this edit the `superset-frontend/src/visualizations/presets/MainPreset.js` and make the following changes:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { PopKPIPlugin } from '@superset-ui/plugin-chart-period-over-period-kpi';
|
||||||
|
```
|
||||||
|
|
||||||
|
to import the plugin and later add the following to the array that's passed to the `plugins` property:
|
||||||
|
|
||||||
|
```js
|
||||||
|
new PopKPIPlugin().configure({ key: 'pop_kpi' }),
|
||||||
|
```
|
||||||
|
|
||||||
|
After that the plugin should show up when you run Superset, e.g. the development server:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev-server
|
||||||
|
```
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "@superset-ui/plugin-chart-period-over-period-kpi",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Big Number with Time Period Comparison",
|
||||||
|
"sideEffects": false,
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"module": "esm/index.js",
|
||||||
|
"files": [
|
||||||
|
"esm",
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"private": true,
|
||||||
|
"keywords": [
|
||||||
|
"superset"
|
||||||
|
],
|
||||||
|
"author": "Bytecodeio",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.30.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@superset-ui/chart-controls": "*",
|
||||||
|
"@superset-ui/core": "*",
|
||||||
|
"react": "^16.13.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^26.0.4",
|
||||||
|
"jest": "^26.6.3"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* 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 React, { createRef } from 'react';
|
||||||
|
import { css, styled, useTheme } from '@superset-ui/core';
|
||||||
|
import { PopKPIComparisonValueStyleProps, PopKPIProps } from './types';
|
||||||
|
|
||||||
|
const ComparisonValue = styled.div<PopKPIComparisonValueStyleProps>`
|
||||||
|
${({ theme, subheaderFontSize }) => `
|
||||||
|
font-weight: ${theme.typography.weights.light};
|
||||||
|
width: 33%;
|
||||||
|
display: table-cell;
|
||||||
|
font-size: ${subheaderFontSize || 20}px;
|
||||||
|
text-align: center;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function PopKPI(props: PopKPIProps) {
|
||||||
|
const {
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
bigNumber,
|
||||||
|
prevNumber,
|
||||||
|
valueDifference,
|
||||||
|
percentDifference,
|
||||||
|
headerFontSize,
|
||||||
|
subheaderFontSize,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const rootElem = createRef<HTMLDivElement>();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const wrapperDivStyles = css`
|
||||||
|
font-family: ${theme.typography.families.sansSerif};
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: ${theme.gridUnit * 4}px;
|
||||||
|
border-radius: ${theme.gridUnit * 2}px;
|
||||||
|
height: ${height}px;
|
||||||
|
width: ${width}px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const bigValueContainerStyles = css`
|
||||||
|
font-size: ${headerFontSize || 60}px;
|
||||||
|
font-weight: ${theme.typography.weights.normal};
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={rootElem} css={wrapperDivStyles}>
|
||||||
|
<div css={bigValueContainerStyles}>{bigNumber}</div>
|
||||||
|
<div
|
||||||
|
css={css`
|
||||||
|
width: 100%;
|
||||||
|
display: table;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={css`
|
||||||
|
display: table-row;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<ComparisonValue subheaderFontSize={subheaderFontSize}>
|
||||||
|
{' '}
|
||||||
|
#: {prevNumber}
|
||||||
|
</ComparisonValue>
|
||||||
|
<ComparisonValue subheaderFontSize={subheaderFontSize}>
|
||||||
|
{' '}
|
||||||
|
Δ: {valueDifference}
|
||||||
|
</ComparisonValue>
|
||||||
|
<ComparisonValue subheaderFontSize={subheaderFontSize}>
|
||||||
|
{' '}
|
||||||
|
%: {percentDifference}
|
||||||
|
</ComparisonValue>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export { default as PopKPIPlugin } from './plugin';
|
||||||
|
/**
|
||||||
|
* Note: this file exports the default export from PopKPI.tsx.
|
||||||
|
* If you want to export multiple visualization modules, you will need to
|
||||||
|
* either add additional plugin folders (similar in structure to ./plugin)
|
||||||
|
* OR export multiple instances of `ChartPlugin` extensions in ./plugin/index.ts
|
||||||
|
* which in turn load exports from CustomViz.tsx
|
||||||
|
*/
|
|
@ -0,0 +1,299 @@
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
AdhocFilter,
|
||||||
|
buildQueryContext,
|
||||||
|
QueryFormData,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import moment, { Moment } from 'moment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buildQuery function is used to create an instance of QueryContext that's
|
||||||
|
* sent to the chart data endpoint. In addition to containing information of which
|
||||||
|
* datasource to use, it specifies the type (e.g. full payload, samples, query) and
|
||||||
|
* format (e.g. CSV or JSON) of the result and whether or not to force refresh the data from
|
||||||
|
* the datasource as opposed to using a cached copy of the data, if available.
|
||||||
|
*
|
||||||
|
* More importantly though, QueryContext contains a property `queries`, which is an array of
|
||||||
|
* QueryObjects specifying individual data requests to be made. A QueryObject specifies which
|
||||||
|
* columns, metrics and filters, among others, to use during the query. Usually it will be enough
|
||||||
|
* to specify just one query based on the baseQueryObject, but for some more advanced use cases
|
||||||
|
* it is possible to define post processing operations in the QueryObject, or multiple queries
|
||||||
|
* if a viz needs multiple different result sets.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type MomentTuple = [moment.Moment | null, moment.Moment | null];
|
||||||
|
|
||||||
|
function getSinceUntil(
|
||||||
|
timeRange: string | null = null,
|
||||||
|
relativeStart: string | null = null,
|
||||||
|
relativeEnd: string | null = null,
|
||||||
|
): MomentTuple {
|
||||||
|
const separator = ' : ';
|
||||||
|
const effectiveRelativeStart = relativeStart || 'today';
|
||||||
|
const effectiveRelativeEnd = relativeEnd || 'today';
|
||||||
|
|
||||||
|
if (!timeRange) {
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
let modTimeRange: string | null = timeRange;
|
||||||
|
|
||||||
|
if (timeRange === 'NO_TIME_RANGE' || timeRange === '_(NO_TIME_RANGE)') {
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeRange?.startsWith('last') && !timeRange.includes(separator)) {
|
||||||
|
modTimeRange = timeRange + separator + effectiveRelativeEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeRange?.startsWith('next') && !timeRange.includes(separator)) {
|
||||||
|
modTimeRange = effectiveRelativeStart + separator + timeRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
timeRange?.startsWith('previous calendar week') &&
|
||||||
|
!timeRange.includes(separator)
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
moment().subtract(1, 'week').startOf('week'),
|
||||||
|
moment().startOf('week'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
timeRange?.startsWith('previous calendar month') &&
|
||||||
|
!timeRange.includes(separator)
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
moment().subtract(1, 'month').startOf('month'),
|
||||||
|
moment().startOf('month'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
timeRange?.startsWith('previous calendar year') &&
|
||||||
|
!timeRange.includes(separator)
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
moment().subtract(1, 'year').startOf('year'),
|
||||||
|
moment().startOf('year'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeRangeLookup: Array<[RegExp, (...args: string[]) => Moment]> = [
|
||||||
|
[
|
||||||
|
/^last\s+(day|week|month|quarter|year)$/i,
|
||||||
|
(unit: string) =>
|
||||||
|
moment().subtract(1, unit as moment.unitOfTime.DurationConstructor),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
/^last\s+([0-9]+)\s+(second|minute|hour|day|week|month|year)s?$/i,
|
||||||
|
(delta: string, unit: string) =>
|
||||||
|
moment().subtract(delta, unit as moment.unitOfTime.DurationConstructor),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
/^next\s+([0-9]+)\s+(second|minute|hour|day|week|month|year)s?$/i,
|
||||||
|
(delta: string, unit: string) =>
|
||||||
|
moment().add(delta, unit as moment.unitOfTime.DurationConstructor),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
|
/DATEADD\(DATETIME\("([^"]+)"\),\s*(-?\d+),\s*([^\)]+)\)/i,
|
||||||
|
(timePart: string, delta: string, unit: string) => {
|
||||||
|
if (timePart === 'now') {
|
||||||
|
return moment().add(
|
||||||
|
delta,
|
||||||
|
unit as moment.unitOfTime.DurationConstructor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (moment(timePart.toUpperCase(), true).isValid()) {
|
||||||
|
return moment(timePart).add(
|
||||||
|
delta,
|
||||||
|
unit as moment.unitOfTime.DurationConstructor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return moment();
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
const sinceAndUntilPartition = modTimeRange
|
||||||
|
.split(separator, 2)
|
||||||
|
.map(part => part.trim());
|
||||||
|
|
||||||
|
const sinceAndUntil: (Moment | null)[] = sinceAndUntilPartition.map(part => {
|
||||||
|
if (!part) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let transformedValue: Moment | null = null;
|
||||||
|
// Matching time_range_lookup
|
||||||
|
const matched = timeRangeLookup.some(([pattern, fn]) => {
|
||||||
|
const result = part.match(pattern);
|
||||||
|
if (result) {
|
||||||
|
transformedValue = fn(...result.slice(1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part === 'today') {
|
||||||
|
transformedValue = moment().startOf('day');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part === 'now') {
|
||||||
|
transformedValue = moment();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matched && transformedValue !== null) {
|
||||||
|
// Handle the transformed value
|
||||||
|
} else {
|
||||||
|
// Handle the case when there was no match
|
||||||
|
transformedValue = moment(`${part}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformedValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
const [_since, _until] = sinceAndUntil;
|
||||||
|
|
||||||
|
if (_since && _until && _since.isAfter(_until)) {
|
||||||
|
throw new Error('From date cannot be larger than to date');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [_since, _until];
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculatePrev(
|
||||||
|
startDate: Moment | null,
|
||||||
|
endDate: Moment | null,
|
||||||
|
calcType: String,
|
||||||
|
) {
|
||||||
|
if (!startDate || !endDate) {
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
const daysBetween = endDate.diff(startDate, 'days');
|
||||||
|
|
||||||
|
let startDatePrev = moment();
|
||||||
|
let endDatePrev = moment();
|
||||||
|
if (calcType === 'y') {
|
||||||
|
startDatePrev = startDate.subtract(1, 'year');
|
||||||
|
endDatePrev = endDate.subtract(1, 'year');
|
||||||
|
} else if (calcType === 'w') {
|
||||||
|
startDatePrev = startDate.subtract(1, 'week');
|
||||||
|
endDatePrev = endDate.subtract(1, 'week');
|
||||||
|
} else if (calcType === 'm') {
|
||||||
|
startDatePrev = startDate.subtract(1, 'month');
|
||||||
|
endDatePrev = endDate.subtract(1, 'month');
|
||||||
|
} else if (calcType === 'r') {
|
||||||
|
startDatePrev = startDate.clone().subtract(daysBetween.valueOf(), 'day');
|
||||||
|
endDatePrev = startDate;
|
||||||
|
} else {
|
||||||
|
startDatePrev = startDate.subtract(1, 'year');
|
||||||
|
endDatePrev = endDate.subtract(1, 'year');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [startDatePrev, endDatePrev];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function buildQuery(formData: QueryFormData) {
|
||||||
|
const { cols: groupby, time_comparison: timeComparison } = formData;
|
||||||
|
|
||||||
|
const queryContextA = buildQueryContext(formData, baseQueryObject => [
|
||||||
|
{
|
||||||
|
...baseQueryObject,
|
||||||
|
groupby,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const timeFilterIndex: number =
|
||||||
|
formData.adhoc_filters?.findIndex(
|
||||||
|
filter => 'operator' in filter && filter.operator === 'TEMPORAL_RANGE',
|
||||||
|
) ?? -1;
|
||||||
|
|
||||||
|
const timeFilter: AdhocFilter | null =
|
||||||
|
timeFilterIndex !== -1 && formData.adhoc_filters
|
||||||
|
? formData.adhoc_filters[timeFilterIndex]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
let testSince = null;
|
||||||
|
let testUntil = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
timeFilter &&
|
||||||
|
'comparator' in timeFilter &&
|
||||||
|
typeof timeFilter.comparator === 'string'
|
||||||
|
) {
|
||||||
|
[testSince, testUntil] = getSinceUntil(
|
||||||
|
timeFilter.comparator.toLocaleLowerCase(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let formDataB: QueryFormData;
|
||||||
|
|
||||||
|
if (timeComparison !== 'c') {
|
||||||
|
const [prevStartDateMoment, prevEndDateMoment] = calculatePrev(
|
||||||
|
testSince,
|
||||||
|
testUntil,
|
||||||
|
timeComparison,
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryBComparator = `${prevStartDateMoment?.format(
|
||||||
|
'YYYY-MM-DDTHH:mm:ss',
|
||||||
|
)} : ${prevEndDateMoment?.format('YYYY-MM-DDTHH:mm:ss')}`;
|
||||||
|
|
||||||
|
const queryBFilter: any = {
|
||||||
|
...timeFilter,
|
||||||
|
comparator: queryBComparator.replace(/Z/g, ''),
|
||||||
|
};
|
||||||
|
|
||||||
|
const otherFilters = formData.adhoc_filters?.filter(
|
||||||
|
(_value: any, index: number) => timeFilterIndex !== index,
|
||||||
|
);
|
||||||
|
const queryBFilters = otherFilters
|
||||||
|
? [queryBFilter, ...otherFilters]
|
||||||
|
: [queryBFilter];
|
||||||
|
|
||||||
|
formDataB = {
|
||||||
|
...formData,
|
||||||
|
adhoc_filters: queryBFilters,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
formDataB = {
|
||||||
|
...formData,
|
||||||
|
adhoc_filters: formData.adhoc_custom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryContextB = buildQueryContext(formDataB, baseQueryObject => [
|
||||||
|
{
|
||||||
|
...baseQueryObject,
|
||||||
|
groupby,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...queryContextA,
|
||||||
|
queries: [...queryContextA.queries, ...queryContextB.queries],
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
/**
|
||||||
|
* 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 { t, validateNonEmpty } from '@superset-ui/core';
|
||||||
|
import {
|
||||||
|
ControlPanelConfig,
|
||||||
|
sharedControls,
|
||||||
|
} from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
|
const config: ControlPanelConfig = {
|
||||||
|
controlPanelSections: [
|
||||||
|
{
|
||||||
|
label: t('Query'),
|
||||||
|
expanded: true,
|
||||||
|
controlSetRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'metrics',
|
||||||
|
config: {
|
||||||
|
...sharedControls.metrics,
|
||||||
|
// it's possible to add validators to controls if
|
||||||
|
// certain selections/types need to be enforced
|
||||||
|
validators: [validateNonEmpty],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['adhoc_filters'],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'time_comparison',
|
||||||
|
config: {
|
||||||
|
type: 'SelectControl',
|
||||||
|
label: t('Range for Comparison'),
|
||||||
|
default: 'y',
|
||||||
|
choices: [
|
||||||
|
['y', 'Year'],
|
||||||
|
['w', 'Week'],
|
||||||
|
['m', 'Month'],
|
||||||
|
['r', 'Range'],
|
||||||
|
['c', 'Custom'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'row_limit',
|
||||||
|
config: sharedControls.row_limit,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Custom Time Range'),
|
||||||
|
expanded: true,
|
||||||
|
controlSetRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: `adhoc_custom`,
|
||||||
|
config: {
|
||||||
|
...sharedControls.adhoc_filters,
|
||||||
|
label: t('FILTERS (Custom)'),
|
||||||
|
description:
|
||||||
|
'This only applies when selecting the Range for Comparison Type- Custom',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Chart Options'),
|
||||||
|
expanded: true,
|
||||||
|
controlSetRows: [
|
||||||
|
['y_axis_format'],
|
||||||
|
['currency_format'],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'header_font_size',
|
||||||
|
config: {
|
||||||
|
type: 'SelectControl',
|
||||||
|
label: t('Big Number Font Size'),
|
||||||
|
renderTrigger: true,
|
||||||
|
clearable: false,
|
||||||
|
default: 60,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t('Tiny'),
|
||||||
|
value: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Small'),
|
||||||
|
value: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Normal'),
|
||||||
|
value: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Large'),
|
||||||
|
value: 48,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Huge'),
|
||||||
|
value: 60,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'subheader_font_size',
|
||||||
|
config: {
|
||||||
|
type: 'SelectControl',
|
||||||
|
label: t('Subheader Font Size'),
|
||||||
|
renderTrigger: true,
|
||||||
|
clearable: false,
|
||||||
|
default: 40,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: t('Tiny'),
|
||||||
|
value: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Small'),
|
||||||
|
value: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Normal'),
|
||||||
|
value: 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Large'),
|
||||||
|
value: 32,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Huge'),
|
||||||
|
value: 40,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
controlOverrides: {
|
||||||
|
y_axis_format: {
|
||||||
|
label: t('Number format'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* 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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||||
|
import buildQuery from './buildQuery';
|
||||||
|
import controlPanel from './controlPanel';
|
||||||
|
import transformProps from './transformProps';
|
||||||
|
import thumbnail from '../images/thumbnail.png';
|
||||||
|
|
||||||
|
export default class PopKPIPlugin extends ChartPlugin {
|
||||||
|
/**
|
||||||
|
* The constructor is used to pass relevant metadata and callbacks that get
|
||||||
|
* registered in respective registries that are used throughout the library
|
||||||
|
* and application. A more thorough description of each property is given in
|
||||||
|
* the respective imported file.
|
||||||
|
*
|
||||||
|
* It is worth noting that `buildQuery` and is optional, and only needed for
|
||||||
|
* advanced visualizations that require either post processing operations
|
||||||
|
* (pivoting, rolling aggregations, sorting etc) or submitting multiple queries.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
const metadata = new ChartMetadata({
|
||||||
|
description: 'KPI viz for comparing multiple period',
|
||||||
|
name: t('Big Number with Time Period Comparison'),
|
||||||
|
thumbnail,
|
||||||
|
});
|
||||||
|
|
||||||
|
super({
|
||||||
|
buildQuery,
|
||||||
|
controlPanel,
|
||||||
|
loadChart: () => import('../PopKPI'),
|
||||||
|
metadata,
|
||||||
|
transformProps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
/**
|
||||||
|
* 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 moment from 'moment';
|
||||||
|
import {
|
||||||
|
ChartProps,
|
||||||
|
getMetricLabel,
|
||||||
|
getValueFormatter,
|
||||||
|
NumberFormats,
|
||||||
|
getNumberFormatter,
|
||||||
|
} 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 0;
|
||||||
|
}
|
||||||
|
return metricValue ?? 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function transformProps(chartProps: ChartProps) {
|
||||||
|
/**
|
||||||
|
* This function is called after a successful response has been
|
||||||
|
* received from the chart data endpoint, and is used to transform
|
||||||
|
* the incoming data prior to being sent to the Visualization.
|
||||||
|
*
|
||||||
|
* The transformProps function is also quite useful to return
|
||||||
|
* additional/modified props to your data viz component. The formData
|
||||||
|
* can also be accessed from your CustomViz.tsx file, but
|
||||||
|
* doing supplying custom props here is often handy for integrating third
|
||||||
|
* party libraries that rely on specific props.
|
||||||
|
*
|
||||||
|
* A description of properties in `chartProps`:
|
||||||
|
* - `height`, `width`: the height/width of the DOM element in which
|
||||||
|
* the chart is located
|
||||||
|
* - `formData`: the chart data request payload that was sent to the
|
||||||
|
* backend.
|
||||||
|
* - `queriesData`: the chart data response payload that was received
|
||||||
|
* from the backend. Some notable properties of `queriesData`:
|
||||||
|
* - `data`: an array with data, each row with an object mapping
|
||||||
|
* the column/alias to its value. Example:
|
||||||
|
* `[{ col1: 'abc', metric1: 10 }, { col1: 'xyz', metric1: 20 }]`
|
||||||
|
* - `rowcount`: the number of rows in `data`
|
||||||
|
* - `query`: the query that was issued.
|
||||||
|
*
|
||||||
|
* Please note: the transformProps function gets cached when the
|
||||||
|
* application loads. When making changes to the `transformProps`
|
||||||
|
* function during development with hot reloading, changes won't
|
||||||
|
* be seen until restarting the development server.
|
||||||
|
*/
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
formData,
|
||||||
|
queriesData,
|
||||||
|
datasource: { currencyFormats = {}, columnFormats = {} },
|
||||||
|
} = chartProps;
|
||||||
|
const {
|
||||||
|
boldText,
|
||||||
|
headerFontSize,
|
||||||
|
headerText,
|
||||||
|
metrics,
|
||||||
|
yAxisFormat,
|
||||||
|
currencyFormat,
|
||||||
|
subheaderFontSize,
|
||||||
|
} = formData;
|
||||||
|
const { data: dataA = [] } = queriesData[0];
|
||||||
|
const { data: dataB = [] } = queriesData[1];
|
||||||
|
const data = dataA;
|
||||||
|
const metricName = getMetricLabel(metrics[0]);
|
||||||
|
let bigNumber: number | string =
|
||||||
|
data.length === 0 ? 0 : parseMetricValue(data[0][metricName]);
|
||||||
|
let prevNumber: number | string =
|
||||||
|
data.length === 0 ? 0 : parseMetricValue(dataB[0][metricName]);
|
||||||
|
|
||||||
|
const numberFormatter = getValueFormatter(
|
||||||
|
metrics[0],
|
||||||
|
currencyFormats,
|
||||||
|
columnFormats,
|
||||||
|
yAxisFormat,
|
||||||
|
currencyFormat,
|
||||||
|
);
|
||||||
|
|
||||||
|
const compTitles = {
|
||||||
|
r: 'Range' as string,
|
||||||
|
y: 'Year' as string,
|
||||||
|
m: 'Month' as string,
|
||||||
|
w: 'Week' as string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatPercentChange = getNumberFormatter(
|
||||||
|
NumberFormats.PERCENT_SIGNED_1_POINT,
|
||||||
|
);
|
||||||
|
|
||||||
|
let valueDifference: number | string = bigNumber - prevNumber;
|
||||||
|
|
||||||
|
const percentDifferenceNum = prevNumber
|
||||||
|
? (bigNumber - prevNumber) / Math.abs(prevNumber)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const compType = compTitles[formData.timeComparison];
|
||||||
|
bigNumber = numberFormatter(bigNumber);
|
||||||
|
prevNumber = numberFormatter(prevNumber);
|
||||||
|
valueDifference = numberFormatter(valueDifference);
|
||||||
|
const percentDifference: string = formatPercentChange(percentDifferenceNum);
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data,
|
||||||
|
// and now your control data, manipulated as needed, and passed through as props!
|
||||||
|
metrics,
|
||||||
|
metricName,
|
||||||
|
bigNumber,
|
||||||
|
prevNumber,
|
||||||
|
valueDifference,
|
||||||
|
percentDifference,
|
||||||
|
boldText,
|
||||||
|
headerFontSize,
|
||||||
|
subheaderFontSize,
|
||||||
|
headerText,
|
||||||
|
compType,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
QueryFormData,
|
||||||
|
supersetTheme,
|
||||||
|
TimeseriesDataRecord,
|
||||||
|
Metric,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
|
export interface PopKPIStylesProps {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
headerFontSize: keyof typeof supersetTheme.typography.sizes;
|
||||||
|
subheaderFontSize: keyof typeof supersetTheme.typography.sizes;
|
||||||
|
boldText: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PopKPICustomizeProps {
|
||||||
|
headerText: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PopKPIComparisonValueStyleProps {
|
||||||
|
subheaderFontSize?: keyof typeof supersetTheme.typography.sizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PopKPIQueryFormData = QueryFormData &
|
||||||
|
PopKPIStylesProps &
|
||||||
|
PopKPICustomizeProps;
|
||||||
|
|
||||||
|
export type PopKPIProps = PopKPIStylesProps &
|
||||||
|
PopKPICustomizeProps & {
|
||||||
|
data: TimeseriesDataRecord[];
|
||||||
|
metrics: Metric[];
|
||||||
|
metricName: String;
|
||||||
|
bigNumber: Number;
|
||||||
|
prevNumber: Number;
|
||||||
|
valueDifference: Number;
|
||||||
|
percentDifference: Number;
|
||||||
|
compType: String;
|
||||||
|
};
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
23
superset-frontend/plugins/plugin-chart-period-over-period-kpi/types/types/external.d.ts
vendored
Normal file
23
superset-frontend/plugins/plugin-chart-period-over-period-kpi/types/types/external.d.ts
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare module '*.png' {
|
||||||
|
const value: any;
|
||||||
|
export default value;
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
* 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 { isFeatureEnabled, FeatureFlag, Preset } from '@superset-ui/core';
|
||||||
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';
|
||||||
|
@ -76,10 +76,17 @@ import {
|
||||||
} from 'src/filters/components';
|
} from 'src/filters/components';
|
||||||
import { PivotTableChartPlugin as PivotTableChartPluginV2 } from '@superset-ui/plugin-chart-pivot-table';
|
import { PivotTableChartPlugin as PivotTableChartPluginV2 } from '@superset-ui/plugin-chart-pivot-table';
|
||||||
import { HandlebarsChartPlugin } from '@superset-ui/plugin-chart-handlebars';
|
import { HandlebarsChartPlugin } from '@superset-ui/plugin-chart-handlebars';
|
||||||
|
import { PopKPIPlugin } from '@superset-ui/plugin-chart-period-over-period-kpi';
|
||||||
import TimeTableChartPlugin from '../TimeTable';
|
import TimeTableChartPlugin from '../TimeTable';
|
||||||
|
|
||||||
export default class MainPreset extends Preset {
|
export default class MainPreset extends Preset {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
const experimentalPlugins = isFeatureEnabled(
|
||||||
|
FeatureFlag.CHART_PLUGINS_EXPERIMENTAL,
|
||||||
|
)
|
||||||
|
? [new PopKPIPlugin().configure({ key: 'pop_kpi' })]
|
||||||
|
: [];
|
||||||
|
|
||||||
super({
|
super({
|
||||||
name: 'Legacy charts',
|
name: 'Legacy charts',
|
||||||
presets: [new DeckGLChartPreset()],
|
presets: [new DeckGLChartPreset()],
|
||||||
|
@ -155,6 +162,7 @@ export default class MainPreset extends Preset {
|
||||||
new EchartsSunburstChartPlugin().configure({ key: 'sunburst_v2' }),
|
new EchartsSunburstChartPlugin().configure({ key: 'sunburst_v2' }),
|
||||||
new HandlebarsChartPlugin().configure({ key: 'handlebars' }),
|
new HandlebarsChartPlugin().configure({ key: 'handlebars' }),
|
||||||
new EchartsBubbleChartPlugin().configure({ key: 'bubble_v2' }),
|
new EchartsBubbleChartPlugin().configure({ key: 'bubble_v2' }),
|
||||||
|
...experimentalPlugins,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -485,6 +485,8 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = {
|
||||||
# Unlike Selenium, Playwright reports support deck.gl visualizations
|
# Unlike Selenium, Playwright reports support deck.gl visualizations
|
||||||
# Enabling this feature flag requires installing "playwright" pip package
|
# Enabling this feature flag requires installing "playwright" pip package
|
||||||
"PLAYWRIGHT_REPORTS_AND_THUMBNAILS": False,
|
"PLAYWRIGHT_REPORTS_AND_THUMBNAILS": False,
|
||||||
|
# Set to True to enable experimental chart plugins
|
||||||
|
"CHART_PLUGINS_EXPERIMENTAL": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
|
|
Loading…
Reference in New Issue