mirror of
https://github.com/apache/superset.git
synced 2024-09-18 11:39:49 -04:00
Revert "build: try to merge superset-ui-plugins"
This reverts commit 823126633ea0743254f78b0a74fe83c68ee6438f.
This commit is contained in:
parent
6f42844366
commit
4d0d05f71f
@ -26,7 +26,6 @@ lib/
|
|||||||
public/
|
public/
|
||||||
node_modules/
|
node_modules/
|
||||||
tmp/
|
tmp/
|
||||||
_gh-pages/
|
|
||||||
|
|
||||||
# Custom
|
# Custom
|
||||||
*.map
|
*.map
|
||||||
@ -44,11 +43,12 @@ jest.config.js
|
|||||||
prettier.config.js
|
prettier.config.js
|
||||||
tsconfig.eslint.json
|
tsconfig.eslint.json
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
!demo/tsconfig.json
|
|
||||||
tsconfig.options.json
|
tsconfig.options.json
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
webpack.config.js
|
webpack.config.js
|
||||||
|
|
||||||
# Ignore npm lock files, always use yarn.loock instead
|
# Lock files, libs should not have lock files
|
||||||
npm-shrinkwrap.json
|
npm-shrinkwrap.json
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
# disable to provide more stability for the ci builds
|
||||||
|
# yarn.lock
|
||||||
|
@ -5,6 +5,14 @@ You can demo your changes by running the storybook demo locally with the followi
|
|||||||
```sh
|
```sh
|
||||||
yarn install
|
yarn install
|
||||||
yarn build
|
yarn build
|
||||||
|
cd packages/superset-ui-demo
|
||||||
|
yarn storybook:run
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can demo your changes by using the following command while in
|
||||||
|
`packages/superset-ui-demo`:
|
||||||
|
|
||||||
|
```sh
|
||||||
yarn storybook
|
yarn storybook
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
"lerna": "3.2.1",
|
"lerna": "3.2.1",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*"
|
||||||
"plugins/*"
|
|
||||||
],
|
],
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"version": "0.12.12"
|
"version": "0.12.12"
|
||||||
|
@ -9,10 +9,7 @@
|
|||||||
"babel:cjs": "nimbus babel --clean --workspaces=\"@superset-ui/!(demo|generator-superset)\"",
|
"babel:cjs": "nimbus babel --clean --workspaces=\"@superset-ui/!(demo|generator-superset)\"",
|
||||||
"babel:esm": "nimbus babel --clean --workspaces=\"@superset-ui/!(demo|generator-superset)\" --esm",
|
"babel:esm": "nimbus babel --clean --workspaces=\"@superset-ui/!(demo|generator-superset)\" --esm",
|
||||||
"build:assets": "node ./scripts/buildAssets.js",
|
"build:assets": "node ./scripts/buildAssets.js",
|
||||||
"demo": "cd packages/demo && yarn demo:build",
|
"clean": "rm -rf ./packages/**/{lib,esm}",
|
||||||
"storybook": "cd packages/demo && yarn storybook",
|
|
||||||
"sb": "yarn storybook",
|
|
||||||
"clean": "rm -rf ./{packages,plugins}/**/{lib,esm}",
|
|
||||||
"commit": "superset-commit",
|
"commit": "superset-commit",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 10",
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 10",
|
||||||
"format": "yarn prettier --write",
|
"format": "yarn prettier --write",
|
||||||
@ -45,12 +42,12 @@
|
|||||||
],
|
],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@airbnb/config-babel": "^3.1.0",
|
"@airbnb/config-babel": "^2.1.3",
|
||||||
"@airbnb/config-eslint": "^3.1.0",
|
"@airbnb/config-eslint": "^2.1.3",
|
||||||
"@airbnb/config-jest": "^3.0.1",
|
"@airbnb/config-jest": "^2.1.3",
|
||||||
"@airbnb/config-prettier": "^3.1.0",
|
"@airbnb/config-prettier": "^2.0.4",
|
||||||
"@airbnb/config-typescript": "^3.0.1",
|
"@airbnb/config-typescript": "^2.1.2",
|
||||||
"@airbnb/nimbus": "^3.1.1",
|
"@airbnb/nimbus": "^2.1.3",
|
||||||
"@superset-ui/commit-config": "^0.0.9",
|
"@superset-ui/commit-config": "^0.0.9",
|
||||||
"@types/enzyme": "^3.10.3",
|
"@types/enzyme": "^3.10.3",
|
||||||
"@types/jest": "^25.1.1",
|
"@types/jest": "^25.1.1",
|
||||||
@ -62,13 +59,12 @@
|
|||||||
"fast-glob": "^3.0.1",
|
"fast-glob": "^3.0.1",
|
||||||
"fs-extra": "^8.0.1",
|
"fs-extra": "^8.0.1",
|
||||||
"husky": "^4.2.1",
|
"husky": "^4.2.1",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
|
||||||
"jest-mock-console": "^1.0.0",
|
"jest-mock-console": "^1.0.0",
|
||||||
"lerna": "^3.15.0",
|
"lerna": "^3.15.0",
|
||||||
"lint-staged": "^10.0.3",
|
"lint-staged": "^10.0.3",
|
||||||
"react": "^16.9.0",
|
"react-test-renderer": "^16.9.0",
|
||||||
"react-dom": "^16.9.0",
|
"react-dom": "^16.9.0",
|
||||||
"react-test-renderer": "^16.9.0"
|
"react": "^16.9.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.10.0",
|
"node": ">=10.10.0",
|
||||||
@ -76,8 +72,7 @@
|
|||||||
"yarn": ">=1.13.0"
|
"yarn": ">=1.13.0"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./packages/*",
|
"./packages/*"
|
||||||
"./plugins/*"
|
|
||||||
],
|
],
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 3 chrome versions",
|
"last 3 chrome versions",
|
||||||
@ -105,10 +100,6 @@
|
|||||||
"globals": {
|
"globals": {
|
||||||
"caches": true
|
"caches": true
|
||||||
},
|
},
|
||||||
"moduleNameMapper": {
|
|
||||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
|
|
||||||
"\\.(css|less)$": "identity-obj-proxy"
|
|
||||||
},
|
|
||||||
"timers": "real",
|
"timers": "real",
|
||||||
"setupFilesAfterEnv": [
|
"setupFilesAfterEnv": [
|
||||||
"@airbnb/config-jest/enzyme"
|
"@airbnb/config-jest/enzyme"
|
||||||
@ -128,13 +119,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"prettier": {
|
|
||||||
"arrowParens": "avoid"
|
|
||||||
},
|
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"rules": {
|
|
||||||
"arrow-parens": ["warn", "as-needed"]
|
|
||||||
},
|
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": "./packages/generator-superset/**/*.test.{js,jsx,ts,tsx}",
|
"files": "./packages/generator-superset/**/*.test.{js,jsx,ts,tsx}",
|
||||||
@ -156,27 +141,14 @@
|
|||||||
"files": "*.{js,jsx,ts,tsx}",
|
"files": "*.{js,jsx,ts,tsx}",
|
||||||
"rules": {
|
"rules": {
|
||||||
"react/jsx-no-literals": "off",
|
"react/jsx-no-literals": "off",
|
||||||
"@typescript-eslint/no-explicit-any": [
|
"@typescript-eslint/no-explicit-any": ["warn", { "fixToUnknown": false }]
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"fixToUnknown": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"files": "./scripts/*",
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true
|
||||||
"composite": true,
|
|
||||||
"resolveJsonModule": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -187,7 +159,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"./{packages,plugins}/*/{src,test,storybook}/**/*.{js,jsx,ts,tsx,json,md}": [
|
"./packages/*/{src,test,storybook}/**/*.{js,jsx,ts,tsx,json,md}": [
|
||||||
"yarn prettier --write",
|
"yarn prettier --write",
|
||||||
"git add"
|
"git add"
|
||||||
]
|
]
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const { lstatSync, readdirSync } = require('fs');
|
|
||||||
|
|
||||||
// find @superset-ui packages
|
|
||||||
const basePath = path.resolve(__dirname, '../../../node_modules/@superset-ui');
|
|
||||||
const packages = readdirSync(basePath).filter(name => {
|
|
||||||
const stat = lstatSync(path.join(basePath, name));
|
|
||||||
return stat.isSymbolicLink();
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
addons: [
|
|
||||||
'@storybook/preset-typescript',
|
|
||||||
'@storybook/addon-actions/register',
|
|
||||||
'@storybook/addon-knobs/register',
|
|
||||||
'storybook-addon-jsx/register',
|
|
||||||
'@storybook/addon-links/register',
|
|
||||||
],
|
|
||||||
stories: [
|
|
||||||
'../storybook/stories/**/*Stories.[tj]sx',
|
|
||||||
],
|
|
||||||
webpackFinal: config => {
|
|
||||||
config.module.rules.push({
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: require.resolve('ts-loader'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
config.resolve.extensions.push('.ts', '.tsx');
|
|
||||||
// let webpack know where to find the source code
|
|
||||||
Object.assign(config.resolve.alias, {
|
|
||||||
...packages.reduce(
|
|
||||||
(acc, name) => ({
|
|
||||||
...acc,
|
|
||||||
[`@superset-ui/${name}$`]: path.join(basePath, name, 'src'),
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
config.stats = 'errors-warnings';
|
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
html,
|
|
||||||
body,
|
|
||||||
#root {
|
|
||||||
height: 100%;
|
|
||||||
font-family: BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;
|
|
||||||
font-weight: 200;
|
|
||||||
color: #484848;
|
|
||||||
}
|
|
||||||
#root > div {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root > div.superset-body {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 16px;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
#root > div.superset-body .panel {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background: none;
|
|
||||||
}
|
|
@ -1,114 +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 React from 'react';
|
|
||||||
import { SuperChart } from '@superset-ui/chart';
|
|
||||||
import { BigNumberChartPlugin } from '@superset-ui/legacy-preset-chart-big-number';
|
|
||||||
import testData from './data';
|
|
||||||
|
|
||||||
new BigNumberChartPlugin().configure({ key: 'big-number' }).register();
|
|
||||||
|
|
||||||
const TIME_COLUMN = '__timestamp';
|
|
||||||
|
|
||||||
const formData = {
|
|
||||||
colorPicker: {
|
|
||||||
r: 0,
|
|
||||||
g: 122,
|
|
||||||
b: 135,
|
|
||||||
a: 1,
|
|
||||||
},
|
|
||||||
compareLag: 1,
|
|
||||||
compareSuffix: 'over 10Y',
|
|
||||||
metric: 'sum__SP_POP_TOTL',
|
|
||||||
showTrendLine: true,
|
|
||||||
startYAxisAtZero: true,
|
|
||||||
vizType: 'big_number',
|
|
||||||
yAxisFormat: '.3s',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add null values to trendline data
|
|
||||||
* @param data input data
|
|
||||||
*/
|
|
||||||
function withNulls(origData: object[], nullPosition: number = 3) {
|
|
||||||
const data = [...origData];
|
|
||||||
data[nullPosition] = {
|
|
||||||
...data[nullPosition],
|
|
||||||
sum__SP_POP_TOTL: null,
|
|
||||||
};
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Legacy Preset|big-number',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const basicWithTrendline = () => (
|
|
||||||
<SuperChart
|
|
||||||
chartType="big-number"
|
|
||||||
width={400}
|
|
||||||
height={400}
|
|
||||||
queryData={{ data: testData }}
|
|
||||||
formData={formData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const nullInTheMiddle = () => (
|
|
||||||
<SuperChart
|
|
||||||
chartType="big-number"
|
|
||||||
width={400}
|
|
||||||
height={400}
|
|
||||||
queryData={{ data: withNulls(testData, 3) }}
|
|
||||||
formData={formData}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const fixedRange = () => (
|
|
||||||
<SuperChart
|
|
||||||
chartType="big-number"
|
|
||||||
width={400}
|
|
||||||
height={400}
|
|
||||||
queryData={{
|
|
||||||
data: testData.slice(0, 9),
|
|
||||||
from_dttm: testData[testData.length - 1][TIME_COLUMN],
|
|
||||||
to_dttm: null,
|
|
||||||
}}
|
|
||||||
formData={{
|
|
||||||
...formData,
|
|
||||||
timeGrainSqla: 'P1Y',
|
|
||||||
timeRangeFixed: true,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const noFixedRange = () => (
|
|
||||||
<SuperChart
|
|
||||||
chartType="big-number"
|
|
||||||
width={400}
|
|
||||||
height={400}
|
|
||||||
queryData={{
|
|
||||||
data: testData.slice(0, 9),
|
|
||||||
from_dttm: testData[testData.length - 1][TIME_COLUMN],
|
|
||||||
to_dttm: testData[0][TIME_COLUMN],
|
|
||||||
}}
|
|
||||||
formData={{
|
|
||||||
...formData,
|
|
||||||
timeRangeFixed: false,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
@ -1,59 +0,0 @@
|
|||||||
/* eslint-disable sort-keys */
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
__timestamp: 1388534400000.0,
|
|
||||||
sum__SP_POP_TOTL: 7237260256.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1356998400000.0,
|
|
||||||
sum__SP_POP_TOTL: 7151135481.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1325376000000.0,
|
|
||||||
sum__SP_POP_TOTL: 7066007165.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1293840000000.0,
|
|
||||||
sum__SP_POP_TOTL: 6984252419.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1262304000000.0,
|
|
||||||
sum__SP_POP_TOTL: 6901110512.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1230768000000.0,
|
|
||||||
sum__SP_POP_TOTL: 6818457192.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1199145600000.0,
|
|
||||||
sum__SP_POP_TOTL: 6735914031.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1167609600000.0,
|
|
||||||
sum__SP_POP_TOTL: 6653571302.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1136073600000.0,
|
|
||||||
sum__SP_POP_TOTL: 6572596462.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1104537600000.0,
|
|
||||||
sum__SP_POP_TOTL: 6491857539.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1072915200000.0,
|
|
||||||
sum__SP_POP_TOTL: 6411615629.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1041379200000.0,
|
|
||||||
sum__SP_POP_TOTL: 6331766837.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 1009843200000.0,
|
|
||||||
sum__SP_POP_TOTL: 6252469127.0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 978307200000.0,
|
|
||||||
sum__SP_POP_TOTL: 617333941.0,
|
|
||||||
},
|
|
||||||
];
|
|
@ -1,58 +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 React from 'react';
|
|
||||||
import { SuperChart } from '@superset-ui/chart';
|
|
||||||
import { BigNumberTotalChartPlugin } from '@superset-ui/legacy-preset-chart-big-number';
|
|
||||||
import data from './data';
|
|
||||||
|
|
||||||
new BigNumberTotalChartPlugin().configure({ key: 'big-number-total' }).register();
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Legacy Preset|big-number',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const totalBasic = () => (
|
|
||||||
<SuperChart
|
|
||||||
chartType="big-number-total"
|
|
||||||
width={400}
|
|
||||||
height={400}
|
|
||||||
queryData={{ data }}
|
|
||||||
formData={{
|
|
||||||
metric: 'sum__num',
|
|
||||||
subheader: 'total female participants',
|
|
||||||
vizType: 'big_number_total',
|
|
||||||
yAxisFormat: '.3s',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const totalNoData = () => (
|
|
||||||
<SuperChart
|
|
||||||
chartType="big-number-total"
|
|
||||||
width={400}
|
|
||||||
height={400}
|
|
||||||
queryData={{ data: [] }}
|
|
||||||
formData={{
|
|
||||||
metric: 'sum__num',
|
|
||||||
subheader: 'total female participants',
|
|
||||||
vizType: 'big_number_total',
|
|
||||||
yAxisFormat: '.3s',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
@ -1,5 +0,0 @@
|
|||||||
export default [
|
|
||||||
{
|
|
||||||
sum__num: 32546308,
|
|
||||||
},
|
|
||||||
];
|
|
@ -1,155 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { SuperChart } from '@superset-ui/chart';
|
|
||||||
import { Props as SuperChartProps } from '@superset-ui/chart/src/components/SuperChart';
|
|
||||||
import TableChartPlugin from '@superset-ui/legacy-plugin-chart-table';
|
|
||||||
import data, { birthNames } from './data';
|
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
||||||
|
|
||||||
new TableChartPlugin().configure({ key: 'table' }).register();
|
|
||||||
|
|
||||||
function paginated(props_: SuperChartProps, pageSize = 50) {
|
|
||||||
const props = { ...props_ };
|
|
||||||
if (props.formData) {
|
|
||||||
props.formData = {
|
|
||||||
...props.formData,
|
|
||||||
page_length: pageSize,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (props.queryData?.form_data) {
|
|
||||||
props.queryData.form_data = {
|
|
||||||
...props.queryData.form_data,
|
|
||||||
page_length: pageSize,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...props,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustNumCols(props: SuperChartProps, numCols = 7) {
|
|
||||||
const newProps = { ...props };
|
|
||||||
if (props.queryData) {
|
|
||||||
const { columns } = props.queryData.data;
|
|
||||||
const curSize = columns.length;
|
|
||||||
const newColumns = [...new Array(numCols)].map((_, i) => {
|
|
||||||
return columns[i % curSize];
|
|
||||||
});
|
|
||||||
newProps.queryData = {
|
|
||||||
...props.queryData,
|
|
||||||
data: {
|
|
||||||
...props.queryData.data,
|
|
||||||
columns: newColumns,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return newProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load sample data for testing
|
|
||||||
* @param props the original props passed to SuperChart
|
|
||||||
* @param pageSize number of records perpage
|
|
||||||
* @param targetSize the target total number of records
|
|
||||||
*/
|
|
||||||
function loadData(props: SuperChartProps, pageSize = 50, targetSize = 2042) {
|
|
||||||
if (!props.queryData) return props;
|
|
||||||
const data = props.queryData && props.queryData.data;
|
|
||||||
if (data.records.length > 0) {
|
|
||||||
while (data.records.length < targetSize) {
|
|
||||||
const records = data.records;
|
|
||||||
data.records = records.concat(records).slice(0, targetSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
props.height = window.innerHeight - 130;
|
|
||||||
return paginated(props, pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Legacy Plugin|table',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const basic = () => (
|
|
||||||
<SuperChart
|
|
||||||
chartType="table"
|
|
||||||
width={400}
|
|
||||||
height={400}
|
|
||||||
datasource={{
|
|
||||||
columnFormats: {},
|
|
||||||
verboseMap: {
|
|
||||||
name: 'name',
|
|
||||||
sum__num: 'sum__num',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
queryData={{ data }}
|
|
||||||
formData={{
|
|
||||||
alignPn: false,
|
|
||||||
colorPn: false,
|
|
||||||
includeSearch: false,
|
|
||||||
metrics: ['sum__num'],
|
|
||||||
orderDesc: true,
|
|
||||||
pageLength: 0,
|
|
||||||
percentMetrics: null,
|
|
||||||
tableFilter: false,
|
|
||||||
tableTimestampFormat: '%Y-%m-%d %H:%M:%S',
|
|
||||||
timeseriesLimitMetric: null,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const bigTable = () => {
|
|
||||||
const initialProps = loadData(birthNames);
|
|
||||||
const [chartProps, setChartProps] = useState(initialProps);
|
|
||||||
|
|
||||||
const updatePageSize = (size: number) => {
|
|
||||||
setChartProps(paginated(initialProps, size));
|
|
||||||
};
|
|
||||||
const updateNumCols = (numCols: number) => {
|
|
||||||
setChartProps(adjustNumCols(initialProps, numCols));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="superset-body">
|
|
||||||
<div className="panel">
|
|
||||||
<div className="panel-heading form-inline">
|
|
||||||
<div className="form-group">
|
|
||||||
Initial page size:{' '}
|
|
||||||
<div className="btn-group btn-group-sm">
|
|
||||||
{[10, 25, 40, 50, 100, -1].map((pageSize) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={pageSize}
|
|
||||||
type="button"
|
|
||||||
className="btn btn-default"
|
|
||||||
onClick={() => updatePageSize(pageSize)}
|
|
||||||
>
|
|
||||||
{pageSize > 0 ? pageSize : 'All'}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="form-group" style={{ marginLeft: 20 }}>
|
|
||||||
Number of columns:{' '}
|
|
||||||
<div className="btn-group btn-group-sm">
|
|
||||||
{[1, 3, 5, 7, 9].map((numCols) => {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={numCols}
|
|
||||||
type="button"
|
|
||||||
className="btn btn-default"
|
|
||||||
onClick={() => updateNumCols(numCols)}
|
|
||||||
>
|
|
||||||
{numCols}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="panel-body">
|
|
||||||
<SuperChart {...chartProps} chartType="table" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
File diff suppressed because it is too large
Load Diff
@ -1,48 +0,0 @@
|
|||||||
/* eslint-disable sort-keys */
|
|
||||||
export { default as birthNames } from './birthNames.json';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
columns: ['name', 'sum__num'],
|
|
||||||
records: [
|
|
||||||
{
|
|
||||||
name: 'Michael',
|
|
||||||
sum__num: 2467063,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Christopher',
|
|
||||||
sum__num: 1725265,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'David',
|
|
||||||
sum__num: 1570516,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'James',
|
|
||||||
sum__num: 1506025,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'John',
|
|
||||||
sum__num: 1426074,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Matthew',
|
|
||||||
sum__num: 1355803,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Robert',
|
|
||||||
sum__num: 1314800,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Daniel',
|
|
||||||
sum__num: 1159354,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Joseph',
|
|
||||||
sum__num: 1114098,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'William',
|
|
||||||
sum__num: 1113701,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
@ -1,98 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { text, select } from '@storybook/addon-knobs';
|
|
||||||
|
|
||||||
import { SuperChart, ChartDataProvider } from '@superset-ui/chart';
|
|
||||||
import { SupersetClient } from '@superset-ui/connection';
|
|
||||||
import { BigNumberChartPlugin as LegacyBigNumberPlugin } from '@superset-ui/legacy-preset-chart-big-number';
|
|
||||||
import LegacySankeyPlugin from '@superset-ui/legacy-plugin-chart-sankey';
|
|
||||||
import LegacySunburstPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
|
||||||
import LegacyWordCloudPlugin from '@superset-ui/legacy-plugin-chart-word-cloud';
|
|
||||||
import WordCloudPlugin from '@superset-ui/plugin-chart-word-cloud';
|
|
||||||
|
|
||||||
import {
|
|
||||||
bigNumberFormData,
|
|
||||||
sankeyFormData,
|
|
||||||
sunburstFormData,
|
|
||||||
wordCloudFormData,
|
|
||||||
} from '@superset-ui/chart/test/fixtures/formData';
|
|
||||||
|
|
||||||
import Expandable from '../../shared/components/Expandable';
|
|
||||||
import VerifyCORS, { renderError } from '../../shared/components/VerifyCORS';
|
|
||||||
|
|
||||||
const BIG_NUMBER = bigNumberFormData.viz_type;
|
|
||||||
const SANKEY = sankeyFormData.viz_type;
|
|
||||||
const SUNBURST = sunburstFormData.viz_type;
|
|
||||||
const WORD_CLOUD_LEGACY = wordCloudFormData.viz_type;
|
|
||||||
const WORD_CLOUD = 'new_word_cloud';
|
|
||||||
|
|
||||||
new LegacyBigNumberPlugin().configure({ key: BIG_NUMBER }).register();
|
|
||||||
// @ts-ignore
|
|
||||||
new LegacySankeyPlugin().configure({ key: SANKEY }).register();
|
|
||||||
// @ts-ignore
|
|
||||||
new LegacySunburstPlugin().configure({ key: SUNBURST }).register();
|
|
||||||
// @ts-ignore
|
|
||||||
new LegacyWordCloudPlugin().configure({ key: WORD_CLOUD_LEGACY }).register();
|
|
||||||
// @ts-ignore
|
|
||||||
new WordCloudPlugin().configure({ key: WORD_CLOUD }).register();
|
|
||||||
|
|
||||||
const VIS_TYPES = [BIG_NUMBER, SANKEY, SUNBURST, WORD_CLOUD, WORD_CLOUD_LEGACY];
|
|
||||||
const FORM_DATA_LOOKUP = {
|
|
||||||
[BIG_NUMBER]: bigNumberFormData,
|
|
||||||
[SANKEY]: sankeyFormData,
|
|
||||||
[SUNBURST]: sunburstFormData,
|
|
||||||
[WORD_CLOUD]: { ...wordCloudFormData, viz_type: WORD_CLOUD },
|
|
||||||
[WORD_CLOUD_LEGACY]: wordCloudFormData,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Core Packages|@superset-ui/chart',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dataProvider = () => {
|
|
||||||
const host = text('Set Superset App host for CORS request', 'localhost:9000');
|
|
||||||
const visType = select('Chart Plugin Type', VIS_TYPES, VIS_TYPES[0]);
|
|
||||||
const formData = text('Override formData', JSON.stringify(FORM_DATA_LOOKUP[visType]));
|
|
||||||
const width = text('Vis width', '500');
|
|
||||||
const height = text('Vis height', '300');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ margin: 16 }}>
|
|
||||||
<VerifyCORS host={host}>
|
|
||||||
{() => (
|
|
||||||
<ChartDataProvider client={SupersetClient} formData={JSON.parse(formData)}>
|
|
||||||
{({ loading, payload, error }) => {
|
|
||||||
if (loading) return <div>Loading!</div>;
|
|
||||||
|
|
||||||
if (error) return renderError(error);
|
|
||||||
|
|
||||||
if (payload)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SuperChart
|
|
||||||
chartType={visType}
|
|
||||||
formData={payload.formData}
|
|
||||||
height={Number(height)}
|
|
||||||
// @TODO fix typing
|
|
||||||
// all vis's now expect objects but api/v1/ returns an array
|
|
||||||
queryData={
|
|
||||||
Array.isArray(payload.queryData) ? payload.queryData[0] : payload.queryData
|
|
||||||
}
|
|
||||||
width={Number(width)}
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<Expandable expandableWhat="payload">
|
|
||||||
<pre style={{ fontSize: 11 }}>{JSON.stringify(payload, null, 2)}</pre>
|
|
||||||
</Expandable>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}}
|
|
||||||
</ChartDataProvider>
|
|
||||||
)}
|
|
||||||
</VerifyCORS>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
dataProvider.story = { name: 'ChartDataProvider' };
|
|
@ -1,143 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { text, withKnobs } from '@storybook/addon-knobs';
|
|
||||||
import { SuperChart } from '@superset-ui/chart';
|
|
||||||
import {
|
|
||||||
DiligentChartPlugin,
|
|
||||||
BuggyChartPlugin,
|
|
||||||
ChartKeys,
|
|
||||||
} from '@superset-ui/chart/test/components/MockChartPlugins';
|
|
||||||
|
|
||||||
new DiligentChartPlugin().configure({ key: ChartKeys.DILIGENT }).register();
|
|
||||||
new BuggyChartPlugin().configure({ key: ChartKeys.BUGGY }).register();
|
|
||||||
|
|
||||||
const DEFAULT_QUERY_DATA = { data: ['foo', 'bar'] };
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Core Packages|@superset-ui/chart',
|
|
||||||
decorators: [withKnobs],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const basic = () => {
|
|
||||||
const width = text('Vis width', '100%');
|
|
||||||
const height = text('Vis height', '100%');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SuperChart
|
|
||||||
chartType={ChartKeys.DILIGENT}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
queryData={DEFAULT_QUERY_DATA}
|
|
||||||
formData={{ hi: 1 }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export const container50pct = () => {
|
|
||||||
const width = text('Vis width', '50%');
|
|
||||||
const height = text('Vis height', '50%');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SuperChart
|
|
||||||
chartType={ChartKeys.DILIGENT}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
queryData={DEFAULT_QUERY_DATA}
|
|
||||||
formData={{ hi: 1 }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
container50pct.story = { name: '50% of container' };
|
|
||||||
|
|
||||||
export const fixedDimension = () => {
|
|
||||||
const width = text('Vis width', '500');
|
|
||||||
const height = text('Vis height', '300');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SuperChart
|
|
||||||
chartType={ChartKeys.DILIGENT}
|
|
||||||
height={height}
|
|
||||||
width={width}
|
|
||||||
queryData={DEFAULT_QUERY_DATA}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fixedWidth100height = () => {
|
|
||||||
const width = text('Vis width', '500');
|
|
||||||
const height = text('Vis height', '100%');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SuperChart
|
|
||||||
chartType={ChartKeys.DILIGENT}
|
|
||||||
height={height}
|
|
||||||
width={width}
|
|
||||||
queryData={DEFAULT_QUERY_DATA}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
fixedWidth100height.story = { name: 'fixed width, 100% height' };
|
|
||||||
|
|
||||||
export const fixedHeight100Width = () => {
|
|
||||||
const width = text('Vis width', '100%');
|
|
||||||
const height = text('Vis height', '300');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SuperChart
|
|
||||||
chartType={ChartKeys.DILIGENT}
|
|
||||||
height={height}
|
|
||||||
width={width}
|
|
||||||
queryData={DEFAULT_QUERY_DATA}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
fixedHeight100Width.story = { name: 'fixed height, 100% width' };
|
|
||||||
|
|
||||||
export const withErrorBoundar = () => {
|
|
||||||
const width = text('Vis width', '500');
|
|
||||||
const height = text('Vis height', '300');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SuperChart
|
|
||||||
chartType={ChartKeys.BUGGY}
|
|
||||||
height={height}
|
|
||||||
width={width}
|
|
||||||
queryData={DEFAULT_QUERY_DATA}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export const withWrapper = () => {
|
|
||||||
const width = text('Vis width', '100%');
|
|
||||||
const height = text('Vis height', '100%');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SuperChart
|
|
||||||
chartType={ChartKeys.DILIGENT}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
queryData={DEFAULT_QUERY_DATA}
|
|
||||||
Wrapper={({ children }) => (
|
|
||||||
<div>
|
|
||||||
<div style={{ margin: 10, position: 'fixed' }}>With wrapper!</div>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export const withNoResults = () => {
|
|
||||||
const width = text('Vis width', '100%');
|
|
||||||
const height = text('Vis height', '100%');
|
|
||||||
|
|
||||||
return <SuperChart chartType={ChartKeys.DILIGENT} width={width} height={height} />;
|
|
||||||
};
|
|
||||||
export const withNoResultsAndMedium = () => {
|
|
||||||
const width = text('Vis width', '400');
|
|
||||||
const height = text('Vis height', '300');
|
|
||||||
|
|
||||||
return <SuperChart chartType={ChartKeys.DILIGENT} width={width} height={height} />;
|
|
||||||
};
|
|
||||||
export const withNoResultsAndSmall = () => {
|
|
||||||
const width = text('Vis width', '150');
|
|
||||||
const height = text('Vis height', '200');
|
|
||||||
|
|
||||||
return <SuperChart chartType={ChartKeys.DILIGENT} width={width} height={height} />;
|
|
||||||
};
|
|
@ -1,33 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import AirbnbPalettes from '@superset-ui/color/src/colorSchemes/categorical/airbnb';
|
|
||||||
import D3Palettes from '@superset-ui/color/src/colorSchemes/categorical/d3';
|
|
||||||
import GooglePalettes from '@superset-ui/color/src/colorSchemes/categorical/google';
|
|
||||||
import LyftPalettes from '@superset-ui/color/src/colorSchemes/categorical/lyft';
|
|
||||||
|
|
||||||
import SequantialCommonPalettes from '@superset-ui/color/src/colorSchemes/sequential/common';
|
|
||||||
import SequantialD3Palettes from '@superset-ui/color/src/colorSchemes/sequential/d3';
|
|
||||||
|
|
||||||
import RenderPalettes from './RenderPalettes';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Core Packages|@superset-ui/color',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const categoricalPalettes = () =>
|
|
||||||
[
|
|
||||||
{ palettes: AirbnbPalettes, storyName: 'Airbnb' },
|
|
||||||
{ palettes: D3Palettes, storyName: 'd3' },
|
|
||||||
{ palettes: GooglePalettes, storyName: 'Google' },
|
|
||||||
{ palettes: LyftPalettes, storyName: 'Lyft' },
|
|
||||||
].map(({ palettes, storyName }) => (
|
|
||||||
<RenderPalettes key={storyName} title={storyName} palettes={palettes} />
|
|
||||||
));
|
|
||||||
|
|
||||||
export const sequentialPalettes = () =>
|
|
||||||
[
|
|
||||||
{ palettes: SequantialCommonPalettes, storyName: 'Common' },
|
|
||||||
{ palettes: SequantialD3Palettes, storyName: 'd3' },
|
|
||||||
].map(({ palettes, storyName }) => (
|
|
||||||
<RenderPalettes key={storyName} title={storyName} palettes={palettes} />
|
|
||||||
));
|
|
@ -1,45 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { select, text, withKnobs } from '@storybook/addon-knobs';
|
|
||||||
import { bigNumberFormData } from '@superset-ui/chart/test/fixtures/formData';
|
|
||||||
|
|
||||||
import VerifyCORS, { Props as VerifyCORSProps } from '../../shared/components/VerifyCORS';
|
|
||||||
import Expandable from '../../shared/components/Expandable';
|
|
||||||
|
|
||||||
const REQUEST_METHODS = ['GET', 'POST'];
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'Core Packages|@superset-ui/connection',
|
|
||||||
decorators: [withKnobs],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const configureCORS = () => {
|
|
||||||
const host = text('Superset App host for CORS request', 'localhost:9000');
|
|
||||||
const endpoint = text('Endpoint to test (blank to test auth only)', '');
|
|
||||||
const method = endpoint ? select('Request method', REQUEST_METHODS, 'POST') : undefined;
|
|
||||||
const postPayload =
|
|
||||||
endpoint && method === 'POST'
|
|
||||||
? text('Optional POST payload', JSON.stringify({ form_data: bigNumberFormData }))
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ margin: 16 }}>
|
|
||||||
<VerifyCORS
|
|
||||||
host={host}
|
|
||||||
endpoint={endpoint}
|
|
||||||
method={method as VerifyCORSProps['method']}
|
|
||||||
postPayload={`${postPayload}`}
|
|
||||||
>
|
|
||||||
{({ payload }) => (
|
|
||||||
<>
|
|
||||||
<div className="alert alert-success">Success! Update knobs below to try again</div>
|
|
||||||
<br />
|
|
||||||
<Expandable expandableWhat="payload">
|
|
||||||
<br />
|
|
||||||
<pre style={{ fontSize: 11 }}>{JSON.stringify(payload, null, 2)}</pre>
|
|
||||||
</Expandable>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</VerifyCORS>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "../../build/lib",
|
|
||||||
"rootDir": "../../",
|
|
||||||
"emitDeclarationOnly": false,
|
|
||||||
"sourceMap": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"jsx": "react",
|
|
||||||
"noEmit": false,
|
|
||||||
},
|
|
||||||
"exclude": ["node_modules"],
|
|
||||||
"include": [
|
|
||||||
"storybook",
|
|
||||||
"../**/src",
|
|
||||||
"../../plugins/**/src",
|
|
||||||
]
|
|
||||||
}
|
|
@ -42,6 +42,11 @@ describe('TooltipTable', () => {
|
|||||||
);
|
);
|
||||||
expect(wrapper.find('tbody')).toHaveLength(1);
|
expect(wrapper.find('tbody')).toHaveLength(1);
|
||||||
expect(wrapper.find('tr')).toHaveLength(3);
|
expect(wrapper.find('tr')).toHaveLength(3);
|
||||||
expect(wrapper.find('tr > td').first().text()).toEqual('Cersei');
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('tr > td')
|
||||||
|
.first()
|
||||||
|
.text(),
|
||||||
|
).toEqual('Cersei');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,7 +42,7 @@ export const TestComponent = ({
|
|||||||
{[width, height].join('x')}
|
{[width, height].join('x')}
|
||||||
</div>
|
</div>
|
||||||
<div className="formData" style={{ padding: 10 }}>
|
<div className="formData" style={{ padding: 10 }}>
|
||||||
<code style={{ color: '#D3F9F7', background: 'none' }}>{JSON.stringify(formData)}</code>
|
<code style={{ color: '#D3F9F7' }}>{JSON.stringify(formData)}</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -247,7 +247,11 @@ describe('SuperChart', () => {
|
|||||||
|
|
||||||
return promiseTimeout(() => {
|
return promiseTimeout(() => {
|
||||||
const renderedWrapper = wrapper.render();
|
const renderedWrapper = wrapper.render();
|
||||||
const boundingBox = renderedWrapper.find('div.test-component').parent().parent().parent();
|
const boundingBox = renderedWrapper
|
||||||
|
.find('div.test-component')
|
||||||
|
.parent()
|
||||||
|
.parent()
|
||||||
|
.parent();
|
||||||
expect(boundingBox.css('width')).toEqual('50%');
|
expect(boundingBox.css('width')).toEqual('50%');
|
||||||
expect(boundingBox.css('height')).toEqual('125px');
|
expect(boundingBox.css('height')).toEqual('125px');
|
||||||
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
|
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
|
||||||
@ -268,7 +272,11 @@ describe('SuperChart', () => {
|
|||||||
|
|
||||||
return promiseTimeout(() => {
|
return promiseTimeout(() => {
|
||||||
const renderedWrapper = wrapper.render();
|
const renderedWrapper = wrapper.render();
|
||||||
const boundingBox = renderedWrapper.find('div.test-component').parent().parent().parent();
|
const boundingBox = renderedWrapper
|
||||||
|
.find('div.test-component')
|
||||||
|
.parent()
|
||||||
|
.parent()
|
||||||
|
.parent();
|
||||||
expect(boundingBox.css('width')).toEqual('50px');
|
expect(boundingBox.css('width')).toEqual('50px');
|
||||||
expect(boundingBox.css('height')).toEqual('25%');
|
expect(boundingBox.css('height')).toEqual('25%');
|
||||||
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
|
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
|
||||||
|
@ -94,7 +94,12 @@ describe('SuperChartCore', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return promiseTimeout(() => {
|
return promiseTimeout(() => {
|
||||||
expect(wrapper.render().find('.message').text()).toEqual('hulk');
|
expect(
|
||||||
|
wrapper
|
||||||
|
.render()
|
||||||
|
.find('.message')
|
||||||
|
.text(),
|
||||||
|
).toEqual('hulk');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('uses preTransformProps when specified', () => {
|
it('uses preTransformProps when specified', () => {
|
||||||
@ -110,7 +115,12 @@ describe('SuperChartCore', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return promiseTimeout(() => {
|
return promiseTimeout(() => {
|
||||||
expect(wrapper.render().find('.message').text()).toEqual('hulk');
|
expect(
|
||||||
|
wrapper
|
||||||
|
.render()
|
||||||
|
.find('.message')
|
||||||
|
.text(),
|
||||||
|
).toEqual('hulk');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('uses postTransformProps when specified', () => {
|
it('uses postTransformProps when specified', () => {
|
||||||
@ -122,7 +132,12 @@ describe('SuperChartCore', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return promiseTimeout(() => {
|
return promiseTimeout(() => {
|
||||||
expect(wrapper.render().find('.message').text()).toEqual('hulk');
|
expect(
|
||||||
|
wrapper
|
||||||
|
.render()
|
||||||
|
.find('.message')
|
||||||
|
.text(),
|
||||||
|
).toEqual('hulk');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('renders if chartProps is not specified', () => {
|
it('renders if chartProps is not specified', () => {
|
||||||
|
@ -64,7 +64,9 @@ export function getNamespace(name: string = DEFAULT_NAMESPACE) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getColor(value?: string, schemeId?: string, namespace?: string) {
|
export function getColor(value?: string, schemeId?: string, namespace?: string) {
|
||||||
return getNamespace(namespace).getScale(schemeId).getColor(value);
|
return getNamespace(namespace)
|
||||||
|
.getScale(schemeId)
|
||||||
|
.getColor(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScale(scheme?: string, namespace?: string) {
|
export function getScale(scheme?: string, namespace?: string) {
|
||||||
|
@ -2,7 +2,7 @@ import { scaleLinear } from 'd3-scale';
|
|||||||
import ColorScheme, { ColorSchemeConfig } from './ColorScheme';
|
import ColorScheme, { ColorSchemeConfig } from './ColorScheme';
|
||||||
|
|
||||||
function range(count: number) {
|
function range(count: number) {
|
||||||
const values: number[] = [];
|
const values = [];
|
||||||
for (let i = 0; i < count; i += 1) {
|
for (let i = 0; i < count; i += 1) {
|
||||||
values.push(i);
|
values.push(i);
|
||||||
}
|
}
|
||||||
@ -31,7 +31,10 @@ export default class SequentialScheme extends ColorScheme {
|
|||||||
const denominator = this.colors.length - 1;
|
const denominator = this.colors.length - 1;
|
||||||
const domain = range(this.colors.length).map(i => valueScale(i / denominator));
|
const domain = range(this.colors.length).map(i => valueScale(i / denominator));
|
||||||
|
|
||||||
return scaleLinear<string>().domain(domain).range(this.colors).clamp(true);
|
return scaleLinear<string>()
|
||||||
|
.domain(domain)
|
||||||
|
.range(this.colors)
|
||||||
|
.clamp(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
getColors(numColors: number = this.colors.length): string[] {
|
getColors(numColors: number = this.colors.length): string[] {
|
||||||
|
@ -127,14 +127,18 @@ describe('CategoricalColorNamespace', () => {
|
|||||||
it('getColor(value) returns a color from default scheme in default namespace', () => {
|
it('getColor(value) returns a color from default scheme in default namespace', () => {
|
||||||
const value = 'dog';
|
const value = 'dog';
|
||||||
const color = getColor(value);
|
const color = getColor(value);
|
||||||
const color2 = getNamespace().getScale().getColor(value);
|
const color2 = getNamespace()
|
||||||
|
.getScale()
|
||||||
|
.getColor(value);
|
||||||
expect(color).toBe(color2);
|
expect(color).toBe(color2);
|
||||||
});
|
});
|
||||||
it('getColor(value, scheme) returns a color from specified scheme in default namespace', () => {
|
it('getColor(value, scheme) returns a color from specified scheme in default namespace', () => {
|
||||||
const value = 'dog';
|
const value = 'dog';
|
||||||
const scheme = 'testColors';
|
const scheme = 'testColors';
|
||||||
const color = getColor(value, scheme);
|
const color = getColor(value, scheme);
|
||||||
const color2 = getNamespace().getScale(scheme).getColor(value);
|
const color2 = getNamespace()
|
||||||
|
.getScale(scheme)
|
||||||
|
.getColor(value);
|
||||||
expect(color).toBe(color2);
|
expect(color).toBe(color2);
|
||||||
});
|
});
|
||||||
it('getColor(value, scheme, namespace) returns a color from specified scheme in specified namespace', () => {
|
it('getColor(value, scheme, namespace) returns a color from specified scheme in specified namespace', () => {
|
||||||
@ -142,7 +146,9 @@ describe('CategoricalColorNamespace', () => {
|
|||||||
const scheme = 'testColors';
|
const scheme = 'testColors';
|
||||||
const namespace = 'test-getColor';
|
const namespace = 'test-getColor';
|
||||||
const color = getColor(value, scheme, namespace);
|
const color = getColor(value, scheme, namespace);
|
||||||
const color2 = getNamespace(namespace).getScale(scheme).getColor(value);
|
const color2 = getNamespace(namespace)
|
||||||
|
.getScale(scheme)
|
||||||
|
.getColor(value);
|
||||||
expect(color).toBe(color2);
|
expect(color).toBe(color2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ export default class ExtensibleFunction extends Function {
|
|||||||
constructor(fn: Function) {
|
constructor(fn: Function) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, no-constructor-return
|
// eslint-disable-next-line no-constructor-return
|
||||||
return Object.setPrototypeOf(fn, new.target.prototype);
|
return Object.setPrototypeOf(fn, new.target.prototype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ExtensibleFunction } from '../../src';
|
import ExtensibleFunction from '../../src/models/ExtensibleFunction';
|
||||||
|
|
||||||
describe('ExtensibleFunction', () => {
|
describe('ExtensibleFunction', () => {
|
||||||
class Func1 extends ExtensibleFunction {
|
class Func1 extends ExtensibleFunction {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Plugin } from '../../src';
|
import Plugin from '../../src/models/Plugin';
|
||||||
|
|
||||||
describe('Plugin', () => {
|
describe('Plugin', () => {
|
||||||
it('exists', () => {
|
it('exists', () => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Plugin, Preset } from '../../src';
|
import Plugin from '../../src/models/Plugin';
|
||||||
|
import Preset from '../../src/models/Preset';
|
||||||
|
|
||||||
describe('Preset', () => {
|
describe('Preset', () => {
|
||||||
it('exists', () => {
|
it('exists', () => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Registry, RegistryWithDefaultKey } from '../../src';
|
import RegistryWithDefaultKey from '../../src/models/RegistryWithDefaultKey';
|
||||||
|
import Registry from '../../src/models/Registry';
|
||||||
|
|
||||||
describe('RegistryWithDefaultKey', () => {
|
describe('RegistryWithDefaultKey', () => {
|
||||||
let registry: RegistryWithDefaultKey<number>;
|
let registry: RegistryWithDefaultKey<number>;
|
||||||
@ -30,7 +31,10 @@ describe('RegistryWithDefaultKey', () => {
|
|||||||
|
|
||||||
describe('.get()', () => {
|
describe('.get()', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
registry.registerValue('abc', 100).registerValue('def', 200).setDefaultKey('abc');
|
registry
|
||||||
|
.registerValue('abc', 100)
|
||||||
|
.registerValue('def', 200)
|
||||||
|
.setDefaultKey('abc');
|
||||||
});
|
});
|
||||||
it('.get() returns value from default key', () => {
|
it('.get() returns value from default key', () => {
|
||||||
expect(registry.get()).toEqual(100);
|
expect(registry.get()).toEqual(100);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { convertKeysToCamelCase } from '../../src';
|
import convertKeysToCamelCase from '../../src/utils/convertKeysToCamelCase';
|
||||||
|
|
||||||
describe('convertKeysToCamelCase(object)', () => {
|
describe('convertKeysToCamelCase(object)', () => {
|
||||||
it('returns undefined for undefined input', () => {
|
it('returns undefined for undefined input', () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isDefined } from '../../src';
|
import isDefined from '../../src/utils/isDefined';
|
||||||
|
|
||||||
describe('isDefined(value)', () => {
|
describe('isDefined(value)', () => {
|
||||||
it('returns true if value is not null and not undefined', () => {
|
it('returns true if value is not null and not undefined', () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isRequired } from '../../src';
|
import isRequired from '../../src/utils/isRequired';
|
||||||
|
|
||||||
describe('isRequired(field)', () => {
|
describe('isRequired(field)', () => {
|
||||||
it('should throw error with the given field in the message', () => {
|
it('should throw error with the given field in the message', () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { makeSingleton } from '../../src';
|
import makeSingleton from '../../src/utils/makeSingleton';
|
||||||
|
|
||||||
describe('makeSingleton()', () => {
|
describe('makeSingleton()', () => {
|
||||||
class Dog {
|
class Dog {
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
import '@storybook/addon-actions/register';
|
||||||
|
import '@storybook/addon-links/register';
|
||||||
|
import '@storybook/addon-knobs/register';
|
||||||
|
import 'storybook-addon-jsx/register';
|
@ -1,10 +1,4 @@
|
|||||||
import { addParameters, addDecorator } from '@storybook/react';
|
import { addParameters, configure } from '@storybook/react';
|
||||||
import { jsxDecorator } from 'storybook-addon-jsx';
|
|
||||||
|
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
||||||
import './storybook.css';
|
|
||||||
|
|
||||||
addDecorator(jsxDecorator);
|
|
||||||
|
|
||||||
addParameters({
|
addParameters({
|
||||||
options: {
|
options: {
|
||||||
@ -21,8 +15,12 @@ addParameters({
|
|||||||
sidebarAnimations: true,
|
sidebarAnimations: true,
|
||||||
sortStoriesByKind: false,
|
sortStoriesByKind: false,
|
||||||
url: '#',
|
url: '#',
|
||||||
storySort: (a, b) => {
|
|
||||||
return a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true });
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function loadStorybook() {
|
||||||
|
require('./storybook.css');
|
||||||
|
require('../storybook/stories'); // all of the stories
|
||||||
|
}
|
||||||
|
|
||||||
|
configure(loadStorybook, module);
|
@ -0,0 +1,8 @@
|
|||||||
|
html,
|
||||||
|
body,
|
||||||
|
#root {
|
||||||
|
height: 100%;
|
||||||
|
font-family: BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;
|
||||||
|
font-weight: 200;
|
||||||
|
color: #484848;
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const jsBabelPresets = [
|
||||||
|
['@babel/preset-env', {
|
||||||
|
useBuiltIns: 'usage',
|
||||||
|
corejs: 3,
|
||||||
|
loose: true,
|
||||||
|
modules: false,
|
||||||
|
shippedProposals: true,
|
||||||
|
targets: false
|
||||||
|
}],
|
||||||
|
'@babel/preset-react',
|
||||||
|
];
|
||||||
|
|
||||||
|
const tsBabelPresets = jsBabelPresets.concat(['@babel/preset-typescript']);
|
||||||
|
|
||||||
|
const babelPlugins = [
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-proposal-class-properties',
|
||||||
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
|
["@babel/plugin-transform-runtime", { "corejs": 3 }]
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = async ({ config }) => {
|
||||||
|
const cacheDirectory = path.resolve('../../../node_modules/.cache/storybook');
|
||||||
|
|
||||||
|
// rule that applies to jsx? files
|
||||||
|
config.module.rules[0].use = {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
cacheDirectory,
|
||||||
|
presets: jsBabelPresets,
|
||||||
|
plugins: babelPlugins,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// add rule for handling typescript
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
cacheDirectory,
|
||||||
|
presets: tsBabelPresets,
|
||||||
|
plugins: babelPlugins,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
config.resolve.extensions.push('.ts', '.tsx');
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
@ -6,9 +6,9 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"demo:clean": "rm -rf _gh-pages",
|
"demo:clean": "rm -rf _gh-pages",
|
||||||
"demo:build": "npm run demo:clean && build-storybook -o _gh-pages",
|
"demo:build": "build-storybook -o _gh-pages",
|
||||||
"demo:publish": "gh-pages -d _gh-pages",
|
"demo:publish": "gh-pages -d _gh-pages",
|
||||||
"deploy-demo": "npm run demo:build && npm run demo:publish && npm run demo:clean",
|
"deploy-demo": "npm run demo:clean && npm run demo:build && npm run demo:publish && npm run demo:clean",
|
||||||
"storybook": "start-storybook -p 9001"
|
"storybook": "start-storybook -p 9001"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -29,39 +29,32 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/apache-superset/superset-ui#readme",
|
"homepage": "https://github.com/apache-superset/superset-ui#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/addon-info": "^5.3.18",
|
"@storybook/addon-actions": "^5.0.6",
|
||||||
"@storybook/addon-actions": "^5.3.18",
|
"@storybook/addon-knobs": "^5.2.3",
|
||||||
"@storybook/addon-knobs": "^5.3.18",
|
"@storybook/addon-links": "^5.0.6",
|
||||||
"@storybook/addon-links": "^5.3.18",
|
"@storybook/addons": "^5.0.6",
|
||||||
"@storybook/addons": "^5.3.18",
|
"@storybook/react": "^5.2.3",
|
||||||
"@storybook/react": "^5.3.18",
|
"@superset-ui/chart": "0.12.12",
|
||||||
"@storybook/preset-typescript": "^3.0.0",
|
"@superset-ui/color": "0.12.12",
|
||||||
"@superset-ui/chart": "*",
|
"@superset-ui/connection": "0.12.12",
|
||||||
"@superset-ui/color": "*",
|
|
||||||
"@superset-ui/connection": "*",
|
|
||||||
"@superset-ui/legacy-plugin-chart-sankey": "^0.11.15",
|
"@superset-ui/legacy-plugin-chart-sankey": "^0.11.15",
|
||||||
"@superset-ui/legacy-plugin-chart-sunburst": "^0.11.15",
|
"@superset-ui/legacy-plugin-chart-sunburst": "^0.11.15",
|
||||||
"@superset-ui/legacy-plugin-chart-word-cloud": "^0.11.15",
|
"@superset-ui/legacy-plugin-chart-word-cloud": "^0.11.15",
|
||||||
"@superset-ui/legacy-plugin-chart-table": "*",
|
"@superset-ui/legacy-preset-chart-big-number": "^0.11.15",
|
||||||
"@superset-ui/legacy-preset-chart-big-number": "*",
|
"@superset-ui/number-format": "0.12.12",
|
||||||
"@superset-ui/number-format": "*",
|
|
||||||
"@superset-ui/plugin-chart-word-cloud": "^0.11.15",
|
"@superset-ui/plugin-chart-word-cloud": "^0.11.15",
|
||||||
"@superset-ui/query": "*",
|
"@superset-ui/query": "0.12.12",
|
||||||
"@superset-ui/time-format": "*",
|
"@superset-ui/time-format": "0.12.12",
|
||||||
"@superset-ui/translation": "*",
|
|
||||||
"@types/storybook__react": "4.0.2",
|
"@types/storybook__react": "4.0.2",
|
||||||
"bootstrap": "^3.4.1",
|
"bootstrap": "^4.3.1",
|
||||||
"core-js": "3.6.4",
|
"core-js": "3.6.4",
|
||||||
"gh-pages": "^2.2.0",
|
|
||||||
"jquery": "^3.4.1",
|
|
||||||
"react": "^16.6.0",
|
"react": "^16.6.0",
|
||||||
"storybook-addon-jsx": "^7.1.0"
|
"storybook-addon-jsx": "^7.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.9.0",
|
"@babel/core": "^7.8.4",
|
||||||
"typescript": "^3.8.3",
|
"@babel/plugin-transform-runtime": "^7.8.3",
|
||||||
"ts-loader": "^6.2.2",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-loader": "^8.1.0",
|
"gh-pages": "^2.2.0"
|
||||||
"fork-ts-checker-webpack-plugin": "^4.1.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,7 +26,11 @@ export default class Expandable extends React.Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button type="button" className="btn btn-primary btn-sm" onClick={this.handleToggle}>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={this.handleToggle}
|
||||||
|
className="btn btn-outline-primary btn-sm"
|
||||||
|
>
|
||||||
{`${open ? 'Hide' : 'Show'} ${expandableWhat}`}
|
{`${open ? 'Hide' : 'Show'} ${expandableWhat}`}
|
||||||
</button>
|
</button>
|
||||||
<br />
|
<br />
|
@ -73,7 +73,7 @@ export default class VerifyCORS extends React.Component<Props, State> {
|
|||||||
.then(response => this.setState({ didVerify: true, error: undefined, payload: response }))
|
.then(response => this.setState({ didVerify: true, error: undefined, payload: response }))
|
||||||
.catch((error: Response) => {
|
.catch((error: Response) => {
|
||||||
const { status, statusText = error } = error;
|
const { status, statusText = error } = error;
|
||||||
this.setState({ error: new Error(`${status || ''}${status ? ':' : ''} ${statusText}`) });
|
this.setState({ error: Error(`${status || ''}${status ? ':' : ''} ${statusText}`) });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +94,11 @@ export default class VerifyCORS extends React.Component<Props, State> {
|
|||||||
3) click below to verify authentication. You may debug CORS further using the
|
3) click below to verify authentication. You may debug CORS further using the
|
||||||
`@superset-ui/connection` story. <br />
|
`@superset-ui/connection` story. <br />
|
||||||
<br />
|
<br />
|
||||||
<button type="button" className="btn btn-primary btn-sm" onClick={this.handleVerify}>
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={this.handleVerify}
|
||||||
|
className="btn btn-outline-primary btn-sm"
|
||||||
|
>
|
||||||
Verify
|
Verify
|
||||||
</button>
|
</button>
|
||||||
<br />
|
<br />
|
@ -0,0 +1,43 @@
|
|||||||
|
import { setAddon, storiesOf } from '@storybook/react';
|
||||||
|
import { withKnobs } from '@storybook/addon-knobs';
|
||||||
|
import JSXAddon from 'storybook-addon-jsx';
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
|
||||||
|
setAddon(JSXAddon);
|
||||||
|
|
||||||
|
const EMPTY_EXAMPLES = [
|
||||||
|
{
|
||||||
|
renderStory: () => 'Does your default export have an `examples` key?',
|
||||||
|
storyName: 'No examples found',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Below we crawl the dir + subdirs looking for index files of stories
|
||||||
|
* Each index is expected to have a default export with examples key containing
|
||||||
|
* an array of examples. Each example should have the shape:
|
||||||
|
* { storyPath: string, storyName: string, renderStory: fn() => node }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const requireContext = require.context('./', /* subdirs= */ true, /index\.(j|t)sx?$/);
|
||||||
|
|
||||||
|
requireContext.keys().forEach(packageName => {
|
||||||
|
const packageExport = requireContext(packageName);
|
||||||
|
if (packageExport && packageExport.default && !Array.isArray(packageExport.default)) {
|
||||||
|
const { examples = EMPTY_EXAMPLES } = packageExport.default;
|
||||||
|
|
||||||
|
examples.forEach(example => {
|
||||||
|
const {
|
||||||
|
storyPath = 'Missing story path',
|
||||||
|
storyName = 'Missing name',
|
||||||
|
renderStory = () => 'Missing `renderStory`',
|
||||||
|
options = {},
|
||||||
|
} = example;
|
||||||
|
|
||||||
|
storiesOf(storyPath, module)
|
||||||
|
.addParameters({ options })
|
||||||
|
.addDecorator(withKnobs({ escapeHTML: false }))
|
||||||
|
.addWithJSX(storyName, renderStory);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,100 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { text, select } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import { SuperChart, ChartDataProvider } from '@superset-ui/chart';
|
||||||
|
import { SupersetClient } from '@superset-ui/connection';
|
||||||
|
import { BigNumberChartPlugin as LegacyBigNumberPlugin } from '@superset-ui/legacy-preset-chart-big-number';
|
||||||
|
import LegacySankeyPlugin from '@superset-ui/legacy-plugin-chart-sankey';
|
||||||
|
import LegacySunburstPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
||||||
|
import LegacyWordCloudPlugin from '@superset-ui/legacy-plugin-chart-word-cloud';
|
||||||
|
import WordCloudPlugin from '@superset-ui/plugin-chart-word-cloud';
|
||||||
|
|
||||||
|
import {
|
||||||
|
bigNumberFormData,
|
||||||
|
sankeyFormData,
|
||||||
|
sunburstFormData,
|
||||||
|
wordCloudFormData,
|
||||||
|
} from '@superset-ui/chart/test/fixtures/formData';
|
||||||
|
|
||||||
|
import Expandable from '../../shared/components/Expandable';
|
||||||
|
import VerifyCORS, { renderError } from '../../shared/components/VerifyCORS';
|
||||||
|
|
||||||
|
const BIG_NUMBER = bigNumberFormData.viz_type;
|
||||||
|
const SANKEY = sankeyFormData.viz_type;
|
||||||
|
const SUNBURST = sunburstFormData.viz_type;
|
||||||
|
const WORD_CLOUD_LEGACY = wordCloudFormData.viz_type;
|
||||||
|
const WORD_CLOUD = 'new_word_cloud';
|
||||||
|
|
||||||
|
new LegacyBigNumberPlugin().configure({ key: BIG_NUMBER }).register();
|
||||||
|
// @ts-ignore
|
||||||
|
new LegacySankeyPlugin().configure({ key: SANKEY }).register();
|
||||||
|
// @ts-ignore
|
||||||
|
new LegacySunburstPlugin().configure({ key: SUNBURST }).register();
|
||||||
|
// @ts-ignore
|
||||||
|
new LegacyWordCloudPlugin().configure({ key: WORD_CLOUD_LEGACY }).register();
|
||||||
|
// @ts-ignore
|
||||||
|
new WordCloudPlugin().configure({ key: WORD_CLOUD }).register();
|
||||||
|
|
||||||
|
const VIS_TYPES = [BIG_NUMBER, SANKEY, SUNBURST, WORD_CLOUD, WORD_CLOUD_LEGACY];
|
||||||
|
const FORM_DATA_LOOKUP = {
|
||||||
|
[BIG_NUMBER]: bigNumberFormData,
|
||||||
|
[SANKEY]: sankeyFormData,
|
||||||
|
[SUNBURST]: sunburstFormData,
|
||||||
|
[WORD_CLOUD]: { ...wordCloudFormData, viz_type: WORD_CLOUD },
|
||||||
|
[WORD_CLOUD_LEGACY]: wordCloudFormData,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const host = text('Set Superset App host for CORS request', 'localhost:9000');
|
||||||
|
const visType = select('Chart Plugin Type', VIS_TYPES, VIS_TYPES[0]);
|
||||||
|
const formData = text('Override formData', JSON.stringify(FORM_DATA_LOOKUP[visType]));
|
||||||
|
const width = text('Vis width', '500');
|
||||||
|
const height = text('Vis height', '300');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ margin: 16 }}>
|
||||||
|
<VerifyCORS host={host}>
|
||||||
|
{() => (
|
||||||
|
<ChartDataProvider client={SupersetClient} formData={JSON.parse(formData)}>
|
||||||
|
{({ loading, payload, error }) => {
|
||||||
|
if (loading) return <div>Loading!</div>;
|
||||||
|
|
||||||
|
if (error) return renderError(error);
|
||||||
|
|
||||||
|
if (payload)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SuperChart
|
||||||
|
chartType={visType}
|
||||||
|
formData={payload.formData}
|
||||||
|
height={Number(height)}
|
||||||
|
// @TODO fix typing
|
||||||
|
// all vis's now expect objects but api/v1/ returns an array
|
||||||
|
queryData={
|
||||||
|
Array.isArray(payload.queryData)
|
||||||
|
? payload.queryData[0]
|
||||||
|
: payload.queryData
|
||||||
|
}
|
||||||
|
width={Number(width)}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<Expandable expandableWhat="payload">
|
||||||
|
<pre style={{ fontSize: 11 }}>{JSON.stringify(payload, null, 2)}</pre>
|
||||||
|
</Expandable>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
</ChartDataProvider>
|
||||||
|
)}
|
||||||
|
</VerifyCORS>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: 'ChartDataProvider',
|
||||||
|
storyPath: '@superset-ui/chart',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,173 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
import { SuperChart } from '../../../../superset-ui-chart/src';
|
||||||
|
import {
|
||||||
|
DiligentChartPlugin,
|
||||||
|
BuggyChartPlugin,
|
||||||
|
ChartKeys,
|
||||||
|
} from '../../../../superset-ui-chart/test/components/MockChartPlugins';
|
||||||
|
|
||||||
|
new DiligentChartPlugin().configure({ key: ChartKeys.DILIGENT }).register();
|
||||||
|
new BuggyChartPlugin().configure({ key: ChartKeys.BUGGY }).register();
|
||||||
|
|
||||||
|
const DEFAULT_QUERY_DATA = { data: ['foo', 'bar'] };
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '100%');
|
||||||
|
const height = text('Vis height', '100%');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SuperChart
|
||||||
|
chartType={ChartKeys.DILIGENT}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
queryData={DEFAULT_QUERY_DATA}
|
||||||
|
formData={{ hi: 1 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: 'Basic',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '50%');
|
||||||
|
const height = text('Vis height', '50%');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SuperChart
|
||||||
|
chartType={ChartKeys.DILIGENT}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
queryData={DEFAULT_QUERY_DATA}
|
||||||
|
formData={{ hi: 1 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: '50% of container',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '500');
|
||||||
|
const height = text('Vis height', '300');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SuperChart
|
||||||
|
chartType={ChartKeys.DILIGENT}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
queryData={DEFAULT_QUERY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: 'fixed dimension',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '500');
|
||||||
|
const height = text('Vis height', '100%');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SuperChart
|
||||||
|
chartType={ChartKeys.DILIGENT}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
queryData={DEFAULT_QUERY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: 'fixed width, 100% height',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '100%');
|
||||||
|
const height = text('Vis height', '300');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SuperChart
|
||||||
|
chartType={ChartKeys.DILIGENT}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
queryData={DEFAULT_QUERY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: 'fixed height, 100% width',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '500');
|
||||||
|
const height = text('Vis height', '300');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SuperChart
|
||||||
|
chartType={ChartKeys.BUGGY}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
queryData={DEFAULT_QUERY_DATA}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: 'With error boundary',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '100%');
|
||||||
|
const height = text('Vis height', '100%');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SuperChart
|
||||||
|
chartType={ChartKeys.DILIGENT}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
queryData={DEFAULT_QUERY_DATA}
|
||||||
|
Wrapper={({ children }) => (
|
||||||
|
<div>
|
||||||
|
<div style={{ margin: 10, position: 'fixed' }}>With wrapper!</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: 'With Wrapper',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '100%');
|
||||||
|
const height = text('Vis height', '100%');
|
||||||
|
|
||||||
|
return <SuperChart chartType={ChartKeys.DILIGENT} width={width} height={height} />;
|
||||||
|
},
|
||||||
|
storyName: 'With no results',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '400');
|
||||||
|
const height = text('Vis height', '300');
|
||||||
|
|
||||||
|
return <SuperChart chartType={ChartKeys.DILIGENT} width={width} height={height} />;
|
||||||
|
},
|
||||||
|
storyName: 'With no results and medium',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const width = text('Vis width', '150');
|
||||||
|
const height = text('Vis height', '200');
|
||||||
|
|
||||||
|
return <SuperChart chartType={ChartKeys.DILIGENT} width={width} height={height} />;
|
||||||
|
},
|
||||||
|
storyName: 'With no results and small',
|
||||||
|
storyPath: '@superset-ui/chart|SuperChart',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,6 @@
|
|||||||
|
import ChartDataProviderStories from './ChartDataProviderStories';
|
||||||
|
import SuperChartStories from './SuperChartStories';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
examples: [...ChartDataProviderStories, ...SuperChartStories],
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AirbnbPalettes from '@superset-ui/color/lib/colorSchemes/categorical/airbnb';
|
||||||
|
import D3Palettes from '@superset-ui/color/lib/colorSchemes/categorical/d3';
|
||||||
|
import GooglePalettes from '@superset-ui/color/lib/colorSchemes/categorical/google';
|
||||||
|
import LyftPalettes from '@superset-ui/color/lib/colorSchemes/categorical/lyft';
|
||||||
|
import RenderPalettes from './RenderPalettes';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
renderStory: () =>
|
||||||
|
[
|
||||||
|
{ palettes: AirbnbPalettes, storyName: 'Airbnb' },
|
||||||
|
{ palettes: D3Palettes, storyName: 'd3' },
|
||||||
|
{ palettes: GooglePalettes, storyName: 'Google' },
|
||||||
|
{ palettes: LyftPalettes, storyName: 'Lyft' },
|
||||||
|
].map(({ palettes, storyName }) => (
|
||||||
|
<RenderPalettes key={storyName} title={storyName} palettes={palettes} />
|
||||||
|
)),
|
||||||
|
storyName: 'Categorical Palettes',
|
||||||
|
storyPath: '@superset-ui/color',
|
||||||
|
},
|
||||||
|
];
|
@ -1,6 +1,5 @@
|
|||||||
/* eslint react/prop-types: 'off' */
|
/* eslint react/prop-types: 'off' */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './color-styles.css';
|
|
||||||
|
|
||||||
export default function RenderPalettes({ title, palettes }) {
|
export default function RenderPalettes({ title, palettes }) {
|
||||||
return (
|
return (
|
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import CommonPalettes from '@superset-ui/color/lib/colorSchemes/sequential/common';
|
||||||
|
import D3Palettes from '@superset-ui/color/lib/colorSchemes/sequential/d3';
|
||||||
|
import RenderPalettes from './RenderPalettes';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
renderStory: () =>
|
||||||
|
[
|
||||||
|
{ palettes: CommonPalettes, storyName: 'Common' },
|
||||||
|
{ palettes: D3Palettes, storyName: 'd3' },
|
||||||
|
].map(({ palettes, storyName }) => (
|
||||||
|
<RenderPalettes key={storyName} title={storyName} palettes={palettes} />
|
||||||
|
)),
|
||||||
|
storyName: 'Sequential Palettes',
|
||||||
|
storyPath: '@superset-ui/color',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,7 @@
|
|||||||
|
import CategoricalStories from './CategoricalStories';
|
||||||
|
import SequentialStories from './SequentialStories';
|
||||||
|
import './color-styles.css';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
examples: [...CategoricalStories, ...SequentialStories],
|
||||||
|
};
|
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { select, text } from '@storybook/addon-knobs';
|
||||||
|
|
||||||
|
import VerifyCORS from '../../shared/components/VerifyCORS';
|
||||||
|
import Expandable from '../../shared/components/Expandable';
|
||||||
|
import { bigNumberFormData } from '../../../../superset-ui-chart/test/fixtures/formData';
|
||||||
|
|
||||||
|
const REQUEST_METHODS = ['GET', 'POST'];
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
renderStory: () => {
|
||||||
|
const host = text('Superset App host for CORS request', 'localhost:9000');
|
||||||
|
const endpoint = text('Endpoint to test (blank to test auth only)', undefined);
|
||||||
|
const method = endpoint ? select('Request method', REQUEST_METHODS, 'POST') : undefined;
|
||||||
|
const postPayload =
|
||||||
|
endpoint && method === 'POST'
|
||||||
|
? text('Optional POST payload', JSON.stringify({ form_data: bigNumberFormData }))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ margin: 16 }}>
|
||||||
|
<VerifyCORS
|
||||||
|
host={host}
|
||||||
|
endpoint={endpoint}
|
||||||
|
method={method}
|
||||||
|
postPayload={`${postPayload}`}
|
||||||
|
>
|
||||||
|
{({ payload }) => (
|
||||||
|
<>
|
||||||
|
<div className="alert alert-success">Success! Update knobs below to try again</div>
|
||||||
|
<br />
|
||||||
|
<Expandable expandableWhat="payload">
|
||||||
|
<br />
|
||||||
|
<pre style={{ fontSize: 11 }}>{JSON.stringify(payload, null, 2)}</pre>
|
||||||
|
</Expandable>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</VerifyCORS>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
storyName: 'Configure CORS',
|
||||||
|
storyPath: '@superset-ui/connection',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,5 @@
|
|||||||
|
import ConnectionStories from './ConnectionStories';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
examples: [...ConnectionStories],
|
||||||
|
};
|
@ -93,7 +93,7 @@ class NumberFormatValidator extends React.PureComponent {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{testValues.map((v) => (
|
{testValues.map(v => (
|
||||||
<tr key={v}>
|
<tr key={v}>
|
||||||
<td>
|
<td>
|
||||||
<code>{`${v}`}</code>
|
<code>{`${v}`}</code>
|
||||||
@ -115,8 +115,10 @@ class NumberFormatValidator extends React.PureComponent {
|
|||||||
NumberFormatValidator.propTypes = propTypes;
|
NumberFormatValidator.propTypes = propTypes;
|
||||||
NumberFormatValidator.defaultProps = defaultProps;
|
NumberFormatValidator.defaultProps = defaultProps;
|
||||||
|
|
||||||
export default {
|
export default [
|
||||||
title: 'Core Packages|@superset-ui/number-format',
|
{
|
||||||
};
|
renderStory: () => <NumberFormatValidator />,
|
||||||
|
storyName: 'Validator',
|
||||||
export const validator = () => <NumberFormatValidator />;
|
storyPath: '@superset-ui/number-format',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,5 @@
|
|||||||
|
import Stories from './Stories';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
examples: [...Stories],
|
||||||
|
};
|
@ -82,7 +82,7 @@ class TimeFormatValidator extends React.PureComponent {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{testValues.map((v) => (
|
{testValues.map(v => (
|
||||||
<tr key={v}>
|
<tr key={v}>
|
||||||
<td>
|
<td>
|
||||||
<code>{v instanceof Date ? v.toUTCString() : `${v}`}</code>
|
<code>{v instanceof Date ? v.toUTCString() : `${v}`}</code>
|
||||||
@ -104,8 +104,10 @@ class TimeFormatValidator extends React.PureComponent {
|
|||||||
TimeFormatValidator.propTypes = propTypes;
|
TimeFormatValidator.propTypes = propTypes;
|
||||||
TimeFormatValidator.defaultProps = defaultProps;
|
TimeFormatValidator.defaultProps = defaultProps;
|
||||||
|
|
||||||
export default {
|
export default [
|
||||||
title: 'Core Packages|@superset-ui/time-format',
|
{
|
||||||
};
|
renderStory: () => <TimeFormatValidator />,
|
||||||
|
storyName: 'Validator',
|
||||||
export const validator = () => <TimeFormatValidator />;
|
storyPath: '@superset-ui/time-format',
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,5 @@
|
|||||||
|
import Stories from './Stories';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
examples: [...Stories],
|
||||||
|
};
|
@ -0,0 +1,4 @@
|
|||||||
|
declare module '@superset-ui/legacy-preset-chart-big-number';
|
||||||
|
declare module '@superset-ui/legacy-plugin-chart-sankey';
|
||||||
|
declare module '@superset-ui/legacy-plugin-chart-sunburst';
|
||||||
|
declare module '@superset-ui/legacy-plugin-chart-word-cloud';
|
@ -166,9 +166,9 @@ describe('mergeMargin(margin1, margin2, mode?)', () => {
|
|||||||
it('if there are NaN or null, use another value', () => {
|
it('if there are NaN or null, use another value', () => {
|
||||||
expect(
|
expect(
|
||||||
mergeMargin(
|
mergeMargin(
|
||||||
|
// @ts-ignore to let us pass `null` for testing
|
||||||
{
|
{
|
||||||
top: 10,
|
top: 10,
|
||||||
// @ts-ignore to let us pass `null` for testing
|
|
||||||
left: null,
|
left: null,
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
right: NaN,
|
right: NaN,
|
||||||
|
@ -6,28 +6,6 @@ const siFormatter = d3Format(`.3~s`);
|
|||||||
const float2PointFormatter = d3Format(`.2~f`);
|
const float2PointFormatter = d3Format(`.2~f`);
|
||||||
const float4PointFormatter = d3Format(`.4~f`);
|
const float4PointFormatter = d3Format(`.4~f`);
|
||||||
|
|
||||||
function formatValue(value: number) {
|
|
||||||
if (value === 0) {
|
|
||||||
return '0';
|
|
||||||
}
|
|
||||||
const absoluteValue = Math.abs(value);
|
|
||||||
if (absoluteValue >= 1000) {
|
|
||||||
// Normal human being are more familiar
|
|
||||||
// with billion (B) that giga (G)
|
|
||||||
return siFormatter(value).replace('G', 'B');
|
|
||||||
}
|
|
||||||
if (absoluteValue >= 1) {
|
|
||||||
return float2PointFormatter(value);
|
|
||||||
}
|
|
||||||
if (absoluteValue >= 0.001) {
|
|
||||||
return float4PointFormatter(value);
|
|
||||||
}
|
|
||||||
if (absoluteValue > 0.000001) {
|
|
||||||
return `${siFormatter(value * 1000000)}µ`;
|
|
||||||
}
|
|
||||||
return siFormatter(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function createSmartNumberFormatter(
|
export default function createSmartNumberFormatter(
|
||||||
config: {
|
config: {
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -39,6 +17,29 @@ export default function createSmartNumberFormatter(
|
|||||||
const { description, signed = false, id, label } = config;
|
const { description, signed = false, id, label } = config;
|
||||||
const getSign = signed ? (value: number) => (value > 0 ? '+' : '') : () => '';
|
const getSign = signed ? (value: number) => (value > 0 ? '+' : '') : () => '';
|
||||||
|
|
||||||
|
function formatValue(value: number) {
|
||||||
|
if (value === 0) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
const absoluteValue = Math.abs(value);
|
||||||
|
if (absoluteValue >= 1000) {
|
||||||
|
// Normal human being are more familiar
|
||||||
|
// with billion (B) that giga (G)
|
||||||
|
return siFormatter(value).replace('G', 'B');
|
||||||
|
}
|
||||||
|
if (absoluteValue >= 1) {
|
||||||
|
return float2PointFormatter(value);
|
||||||
|
}
|
||||||
|
if (absoluteValue >= 0.001) {
|
||||||
|
return float4PointFormatter(value);
|
||||||
|
}
|
||||||
|
if (absoluteValue > 0.000001) {
|
||||||
|
return `${siFormatter(value * 1000000)}µ`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return siFormatter(value);
|
||||||
|
}
|
||||||
|
|
||||||
return new NumberFormatter({
|
return new NumberFormatter({
|
||||||
description,
|
description,
|
||||||
formatFunc: value => `${getSign(value)}${formatValue(value)}`,
|
formatFunc: value => `${getSign(value)}${formatValue(value)}`,
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
## @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://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-big-number.svg?style=flat-square)
|
|
||||||
[![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={...}
|
|
||||||
queryData={{
|
|
||||||
data: {...},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
```
|
|
@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@superset-ui/legacy-preset-chart-big-number",
|
|
||||||
"version": "0.11.21",
|
|
||||||
"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-plugins.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"superset"
|
|
||||||
],
|
|
||||||
"author": "Superset",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/apache-superset/superset-ui-plugins/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/apache-superset/superset-ui-plugins#readme",
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@data-ui/xy-chart": "^0.0.84",
|
|
||||||
"@types/d3-color": "^1.2.2",
|
|
||||||
"@types/shortid": "^0.0.29",
|
|
||||||
"d3-color": "^1.2.3",
|
|
||||||
"shortid": "^2.2.14"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@superset-ui/chart": "^0.12.0",
|
|
||||||
"@superset-ui/color": "^0.12.0",
|
|
||||||
"@superset-ui/core": "^0.12.0",
|
|
||||||
"@superset-ui/dimension": "^0.12.0",
|
|
||||||
"@superset-ui/number-format": "^0.12.0",
|
|
||||||
"@superset-ui/time-format": "^0.12.0",
|
|
||||||
"@superset-ui/translation": "^0.12.0",
|
|
||||||
"react": "^15 || ^16"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +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-legacy-chart-big-number {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
|
|
||||||
Open Sans, Helvetica Neue, sans-serif;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-legacy-chart-big-number.no-trendline .subheader-line {
|
|
||||||
padding-bottom: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-legacy-chart-big-number .text-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-legacy-chart-big-number .text-container .alert {
|
|
||||||
font-size: 11px;
|
|
||||||
margin: -0.5em 0 0.4em;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 2px 4px 3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-legacy-chart-big-number .header-line {
|
|
||||||
position: relative;
|
|
||||||
line-height: 1em;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-legacy-chart-big-number .header-line span {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-legacy-chart-big-number .subheader-line {
|
|
||||||
line-height: 1em;
|
|
||||||
padding-bottom: 0;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-legacy-chart-big-number.is-fallback-value .header-line,
|
|
||||||
.superset-legacy-chart-big-number.is-fallback-value .subheader-line {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-data-ui-tooltip {
|
|
||||||
z-index: 1000;
|
|
||||||
background: #000;
|
|
||||||
}
|
|
@ -1,305 +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 React from 'react';
|
|
||||||
import shortid from 'shortid';
|
|
||||||
import { t } from '@superset-ui/translation';
|
|
||||||
import { getNumberFormatter } from '@superset-ui/number-format';
|
|
||||||
import { XYChart, AreaSeries, CrossHair, LinearGradient } from '@data-ui/xy-chart';
|
|
||||||
import { BRAND_COLOR } from '@superset-ui/color';
|
|
||||||
import { computeMaxFontSize } from '@superset-ui/dimension';
|
|
||||||
import NumberFormatter from '@superset-ui/number-format/src/NumberFormatter';
|
|
||||||
import { smartDateVerboseFormatter } from '@superset-ui/time-format';
|
|
||||||
import TimeFormatter from '@superset-ui/time-format/src/TimeFormatter';
|
|
||||||
|
|
||||||
import './BigNumber.css';
|
|
||||||
|
|
||||||
const defaultNumberFormatter = getNumberFormatter();
|
|
||||||
|
|
||||||
const CHART_MARGIN = {
|
|
||||||
top: 4,
|
|
||||||
right: 4,
|
|
||||||
bottom: 4,
|
|
||||||
left: 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PROPORTION = {
|
|
||||||
// text size: proportion of the chart container sans trendline
|
|
||||||
HEADER: 0.3,
|
|
||||||
SUBHEADER: 0.125,
|
|
||||||
// trendline size: proportion of the whole chart container
|
|
||||||
TRENDLINE: 0.3,
|
|
||||||
};
|
|
||||||
|
|
||||||
type TimeSeriesDatum = {
|
|
||||||
x: number; // timestamp as a number
|
|
||||||
y: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function renderTooltipFactory(
|
|
||||||
formatDate = smartDateVerboseFormatter,
|
|
||||||
formatValue = defaultNumberFormatter,
|
|
||||||
) {
|
|
||||||
return function renderTooltip({ datum: { x, y } }: { datum: TimeSeriesDatum }) {
|
|
||||||
// even though `formatDate` supports timestamp as numbers, we need
|
|
||||||
// `new Date` to pass type check
|
|
||||||
return (
|
|
||||||
<div style={{ padding: '4px 8px' }}>
|
|
||||||
{formatDate(new Date(x))}
|
|
||||||
<br />
|
|
||||||
<strong>{y === null ? t('N/A') : formatValue(y)}</strong>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
type BigNumberVisProps = {
|
|
||||||
className?: string;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
bigNumber?: number | null;
|
|
||||||
bigNumberFallback?: TimeSeriesDatum;
|
|
||||||
formatNumber: NumberFormatter;
|
|
||||||
formatTime: TimeFormatter;
|
|
||||||
fromDatetime?: number;
|
|
||||||
toDatetime?: number;
|
|
||||||
headerFontSize: number;
|
|
||||||
subheader: string;
|
|
||||||
subheaderFontSize: number;
|
|
||||||
showTrendLine?: boolean;
|
|
||||||
startYAxisAtZero?: boolean;
|
|
||||||
timeRangeFixed?: boolean;
|
|
||||||
trendLineData?: TimeSeriesDatum[];
|
|
||||||
mainColor: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BigNumberVis extends React.PureComponent<BigNumberVisProps, {}> {
|
|
||||||
private gradientId: string = shortid.generate();
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
className: '',
|
|
||||||
formatNumber: (num: number) => String(num),
|
|
||||||
formatTime: smartDateVerboseFormatter.formatFunc,
|
|
||||||
headerFontSize: PROPORTION.HEADER,
|
|
||||||
mainColor: BRAND_COLOR,
|
|
||||||
showTrendLine: false,
|
|
||||||
startYAxisAtZero: true,
|
|
||||||
subheader: '',
|
|
||||||
subheaderFontSize: PROPORTION.SUBHEADER,
|
|
||||||
timeRangeFixed: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
getClassName() {
|
|
||||||
const { className, showTrendLine, bigNumberFallback } = this.props;
|
|
||||||
const names = `superset-legacy-chart-big-number ${className} ${
|
|
||||||
bigNumberFallback ? 'is-fallback-value' : ''
|
|
||||||
}`;
|
|
||||||
if (showTrendLine) return names;
|
|
||||||
return `${names} no-trendline`;
|
|
||||||
}
|
|
||||||
|
|
||||||
createTemporaryContainer() {
|
|
||||||
const container = document.createElement('div');
|
|
||||||
container.className = this.getClassName();
|
|
||||||
container.style.position = 'absolute'; // so it won't disrupt page layout
|
|
||||||
container.style.opacity = '0'; // and not visible
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFallbackWarning() {
|
|
||||||
const { bigNumberFallback, formatTime } = this.props;
|
|
||||||
if (!bigNumberFallback) return null;
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className="alert alert-warning"
|
|
||||||
role="alert"
|
|
||||||
title={t(`Last available value seen on %s`, formatTime(bigNumberFallback.x))}
|
|
||||||
>
|
|
||||||
{t('Not up to date')}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderHeader(maxHeight: number) {
|
|
||||||
const { bigNumber, formatNumber, width } = this.props;
|
|
||||||
const text = bigNumber === null ? t('No data') : formatNumber(bigNumber);
|
|
||||||
|
|
||||||
const container = this.createTemporaryContainer();
|
|
||||||
document.body.append(container);
|
|
||||||
const fontSize = computeMaxFontSize({
|
|
||||||
text,
|
|
||||||
maxWidth: width,
|
|
||||||
maxHeight,
|
|
||||||
className: 'header-line',
|
|
||||||
container,
|
|
||||||
});
|
|
||||||
container.remove();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="header-line"
|
|
||||||
style={{
|
|
||||||
fontSize,
|
|
||||||
height: maxHeight,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSubheader(maxHeight: number) {
|
|
||||||
const { bigNumber, subheader, width, bigNumberFallback } = this.props;
|
|
||||||
let fontSize = 0;
|
|
||||||
|
|
||||||
const NO_DATA_OR_HASNT_LANDED = t(
|
|
||||||
'No data after filtering or data is NULL for the latest time record',
|
|
||||||
);
|
|
||||||
const NO_DATA = t('Try applying different filters or ensuring your datasource has data');
|
|
||||||
let text = subheader;
|
|
||||||
if (bigNumber === null) {
|
|
||||||
text = bigNumberFallback ? NO_DATA : NO_DATA_OR_HASNT_LANDED;
|
|
||||||
}
|
|
||||||
if (text) {
|
|
||||||
const container = this.createTemporaryContainer();
|
|
||||||
document.body.append(container);
|
|
||||||
fontSize = computeMaxFontSize({
|
|
||||||
text,
|
|
||||||
maxWidth: width,
|
|
||||||
maxHeight,
|
|
||||||
className: 'subheader-line',
|
|
||||||
container,
|
|
||||||
});
|
|
||||||
container.remove();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="subheader-line"
|
|
||||||
style={{
|
|
||||||
fontSize,
|
|
||||||
height: maxHeight,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTrendline(maxHeight: number) {
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
trendLineData,
|
|
||||||
mainColor,
|
|
||||||
subheader,
|
|
||||||
startYAxisAtZero,
|
|
||||||
formatNumber,
|
|
||||||
formatTime,
|
|
||||||
fromDatetime,
|
|
||||||
timeRangeFixed,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
// if can't find any non-null values, no point rendering the trendline
|
|
||||||
if (!trendLineData?.some(d => d.y !== null)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply a fixed X range if a time range is specified.
|
|
||||||
//
|
|
||||||
// XYChart checks the existence of `domain` property and decide whether to
|
|
||||||
// apply a domain or not, so it must not be `null` or `undefined`
|
|
||||||
const xScale: { type: string; domain?: number[] } = { type: 'timeUtc' };
|
|
||||||
const tooltipData = trendLineData && [...trendLineData];
|
|
||||||
if (tooltipData && timeRangeFixed && fromDatetime) {
|
|
||||||
const toDatetime = this.props.toDatetime ?? Date.now();
|
|
||||||
if (tooltipData[0].x > fromDatetime) {
|
|
||||||
tooltipData.unshift({
|
|
||||||
x: fromDatetime,
|
|
||||||
y: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (tooltipData[tooltipData.length - 1].x < toDatetime) {
|
|
||||||
tooltipData.push({
|
|
||||||
x: toDatetime,
|
|
||||||
y: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
xScale.domain = [fromDatetime, toDatetime];
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<XYChart
|
|
||||||
snapTooltipToDataX
|
|
||||||
ariaLabel={`Big number visualization ${subheader}`}
|
|
||||||
renderTooltip={renderTooltipFactory(formatTime, formatNumber)}
|
|
||||||
xScale={xScale}
|
|
||||||
yScale={{
|
|
||||||
type: 'linear',
|
|
||||||
includeZero: startYAxisAtZero,
|
|
||||||
}}
|
|
||||||
width={Math.floor(width)}
|
|
||||||
height={maxHeight}
|
|
||||||
margin={CHART_MARGIN}
|
|
||||||
eventTrigger="container"
|
|
||||||
>
|
|
||||||
<LinearGradient id={this.gradientId} from={mainColor} to="#fff" />
|
|
||||||
<AreaSeries data={tooltipData} fill={`url(#${this.gradientId})`} stroke={mainColor} />
|
|
||||||
<CrossHair
|
|
||||||
fullHeight
|
|
||||||
stroke={mainColor}
|
|
||||||
circleFill={mainColor}
|
|
||||||
circleStroke="#fff"
|
|
||||||
showHorizontalLine={false}
|
|
||||||
strokeDasharray="5,2"
|
|
||||||
/>
|
|
||||||
</XYChart>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { showTrendLine, height, headerFontSize, subheaderFontSize } = this.props;
|
|
||||||
const className = this.getClassName();
|
|
||||||
|
|
||||||
if (showTrendLine) {
|
|
||||||
const chartHeight = Math.floor(PROPORTION.TRENDLINE * height);
|
|
||||||
const allTextHeight = height - chartHeight;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<div className="text-container" style={{ height: allTextHeight }}>
|
|
||||||
{this.renderFallbackWarning()}
|
|
||||||
{this.renderHeader(Math.ceil(headerFontSize * (1 - PROPORTION.TRENDLINE) * height))}
|
|
||||||
{this.renderSubheader(
|
|
||||||
Math.ceil(subheaderFontSize * (1 - PROPORTION.TRENDLINE) * height),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{this.renderTrendline(chartHeight)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className} style={{ height }}>
|
|
||||||
{this.renderHeader(Math.ceil(headerFontSize * height))}
|
|
||||||
{this.renderSubheader(Math.ceil(subheaderFontSize * height))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BigNumberVis;
|
|
Binary file not shown.
Before Width: | Height: | Size: 101 KiB |
Binary file not shown.
Before Width: | Height: | Size: 50 KiB |
@ -1,39 +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 { t } from '@superset-ui/translation';
|
|
||||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
|
||||||
import transformProps from './transformProps';
|
|
||||||
import thumbnail from './images/thumbnail.png';
|
|
||||||
|
|
||||||
const metadata = new ChartMetadata({
|
|
||||||
description: '',
|
|
||||||
name: t('Big Number with Trendline'),
|
|
||||||
thumbnail,
|
|
||||||
useLegacyApi: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class BigNumberChartPlugin extends ChartPlugin {
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
loadChart: () => import('./BigNumber'),
|
|
||||||
metadata,
|
|
||||||
transformProps,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,138 +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 { getNumberFormatter, NumberFormats } from '@superset-ui/number-format';
|
|
||||||
import { ChartProps } from '@superset-ui/chart';
|
|
||||||
import getTimeFormatterForGranularity from '../utils/getTimeFormatterForGranularity';
|
|
||||||
|
|
||||||
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
|
|
||||||
type BigNumberDatum = {
|
|
||||||
[TIME_COLUMN]: number;
|
|
||||||
[key: string]: number | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function transformProps(chartProps: ChartProps) {
|
|
||||||
const { width, height, formData, queryData } = chartProps;
|
|
||||||
const {
|
|
||||||
colorPicker,
|
|
||||||
compareLag: compareLagInput,
|
|
||||||
compareSuffix = '',
|
|
||||||
headerFontSize,
|
|
||||||
metric,
|
|
||||||
showTrendLine,
|
|
||||||
startYAxisAtZero,
|
|
||||||
subheader = '',
|
|
||||||
subheaderFontSize,
|
|
||||||
timeGrainSqla: granularity,
|
|
||||||
vizType,
|
|
||||||
timeRangeFixed = false,
|
|
||||||
} = formData;
|
|
||||||
let { yAxisFormat } = formData;
|
|
||||||
const { data, from_dttm: fromDatetime, to_dttm: toDatetime } = queryData;
|
|
||||||
const metricName = metric?.label ? metric.label : metric;
|
|
||||||
const compareLag = Number(compareLagInput) || 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 bigNumberFallback;
|
|
||||||
|
|
||||||
if (data.length > 0) {
|
|
||||||
const sortedData = (data as BigNumberDatum[])
|
|
||||||
.map(d => ({ x: d[TIME_COLUMN], y: d[metricName] }))
|
|
||||||
.sort((a, b) => b.x - a.x); // sort in time descending order
|
|
||||||
|
|
||||||
bigNumber = sortedData[0].y;
|
|
||||||
if (bigNumber === null) {
|
|
||||||
bigNumberFallback = sortedData.find(d => d.y !== null);
|
|
||||||
bigNumber = bigNumberFallback ? bigNumberFallback.y : 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(
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
(metricEntry: { metric_name?: string; d3format: string }) => {
|
|
||||||
if (metricEntry.metric_name === metric && metricEntry.d3format) {
|
|
||||||
yAxisFormat = metricEntry.d3format;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatNumber = getNumberFormatter(yAxisFormat);
|
|
||||||
const formatTime = getTimeFormatterForGranularity(granularity);
|
|
||||||
|
|
||||||
return {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
bigNumber,
|
|
||||||
bigNumberFallback,
|
|
||||||
className,
|
|
||||||
formatNumber,
|
|
||||||
formatTime,
|
|
||||||
headerFontSize,
|
|
||||||
subheaderFontSize,
|
|
||||||
mainColor,
|
|
||||||
showTrendLine: supportAndShowTrendLine,
|
|
||||||
startYAxisAtZero,
|
|
||||||
subheader: formattedSubheader,
|
|
||||||
trendLineData,
|
|
||||||
fromDatetime,
|
|
||||||
toDatetime,
|
|
||||||
timeRangeFixed,
|
|
||||||
};
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 27 KiB |
@ -1,39 +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 { t } from '@superset-ui/translation';
|
|
||||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
|
||||||
import transformProps from '../BigNumber/transformProps';
|
|
||||||
import thumbnail from './images/thumbnail.png';
|
|
||||||
|
|
||||||
const metadata = new ChartMetadata({
|
|
||||||
description: '',
|
|
||||||
name: t('Big Number'),
|
|
||||||
thumbnail,
|
|
||||||
useLegacyApi: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class BigNumberTotalChartPlugin extends ChartPlugin {
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
loadChart: () => import('../BigNumber/BigNumber'),
|
|
||||||
metadata,
|
|
||||||
transformProps,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
export { default as BigNumberChartPlugin } from './BigNumber/index';
|
|
||||||
export { default as BigNumberTotalChartPlugin } from './BigNumberTotal/index';
|
|
||||||
export { default as BigNumberChartPreset } from './preset';
|
|
@ -1,33 +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 { Preset } from '@superset-ui/core';
|
|
||||||
import BigNumberChartPlugin from './BigNumber';
|
|
||||||
import BigNumberTotalChartPlugin from './BigNumberTotal';
|
|
||||||
|
|
||||||
export default class BigNumberChartPreset extends Preset {
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
name: 'BigNumber charts',
|
|
||||||
plugins: [
|
|
||||||
new BigNumberChartPlugin().configure({ key: 'big_number' }),
|
|
||||||
new BigNumberTotalChartPlugin().configure({ key: 'big_number_total' }),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,109 +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 transformProps from '../BigNumber/transformProps';
|
|
||||||
|
|
||||||
const formData = {
|
|
||||||
metric: 'value',
|
|
||||||
colorPicker: {
|
|
||||||
r: 0,
|
|
||||||
g: 122,
|
|
||||||
b: 135,
|
|
||||||
a: 1,
|
|
||||||
},
|
|
||||||
compareLag: 1,
|
|
||||||
timeGrainSqla: 'P0.25Y',
|
|
||||||
compareSuffix: 'over last quarter',
|
|
||||||
vizType: 'big_number',
|
|
||||||
yAxisFormat: '.3s',
|
|
||||||
};
|
|
||||||
|
|
||||||
function generateProps(data: object[], extraFormData = {}, extraQueryData = {}) {
|
|
||||||
return {
|
|
||||||
width: 200,
|
|
||||||
height: 500,
|
|
||||||
annotationData: {},
|
|
||||||
datasource: {
|
|
||||||
columnFormats: {},
|
|
||||||
verboseMap: {},
|
|
||||||
},
|
|
||||||
rawDatasource: {},
|
|
||||||
rawFormData: {},
|
|
||||||
hooks: {},
|
|
||||||
initialValues: {},
|
|
||||||
formData: {
|
|
||||||
...formData,
|
|
||||||
...extraFormData,
|
|
||||||
},
|
|
||||||
queryData: {
|
|
||||||
data,
|
|
||||||
...extraQueryData,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('BigNumber', () => {
|
|
||||||
const props = generateProps(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
__timestamp: 0,
|
|
||||||
value: 1.2345,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
__timestamp: 100,
|
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{ showTrendLine: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('transformProps()', () => {
|
|
||||||
it('should fallback and format time', () => {
|
|
||||||
const transformed = transformProps(props);
|
|
||||||
// the first item is the last item sorted by __timestamp
|
|
||||||
const lastDatum = transformed.trendLineData?.pop();
|
|
||||||
|
|
||||||
// should use last available value
|
|
||||||
expect(lastDatum?.x).toStrictEqual(100);
|
|
||||||
expect(lastDatum?.y).toBeNull();
|
|
||||||
|
|
||||||
// should note this is a fallback
|
|
||||||
expect(transformed.bigNumber).toStrictEqual(1.2345);
|
|
||||||
expect(transformed.bigNumberFallback).not.toBeNull();
|
|
||||||
|
|
||||||
// should successfully formatTime by ganularity
|
|
||||||
expect(transformed.formatTime(new Date('2020-01-01'))).toStrictEqual('2020 Q1');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should respect datasource d3 format', () => {
|
|
||||||
const propsWithDatasource = {
|
|
||||||
...props,
|
|
||||||
datasource: {
|
|
||||||
metrics: [
|
|
||||||
{
|
|
||||||
metric_name: 'value',
|
|
||||||
d3format: '.2f',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const transformed = transformProps(propsWithDatasource);
|
|
||||||
expect(transformed.formatNumber(transformed.bigNumber)).toStrictEqual('1.23');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,20 +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.
|
|
||||||
*/
|
|
||||||
declare module '@data-ui/xy-chart';
|
|
||||||
declare module '*.png';
|
|
@ -1,70 +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 { getTimeFormatter, TimeFormats, smartDateVerboseFormatter } from '@superset-ui/time-format';
|
|
||||||
|
|
||||||
// Translate time granularity to d3-format
|
|
||||||
const MINUTE = '%Y-%m-%d %H:%M';
|
|
||||||
const SUNDAY_BASED_WEEK = '%Y W%U';
|
|
||||||
const MONDAY_BASED_WEEK = '%Y W%W';
|
|
||||||
const { DATABASE_DATE, DATABASE_DATETIME } = TimeFormats;
|
|
||||||
|
|
||||||
// search for `builtin_time_grains` in incubator-superset/superset/db_engine_specs/base.py
|
|
||||||
const formats = {
|
|
||||||
date: DATABASE_DATE,
|
|
||||||
PT1S: DATABASE_DATETIME, // second
|
|
||||||
PT1M: MINUTE, // minute
|
|
||||||
PT5M: MINUTE, // 5 minute
|
|
||||||
PT10M: MINUTE, // 10 minute
|
|
||||||
PT15M: MINUTE, // 15 minute
|
|
||||||
'PT0.5H': MINUTE, // half hour
|
|
||||||
PT1H: '%Y-%m-%d %H:00', // hour
|
|
||||||
P1D: DATABASE_DATE, // day
|
|
||||||
P1W: SUNDAY_BASED_WEEK, // week
|
|
||||||
P1M: '%Y-%m', // month
|
|
||||||
'P0.25Y': '%Y Q%q', // quarter
|
|
||||||
P1Y: '%Y', // year
|
|
||||||
// d3-time-format weeks does not support weeks start on Sunday
|
|
||||||
'1969-12-28T00:00:00Z/P1W': SUNDAY_BASED_WEEK, // 'week_start_sunday'
|
|
||||||
'1969-12-29T00:00:00Z/P1W': MONDAY_BASED_WEEK, // 'week_start_monday'
|
|
||||||
'P1W/1970-01-03T00:00:00Z': SUNDAY_BASED_WEEK, // 'week_ending_saturday'
|
|
||||||
'P1W/1970-01-04T00:00:00Z': MONDAY_BASED_WEEK, // 'week_ending_sunday'
|
|
||||||
};
|
|
||||||
|
|
||||||
type TimeGranularity =
|
|
||||||
| 'date'
|
|
||||||
| 'PT1S'
|
|
||||||
| 'PT1M'
|
|
||||||
| 'PT5M'
|
|
||||||
| 'PT10M'
|
|
||||||
| 'PT15M'
|
|
||||||
| 'PT0.5H'
|
|
||||||
| 'PT1H'
|
|
||||||
| 'P1D'
|
|
||||||
| 'P1W'
|
|
||||||
| 'P0.25Y'
|
|
||||||
| 'P1Y'
|
|
||||||
| '1969-12-28T00:00:00Z/P1W'
|
|
||||||
| '1969-12-29T00:00:00Z/P1W'
|
|
||||||
| 'P1W/1970-01-03T00:00:00Z';
|
|
||||||
|
|
||||||
export default function getTimeFormatterForGranularity(granularity: TimeGranularity) {
|
|
||||||
return granularity in formats
|
|
||||||
? getTimeFormatter(formats[granularity])
|
|
||||||
: smartDateVerboseFormatter;
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
## @superset-ui/legacy-plugin-chart-table
|
|
||||||
|
|
||||||
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-table.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-table.svg?style=flat-square)
|
|
||||||
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-legacy-plugin-chart-table&style=flat-square)](https://david-dm.org/apache-superset/superset-ui-plugins?path=plugins/superset-ui-legacy-plugin-chart-table)
|
|
||||||
|
|
||||||
This plugin provides Table for Superset.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
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 TableChartPlugin from '@superset-ui/legacy-plugin-chart-table';
|
|
||||||
|
|
||||||
new TableChartPlugin().configure({ key: 'table' }).register();
|
|
||||||
```
|
|
||||||
|
|
||||||
Then use it via `SuperChart`. See
|
|
||||||
[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-table)
|
|
||||||
for more details.
|
|
||||||
|
|
||||||
```js
|
|
||||||
<SuperChart
|
|
||||||
chartType="table"
|
|
||||||
width={600}
|
|
||||||
height={600}
|
|
||||||
formData={...}
|
|
||||||
queryData={{
|
|
||||||
data: {...},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
```
|
|
@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@superset-ui/legacy-plugin-chart-table",
|
|
||||||
"version": "0.11.20",
|
|
||||||
"description": "Superset Legacy Chart - Table",
|
|
||||||
"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-plugins.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"superset"
|
|
||||||
],
|
|
||||||
"author": "Superset",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/apache-superset/superset-ui-plugins/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/apache-superset/superset-ui-plugins#readme",
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@types/react-dom": "^16.9.6",
|
|
||||||
"datatables.net-bs": "^1.10.20",
|
|
||||||
"xss": "^1.0.6"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/datatables.net": "^1.10.18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@superset-ui/chart": "^0.12.0",
|
|
||||||
"@superset-ui/number-format": "^0.12.10",
|
|
||||||
"@superset-ui/query": "^0.12.8",
|
|
||||||
"@superset-ui/time-format": "^0.12.0",
|
|
||||||
"@superset-ui/translation": "^0.12.0",
|
|
||||||
"jquery": "^3.4.1",
|
|
||||||
"react": "^16.8.0",
|
|
||||||
"react-dom": "^16.8.0"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,261 +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 { t } from '@superset-ui/translation';
|
|
||||||
import React, { useEffect, createRef } from 'react';
|
|
||||||
import ReactDOMServer from 'react-dom/server';
|
|
||||||
import { formatNumber, NumberFormats } from '@superset-ui/number-format';
|
|
||||||
import { getTimeFormatter } from '@superset-ui/time-format';
|
|
||||||
import { filterXSS } from 'xss';
|
|
||||||
|
|
||||||
// initialize datatables.net
|
|
||||||
import $ from 'jquery';
|
|
||||||
import dt from 'datatables.net-bs/js/dataTables.bootstrap';
|
|
||||||
import 'datatables.net-bs/css/dataTables.bootstrap.css';
|
|
||||||
import './Table.css';
|
|
||||||
|
|
||||||
import { DataTableProps } from './transformProps';
|
|
||||||
|
|
||||||
// Depending on how the modules are imported, `dt` may be a CommonJS init function,
|
|
||||||
// or the DataTable class itself. In case it is the former, we'd need to tell it
|
|
||||||
// where is jQuery.
|
|
||||||
if (!dt.$) {
|
|
||||||
dt(window, $);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { PERCENT_3_POINT } = NumberFormats;
|
|
||||||
const isProbablyHTML = (text: string) => /<[^>]+>/.test(text);
|
|
||||||
|
|
||||||
export default function ReactDataTable(props: DataTableProps) {
|
|
||||||
const {
|
|
||||||
data,
|
|
||||||
height,
|
|
||||||
alignPositiveNegative = false,
|
|
||||||
colorPositiveNegative = false,
|
|
||||||
columns,
|
|
||||||
includeSearch = false,
|
|
||||||
metrics: aggMetrics,
|
|
||||||
pageLength,
|
|
||||||
percentMetrics,
|
|
||||||
tableTimestampFormat,
|
|
||||||
// orderDesc,
|
|
||||||
// TODO: add back the broken dashboard filters feature
|
|
||||||
// filters = {},
|
|
||||||
// onAddFilter = NOOP,
|
|
||||||
// onRemoveFilter = NOOP,
|
|
||||||
// tableFilter,
|
|
||||||
// timeseriesLimitMetric,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const formatTimestamp = getTimeFormatter(tableTimestampFormat);
|
|
||||||
const metrics = (aggMetrics || [])
|
|
||||||
.concat(percentMetrics || [])
|
|
||||||
// actual records must be of numeric types as well
|
|
||||||
.filter(m => data[0] && typeof data[0][m] === 'number');
|
|
||||||
|
|
||||||
// check whethere a key is a metric
|
|
||||||
const metricsSet = new Set(aggMetrics);
|
|
||||||
const percentMetricsSet = new Set(percentMetrics);
|
|
||||||
|
|
||||||
// collect min/max for rendering bars
|
|
||||||
const maxes: { [key: string]: number } = {};
|
|
||||||
const mins: { [key: string]: number } = {};
|
|
||||||
columns.forEach(({ key }) => {
|
|
||||||
const vals = data.map(row => row[key]);
|
|
||||||
if (metrics.includes(key)) {
|
|
||||||
const nums = vals as number[];
|
|
||||||
if (alignPositiveNegative) {
|
|
||||||
maxes[key] = Math.max(...nums.map(Math.abs));
|
|
||||||
} else {
|
|
||||||
maxes[key] = Math.max(...nums);
|
|
||||||
mins[key] = Math.min(...nums);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const viewportHeight = Math.min(height, window.innerHeight);
|
|
||||||
const pageLengthChoices = [10, 25, 40, 50, 75, 100, 150, 200];
|
|
||||||
const hasPagination = pageLength > 0;
|
|
||||||
|
|
||||||
const rootElem = createRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjust styles after rendering the table
|
|
||||||
*/
|
|
||||||
function drawCallback(this: DataTables.JQueryDataTables) {
|
|
||||||
const root = rootElem.current as HTMLElement;
|
|
||||||
// force smaller pagination, because datatables-bs hard-corded pagination styles
|
|
||||||
$('.pagination', root).addClass('pagination-sm');
|
|
||||||
// display tr rows on current page
|
|
||||||
$('tr', root).css('display', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format text for cell value
|
|
||||||
*/
|
|
||||||
function cellText(key: string, format: string | undefined, val: unknown) {
|
|
||||||
if (key === '__timestamp') {
|
|
||||||
return formatTimestamp(val);
|
|
||||||
}
|
|
||||||
if (typeof val === 'string') {
|
|
||||||
return filterXSS(val, { stripIgnoreTag: true });
|
|
||||||
}
|
|
||||||
if (percentMetricsSet.has(key)) {
|
|
||||||
// in case percent metric can specify percent format in the future
|
|
||||||
return formatNumber(format || PERCENT_3_POINT, val as number);
|
|
||||||
}
|
|
||||||
if (metricsSet.has(key)) {
|
|
||||||
// default format '' will return human readable numbers (e.g. 50M, 33k)
|
|
||||||
return formatNumber(format, val as number);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cell background to render columns as horizontal bar chart
|
|
||||||
*/
|
|
||||||
function cellBar(key: string, val: number) {
|
|
||||||
const r = colorPositiveNegative && val < 0 ? 150 : 0;
|
|
||||||
if (alignPositiveNegative) {
|
|
||||||
const perc = Math.abs(Math.round((val / maxes[key]) * 100));
|
|
||||||
// The 0.01 to 0.001 is a workaround for what appears to be a
|
|
||||||
// CSS rendering bug on flat, transparent colors
|
|
||||||
return (
|
|
||||||
`linear-gradient(to right, rgba(${r},0,0,0.2), rgba(${r},0,0,0.2) ${perc}%, ` +
|
|
||||||
`rgba(0,0,0,0.01) ${perc}%, rgba(0,0,0,0.001) 100%)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const posExtent = Math.abs(Math.max(maxes[key], 0));
|
|
||||||
const negExtent = Math.abs(Math.min(mins[key], 0));
|
|
||||||
const tot = posExtent + negExtent;
|
|
||||||
const perc1 = Math.round((Math.min(negExtent + val, negExtent) / tot) * 100);
|
|
||||||
const perc2 = Math.round((Math.abs(val) / tot) * 100);
|
|
||||||
// The 0.01 to 0.001 is a workaround for what appears to be a
|
|
||||||
// CSS rendering bug on flat, transparent colors
|
|
||||||
return (
|
|
||||||
`linear-gradient(to right, rgba(0,0,0,0.01), rgba(0,0,0,0.001) ${perc1}%, ` +
|
|
||||||
`rgba(${r},0,0,0.2) ${perc1}%, rgba(${r},0,0,0.2) ${perc1 + perc2}%, ` +
|
|
||||||
`rgba(0,0,0,0.01) ${perc1 + perc2}%, rgba(0,0,0,0.001) 100%)`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
aaSorting: [], // initial sorting order, reset to [] to use backend ordering
|
|
||||||
autoWidth: false,
|
|
||||||
paging: hasPagination,
|
|
||||||
pagingType: 'first_last_numbers',
|
|
||||||
pageLength,
|
|
||||||
lengthMenu: [
|
|
||||||
[...pageLengthChoices, -1],
|
|
||||||
[...pageLengthChoices, t('All')],
|
|
||||||
],
|
|
||||||
searching: includeSearch,
|
|
||||||
language: {
|
|
||||||
paginate: {
|
|
||||||
first: t('First'),
|
|
||||||
last: t('Last'),
|
|
||||||
previous: t('Previous'),
|
|
||||||
next: t('Next'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bInfo: false,
|
|
||||||
scrollY: `${viewportHeight}px`,
|
|
||||||
scrollCollapse: true,
|
|
||||||
scrollX: true,
|
|
||||||
drawCallback,
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const $root = $(rootElem.current as HTMLElement);
|
|
||||||
const dataTable = $root.find('table').DataTable(options);
|
|
||||||
|
|
||||||
// adjust table height
|
|
||||||
const scrollHeadHeight = $root.find('.dataTables_scrollHead').height() || 0;
|
|
||||||
const paginationHeight = $root.find('.dataTables_paginate').height() || 0;
|
|
||||||
const searchBarHeight =
|
|
||||||
$root.find('.dataTables_length,.dataTables_filter').closest('.row').height() || 0;
|
|
||||||
const scrollBodyHeight = viewportHeight - scrollHeadHeight - paginationHeight - searchBarHeight;
|
|
||||||
$root.find('.dataTables_scrollBody').css('max-height', scrollBodyHeight);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// there may be weird lifecycle issues, so put destroy in try/catch
|
|
||||||
try {
|
|
||||||
dataTable.destroy();
|
|
||||||
// reset height
|
|
||||||
$root.find('.dataTables_scrollBody').css('max-height', '');
|
|
||||||
} catch (error) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const tableElement = (
|
|
||||||
<table className="table table-striped table-condensed table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{columns.map(col => (
|
|
||||||
// by default all columns will have sorting
|
|
||||||
<th key={col.key} className="sorting" title={col.label}>
|
|
||||||
{col.label}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{data.map((record, i) => (
|
|
||||||
<tr
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
key={i}
|
|
||||||
// hide rows after first page makes the initial render faster (less layout computation)
|
|
||||||
style={{ display: pageLength > 0 && i >= pageLength ? 'none' : undefined }}
|
|
||||||
>
|
|
||||||
{columns.map(({ key, format }) => {
|
|
||||||
const val = record[key];
|
|
||||||
const keyIsMetric = metricsSet.has(key);
|
|
||||||
const text = cellText(key, format, val);
|
|
||||||
const isHtml = !keyIsMetric && isProbablyHTML(text);
|
|
||||||
return (
|
|
||||||
<td
|
|
||||||
key={key}
|
|
||||||
// only set innerHTML for actual html content, this saves time
|
|
||||||
dangerouslySetInnerHTML={isHtml ? { __html: text } : undefined}
|
|
||||||
data-sort={val}
|
|
||||||
className={keyIsMetric ? 'text-right' : ''}
|
|
||||||
style={{
|
|
||||||
backgroundImage: keyIsMetric ? cellBar(key, val as number) : undefined,
|
|
||||||
}}
|
|
||||||
title={keyIsMetric || percentMetricsSet.has(key) ? String(val) : ''}
|
|
||||||
>
|
|
||||||
{isHtml ? null : text}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{ __html: ReactDOMServer.renderToStaticMarkup(tableElement) }}
|
|
||||||
ref={rootElem}
|
|
||||||
className="superset-legacy-chart-table"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,43 +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-legacy-chart-table {
|
|
||||||
margin: 0px auto;
|
|
||||||
}
|
|
||||||
.superset-legacy-chart-table table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.superset-legacy-chart-table .dt-metric {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.superset-legacy-chart-table div.dataTables_wrapper div.dataTables_paginate {
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
.superset-legacy-chart-table div.dataTables_wrapper div.dataTables_paginate ul.pagination {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.superset-legacy-chart-table table.table thead th.sorting:after,
|
|
||||||
.superset-legacy-chart-table table.table thead th.sorting_asc:after,
|
|
||||||
.superset-legacy-chart-table table.table thead th.sorting_desc:after {
|
|
||||||
top: auto;
|
|
||||||
bottom: 6px;
|
|
||||||
}
|
|
||||||
.superset-legacy-chart-table td {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
Before Width: | Height: | Size: 107 KiB |
@ -1,40 +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 { t } from '@superset-ui/translation';
|
|
||||||
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
|
|
||||||
import transformProps from './transformProps';
|
|
||||||
import thumbnail from './images/thumbnail.png';
|
|
||||||
|
|
||||||
const metadata = new ChartMetadata({
|
|
||||||
canBeAnnotationTypes: ['EVENT', 'INTERVAL'],
|
|
||||||
description: '',
|
|
||||||
name: t('Table'),
|
|
||||||
thumbnail,
|
|
||||||
useLegacyApi: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class TableChartPlugin extends ChartPlugin {
|
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
loadChart: () => import('./ReactDataTable'),
|
|
||||||
metadata,
|
|
||||||
transformProps,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +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 { ChartProps } from '@superset-ui/chart';
|
|
||||||
import { QueryFormDataMetric } from '@superset-ui/query';
|
|
||||||
|
|
||||||
interface DataRecord {
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DataColumnMeta {
|
|
||||||
// `key` is what is called `label` in the input props
|
|
||||||
key: string;
|
|
||||||
// `label` is verbose column name used for rendering
|
|
||||||
label: string;
|
|
||||||
format?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DataTableProps {
|
|
||||||
// Each object is { field1: value1, field2: value2 }
|
|
||||||
data: DataRecord[];
|
|
||||||
height: number;
|
|
||||||
alignPositiveNegative: boolean;
|
|
||||||
colorPositiveNegative: boolean;
|
|
||||||
columns: DataColumnMeta[];
|
|
||||||
metrics: string[];
|
|
||||||
percentMetrics: string[];
|
|
||||||
includeSearch: boolean;
|
|
||||||
orderDesc: boolean;
|
|
||||||
pageLength: number;
|
|
||||||
tableTimestampFormat: string;
|
|
||||||
// TODO: add filters back or clean up
|
|
||||||
// filters: object;
|
|
||||||
// onAddFilter?: (key: string, value: number[]) => void;
|
|
||||||
// onRemoveFilter?: (key: string, value: number[]) => void;
|
|
||||||
// tableFilter: boolean;
|
|
||||||
// timeseriesLimitMetric: string | object;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consolidate list of metrics to string, identified by its unique identifier
|
|
||||||
*/
|
|
||||||
const consolidateMetricShape = (metric: QueryFormDataMetric) => {
|
|
||||||
if (typeof metric === 'string') return metric;
|
|
||||||
// even thought `metric.optionName` is more unique, it's not used
|
|
||||||
// anywhere else in `queryData` and cannot be used to access `data.records`.
|
|
||||||
// The records are still keyed by `metric.label`.
|
|
||||||
return metric.label;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function transformProps(chartProps: ChartProps): DataTableProps {
|
|
||||||
const { height, datasource, formData, queryData } = chartProps;
|
|
||||||
|
|
||||||
const {
|
|
||||||
alignPn,
|
|
||||||
colorPn,
|
|
||||||
includeSearch,
|
|
||||||
orderDesc,
|
|
||||||
pageLength,
|
|
||||||
metrics: metrics_,
|
|
||||||
percentMetrics: percentMetrics_,
|
|
||||||
tableTimestampFormat,
|
|
||||||
} = formData;
|
|
||||||
const { columnFormats, verboseMap } = datasource;
|
|
||||||
const { records, columns: columns_ } = queryData.data;
|
|
||||||
const metrics = (metrics_ ?? []).map(consolidateMetricShape);
|
|
||||||
// percent metrics always starts with a '%' sign.
|
|
||||||
const percentMetrics = (percentMetrics_ ?? [])
|
|
||||||
.map(consolidateMetricShape)
|
|
||||||
.map((x: string) => `%${x}`);
|
|
||||||
const columns = columns_.map((key: string) => {
|
|
||||||
let label = verboseMap[key] || key;
|
|
||||||
|
|
||||||
// make sure there is a " " after "%" for percent metrics
|
|
||||||
if (label[0] === '%' && label[1] !== ' ') {
|
|
||||||
label = `% ${label.slice(1)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
label,
|
|
||||||
format: columnFormats?.[key],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
height,
|
|
||||||
data: records,
|
|
||||||
columns,
|
|
||||||
metrics,
|
|
||||||
percentMetrics,
|
|
||||||
alignPositiveNegative: alignPn,
|
|
||||||
colorPositiveNegative: colorPn,
|
|
||||||
includeSearch,
|
|
||||||
orderDesc,
|
|
||||||
pageLength: pageLength && parseInt(pageLength, 10),
|
|
||||||
tableTimestampFormat,
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,58 +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 React from 'react';
|
|
||||||
import { mount, CommonWrapper } from 'enzyme';
|
|
||||||
import ReactDataTable from '../src/ReactDataTable';
|
|
||||||
import transformProps from '../src/transformProps';
|
|
||||||
import testData from './testData';
|
|
||||||
|
|
||||||
describe('legacy-table', () => {
|
|
||||||
// Can test more prop transformation here. Not needed for now.
|
|
||||||
describe('transformProps', () => {});
|
|
||||||
|
|
||||||
describe('ReactDataTable', () => {
|
|
||||||
let wrap: CommonWrapper; // the ReactDataTable wraper
|
|
||||||
|
|
||||||
it('render basic data', () => {
|
|
||||||
wrap = mount(<ReactDataTable {...transformProps(testData.basic)} />);
|
|
||||||
const tree = wrap.render(); // returns a CheerioWrapper with jQuery-like API
|
|
||||||
const cells = tree.find('td');
|
|
||||||
expect(tree.hasClass('superset-legacy-chart-table')).toEqual(true);
|
|
||||||
expect(cells).toHaveLength(4);
|
|
||||||
expect(cells.eq(0).text()).toEqual('Michael');
|
|
||||||
expect(cells.eq(3).attr('data-sort')).toEqual('2467');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('render advanced data', () => {
|
|
||||||
// should successfull rerender with new props
|
|
||||||
wrap.setProps(transformProps(testData.advanced));
|
|
||||||
const tree = wrap.render();
|
|
||||||
const cells = tree.find('td');
|
|
||||||
expect(tree.find('th').eq(1).text()).toEqual('Sum of Num');
|
|
||||||
expect(cells.eq(2).text()).toEqual('12.346%');
|
|
||||||
expect(cells.eq(4).text()).toEqual('2.47k');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('render empty data', () => {
|
|
||||||
wrap.setProps(transformProps(testData.empty));
|
|
||||||
const tree = wrap.render();
|
|
||||||
expect(tree.text()).toContain('No data available in table');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,120 +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 { ChartProps } from '@superset-ui/chart';
|
|
||||||
|
|
||||||
const basicFormData = {
|
|
||||||
alignPn: false,
|
|
||||||
colorPn: false,
|
|
||||||
includeSearch: false,
|
|
||||||
orderDesc: true,
|
|
||||||
pageLength: 0,
|
|
||||||
metrics: [],
|
|
||||||
percentMetrics: null,
|
|
||||||
timeseriesLimitMetric: null,
|
|
||||||
tableFilter: false,
|
|
||||||
tableTimestampFormat: '%Y-%m-%d %H:%M:%S',
|
|
||||||
};
|
|
||||||
|
|
||||||
const basicChartProps = {
|
|
||||||
width: 200,
|
|
||||||
height: 500,
|
|
||||||
annotationData: {},
|
|
||||||
datasource: {
|
|
||||||
columnFormats: {},
|
|
||||||
verboseMap: {},
|
|
||||||
},
|
|
||||||
rawDatasource: {},
|
|
||||||
rawFormData: {},
|
|
||||||
hooks: {},
|
|
||||||
initialValues: {},
|
|
||||||
queryData: {
|
|
||||||
data: {
|
|
||||||
columns: [],
|
|
||||||
records: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
formData: basicFormData,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic data input
|
|
||||||
*/
|
|
||||||
const basic: ChartProps = {
|
|
||||||
...basicChartProps,
|
|
||||||
queryData: {
|
|
||||||
data: {
|
|
||||||
columns: ['name', 'sum__num'],
|
|
||||||
records: [
|
|
||||||
{
|
|
||||||
name: 'Michael',
|
|
||||||
sum__num: 2467063,
|
|
||||||
'%pct_nice': 0.123456,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Joe',
|
|
||||||
sum__num: 2467,
|
|
||||||
'%pct_nice': 0.00001,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advanced data input with
|
|
||||||
* - verbose map
|
|
||||||
* - metric columns
|
|
||||||
*/
|
|
||||||
const advanced: ChartProps = {
|
|
||||||
...basic,
|
|
||||||
datasource: {
|
|
||||||
columnFormats: {},
|
|
||||||
verboseMap: {
|
|
||||||
sum__num: 'Sum of Num',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
formData: {
|
|
||||||
...basicFormData,
|
|
||||||
metrics: ['sum__num'],
|
|
||||||
percentMetrics: ['pct_nice'],
|
|
||||||
},
|
|
||||||
queryData: {
|
|
||||||
data: {
|
|
||||||
columns: ['name', 'sum__num', '%pct_nice'],
|
|
||||||
records: [...basic.queryData.data.records],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const empty = {
|
|
||||||
...advanced,
|
|
||||||
queryData: {
|
|
||||||
...advanced.queryData,
|
|
||||||
data: {
|
|
||||||
...advanced.queryData.data,
|
|
||||||
records: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
basic,
|
|
||||||
advanced,
|
|
||||||
empty,
|
|
||||||
};
|
|
@ -1,2 +0,0 @@
|
|||||||
declare module 'datatables.net-bs/js/dataTables.bootstrap';
|
|
||||||
declare module '*.png';
|
|
@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* Build only plugins specified by globs
|
|
||||||
*/
|
|
||||||
const { spawnSync } = require('child_process');
|
|
||||||
|
|
||||||
const glob = process.argv[2];
|
|
||||||
const extraArgs = process.argv.slice(2);
|
|
||||||
|
|
||||||
process.env.PATH = `./node_modules/.bin:${process.env.PATH}`;
|
|
||||||
|
|
||||||
const run = cmd => {
|
|
||||||
console.log(`>> ${cmd}`);
|
|
||||||
const [p, ...args] = cmd.split(' ');
|
|
||||||
const runner = spawnSync;
|
|
||||||
const { status } = runner(p, args, { stdio: 'inherit' });
|
|
||||||
if (status !== 0) {
|
|
||||||
process.exit(status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (glob) {
|
|
||||||
run(`nimbus prettier plugins/${glob}/{src,test}/**/*.{js,jsx,ts,tsx,css}"`);
|
|
||||||
// lint is slow, so not turning it on by default
|
|
||||||
if (extraArgs.includes('--lint')) {
|
|
||||||
run(`nimbus eslint plugins/${glob}/{src,test}`);
|
|
||||||
}
|
|
||||||
run(`nimbus babel --clean --workspaces="@superset-ui/${glob}"`);
|
|
||||||
run(`nimbus babel --clean --workspaces="@superset-ui/${glob}" --esm`);
|
|
||||||
run(`nimbus typescript --build --workspaces="@superset-ui/${glob}"`);
|
|
||||||
require('./buildAssets');
|
|
||||||
} else {
|
|
||||||
run('yarn build');
|
|
||||||
}
|
|
@ -1,32 +1,23 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies, no-console */
|
/* eslint-disable import/no-extraneous-dependencies, no-console */
|
||||||
const fg = require('fast-glob');
|
const fg = require('fast-glob');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const pkgGlob = process.argv[2] || '*';
|
|
||||||
|
|
||||||
const packages = fg.sync([`{packages,plugins}/${pkgGlob}`], {
|
const packages = fg.sync(['packages/*'], {
|
||||||
onlyDirectories: true,
|
onlyDirectories: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Copying asset files from package {src} to {lib,esm}...');
|
|
||||||
packages.forEach(pkg => {
|
packages.forEach(pkg => {
|
||||||
const assets = fg.sync([`${pkg}/src/**/*.{png,gif,jpg,css,geojson}`]);
|
const assets = fg.sync([`${pkg}/src/**/*.{png,gif,jpg,css,geojson}`]);
|
||||||
assets.forEach(filePath => {
|
assets.forEach(filePath => {
|
||||||
['lib', 'esm']
|
const newPaths = ['lib', 'esm'].map(dir => filePath.replace(`${pkg}/src`, `${pkg}/${dir}`));
|
||||||
.map(dir => filePath.replace(`${pkg}/src`, `${pkg}/${dir}`))
|
newPaths.forEach(p => {
|
||||||
.forEach(newFilePath => {
|
fs.copy(filePath, p, err => {
|
||||||
fs.copy(filePath, newFilePath, err => {
|
if (err) {
|
||||||
if (err) {
|
console.error(err);
|
||||||
console.error(err);
|
}
|
||||||
}
|
console.log(`Copy ${filePath}`);
|
||||||
});
|
console.log(`=> to ${p}`);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
if (assets.length > 0) {
|
|
||||||
console.log(
|
|
||||||
` Copied ${assets.length.toString().padStart(2)} asset files for ${pkg.replace(
|
|
||||||
'packages/superset-ui-',
|
|
||||||
'',
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user