Revert "build: try to merge superset-ui-plugins"

This reverts commit 823126633ea0743254f78b0a74fe83c68ee6438f.
This commit is contained in:
Jesse Yang 2020-04-01 12:45:42 -07:00 committed by Yongjie Zhao
parent 6f42844366
commit 4d0d05f71f
91 changed files with 3308 additions and 30129 deletions

View File

@ -26,7 +26,6 @@ lib/
public/
node_modules/
tmp/
_gh-pages/
# Custom
*.map
@ -44,11 +43,12 @@ jest.config.js
prettier.config.js
tsconfig.eslint.json
tsconfig.json
!demo/tsconfig.json
tsconfig.options.json
*.tsbuildinfo
webpack.config.js
# Ignore npm lock files, always use yarn.loock instead
# Lock files, libs should not have lock files
npm-shrinkwrap.json
package-lock.json
# disable to provide more stability for the ci builds
# yarn.lock

View File

@ -5,6 +5,14 @@ You can demo your changes by running the storybook demo locally with the followi
```sh
yarn install
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
```

View File

@ -2,8 +2,7 @@
"lerna": "3.2.1",
"npmClient": "yarn",
"packages": [
"packages/*",
"plugins/*"
"packages/*"
],
"useWorkspaces": true,
"version": "0.12.12"

View File

@ -9,10 +9,7 @@
"babel:cjs": "nimbus babel --clean --workspaces=\"@superset-ui/!(demo|generator-superset)\"",
"babel:esm": "nimbus babel --clean --workspaces=\"@superset-ui/!(demo|generator-superset)\" --esm",
"build:assets": "node ./scripts/buildAssets.js",
"demo": "cd packages/demo && yarn demo:build",
"storybook": "cd packages/demo && yarn storybook",
"sb": "yarn storybook",
"clean": "rm -rf ./{packages,plugins}/**/{lib,esm}",
"clean": "rm -rf ./packages/**/{lib,esm}",
"commit": "superset-commit",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 10",
"format": "yarn prettier --write",
@ -45,12 +42,12 @@
],
"license": "Apache-2.0",
"devDependencies": {
"@airbnb/config-babel": "^3.1.0",
"@airbnb/config-eslint": "^3.1.0",
"@airbnb/config-jest": "^3.0.1",
"@airbnb/config-prettier": "^3.1.0",
"@airbnb/config-typescript": "^3.0.1",
"@airbnb/nimbus": "^3.1.1",
"@airbnb/config-babel": "^2.1.3",
"@airbnb/config-eslint": "^2.1.3",
"@airbnb/config-jest": "^2.1.3",
"@airbnb/config-prettier": "^2.0.4",
"@airbnb/config-typescript": "^2.1.2",
"@airbnb/nimbus": "^2.1.3",
"@superset-ui/commit-config": "^0.0.9",
"@types/enzyme": "^3.10.3",
"@types/jest": "^25.1.1",
@ -62,13 +59,12 @@
"fast-glob": "^3.0.1",
"fs-extra": "^8.0.1",
"husky": "^4.2.1",
"identity-obj-proxy": "^3.0.0",
"jest-mock-console": "^1.0.0",
"lerna": "^3.15.0",
"lint-staged": "^10.0.3",
"react": "^16.9.0",
"react-test-renderer": "^16.9.0",
"react-dom": "^16.9.0",
"react-test-renderer": "^16.9.0"
"react": "^16.9.0"
},
"engines": {
"node": ">=10.10.0",
@ -76,8 +72,7 @@
"yarn": ">=1.13.0"
},
"workspaces": [
"./packages/*",
"./plugins/*"
"./packages/*"
],
"browserslist": [
"last 3 chrome versions",
@ -105,10 +100,6 @@
"globals": {
"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",
"setupFilesAfterEnv": [
"@airbnb/config-jest/enzyme"
@ -128,13 +119,7 @@
}
]
},
"prettier": {
"arrowParens": "avoid"
},
"eslint": {
"rules": {
"arrow-parens": ["warn", "as-needed"]
},
"overrides": [
{
"files": "./packages/generator-superset/**/*.test.{js,jsx,ts,tsx}",
@ -156,27 +141,14 @@
"files": "*.{js,jsx,ts,tsx}",
"rules": {
"react/jsx-no-literals": "off",
"@typescript-eslint/no-explicit-any": [
"warn",
{
"fixToUnknown": false
}
]
}
},
{
"files": "./scripts/*",
"env": {
"node": true
"@typescript-eslint/no-explicit-any": ["warn", { "fixToUnknown": false }]
}
}
]
},
"typescript": {
"compilerOptions": {
"emitDeclarationOnly": true,
"composite": true,
"resolveJsonModule": true
"emitDeclarationOnly": true
}
}
},
@ -187,7 +159,7 @@
}
},
"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",
"git add"
]

View File

@ -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;
},
};

View File

@ -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;
}

View File

@ -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,
}}
/>
);

View File

@ -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,
},
];

View File

@ -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',
}}
/>
);

View File

@ -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>
);
};

View File

@ -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,
},
],
};

View File

@ -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' };

View File

@ -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} />;
};

View File

@ -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} />
));

View File

@ -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>
);
};

View File

@ -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",
]
}

View File

@ -42,6 +42,11 @@ describe('TooltipTable', () => {
);
expect(wrapper.find('tbody')).toHaveLength(1);
expect(wrapper.find('tr')).toHaveLength(3);
expect(wrapper.find('tr > td').first().text()).toEqual('Cersei');
expect(
wrapper
.find('tr > td')
.first()
.text(),
).toEqual('Cersei');
});
});

View File

@ -42,7 +42,7 @@ export const TestComponent = ({
{[width, height].join('x')}
</div>
<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>
);

View File

@ -247,7 +247,11 @@ describe('SuperChart', () => {
return promiseTimeout(() => {
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('height')).toEqual('125px');
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);
@ -268,7 +272,11 @@ describe('SuperChart', () => {
return promiseTimeout(() => {
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('height')).toEqual('25%');
expect(renderedWrapper.find('div.test-component')).toHaveLength(1);

View File

@ -94,7 +94,12 @@ describe('SuperChartCore', () => {
);
return promiseTimeout(() => {
expect(wrapper.render().find('.message').text()).toEqual('hulk');
expect(
wrapper
.render()
.find('.message')
.text(),
).toEqual('hulk');
});
});
it('uses preTransformProps when specified', () => {
@ -110,7 +115,12 @@ describe('SuperChartCore', () => {
);
return promiseTimeout(() => {
expect(wrapper.render().find('.message').text()).toEqual('hulk');
expect(
wrapper
.render()
.find('.message')
.text(),
).toEqual('hulk');
});
});
it('uses postTransformProps when specified', () => {
@ -122,7 +132,12 @@ describe('SuperChartCore', () => {
);
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', () => {

View File

@ -64,7 +64,9 @@ export function getNamespace(name: string = DEFAULT_NAMESPACE) {
}
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) {

View File

@ -2,7 +2,7 @@ import { scaleLinear } from 'd3-scale';
import ColorScheme, { ColorSchemeConfig } from './ColorScheme';
function range(count: number) {
const values: number[] = [];
const values = [];
for (let i = 0; i < count; i += 1) {
values.push(i);
}
@ -31,7 +31,10 @@ export default class SequentialScheme extends ColorScheme {
const denominator = this.colors.length - 1;
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[] {

View File

@ -127,14 +127,18 @@ describe('CategoricalColorNamespace', () => {
it('getColor(value) returns a color from default scheme in default namespace', () => {
const value = 'dog';
const color = getColor(value);
const color2 = getNamespace().getScale().getColor(value);
const color2 = getNamespace()
.getScale()
.getColor(value);
expect(color).toBe(color2);
});
it('getColor(value, scheme) returns a color from specified scheme in default namespace', () => {
const value = 'dog';
const scheme = 'testColors';
const color = getColor(value, scheme);
const color2 = getNamespace().getScale(scheme).getColor(value);
const color2 = getNamespace()
.getScale(scheme)
.getColor(value);
expect(color).toBe(color2);
});
it('getColor(value, scheme, namespace) returns a color from specified scheme in specified namespace', () => {
@ -142,7 +146,9 @@ describe('CategoricalColorNamespace', () => {
const scheme = 'testColors';
const namespace = 'test-getColor';
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);
});
});

View File

@ -6,7 +6,7 @@ export default class ExtensibleFunction extends Function {
constructor(fn: Function) {
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);
}
}

View File

@ -1,4 +1,4 @@
import { ExtensibleFunction } from '../../src';
import ExtensibleFunction from '../../src/models/ExtensibleFunction';
describe('ExtensibleFunction', () => {
class Func1 extends ExtensibleFunction {

View File

@ -1,4 +1,4 @@
import { Plugin } from '../../src';
import Plugin from '../../src/models/Plugin';
describe('Plugin', () => {
it('exists', () => {

View File

@ -1,4 +1,5 @@
import { Plugin, Preset } from '../../src';
import Plugin from '../../src/models/Plugin';
import Preset from '../../src/models/Preset';
describe('Preset', () => {
it('exists', () => {

View File

@ -1,4 +1,5 @@
import { Registry, RegistryWithDefaultKey } from '../../src';
import RegistryWithDefaultKey from '../../src/models/RegistryWithDefaultKey';
import Registry from '../../src/models/Registry';
describe('RegistryWithDefaultKey', () => {
let registry: RegistryWithDefaultKey<number>;
@ -30,7 +31,10 @@ describe('RegistryWithDefaultKey', () => {
describe('.get()', () => {
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', () => {
expect(registry.get()).toEqual(100);

View File

@ -1,4 +1,4 @@
import { convertKeysToCamelCase } from '../../src';
import convertKeysToCamelCase from '../../src/utils/convertKeysToCamelCase';
describe('convertKeysToCamelCase(object)', () => {
it('returns undefined for undefined input', () => {

View File

@ -1,4 +1,4 @@
import { isDefined } from '../../src';
import isDefined from '../../src/utils/isDefined';
describe('isDefined(value)', () => {
it('returns true if value is not null and not undefined', () => {

View File

@ -1,4 +1,4 @@
import { isRequired } from '../../src';
import isRequired from '../../src/utils/isRequired';
describe('isRequired(field)', () => {
it('should throw error with the given field in the message', () => {

View File

@ -1,4 +1,4 @@
import { makeSingleton } from '../../src';
import makeSingleton from '../../src/utils/makeSingleton';
describe('makeSingleton()', () => {
class Dog {

View File

@ -0,0 +1,4 @@
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
import '@storybook/addon-knobs/register';
import 'storybook-addon-jsx/register';

View File

@ -1,10 +1,4 @@
import { addParameters, addDecorator } from '@storybook/react';
import { jsxDecorator } from 'storybook-addon-jsx';
import 'bootstrap/dist/css/bootstrap.min.css';
import './storybook.css';
addDecorator(jsxDecorator);
import { addParameters, configure } from '@storybook/react';
addParameters({
options: {
@ -21,8 +15,12 @@ addParameters({
sidebarAnimations: true,
sortStoriesByKind: false,
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);

View File

@ -0,0 +1,8 @@
html,
body,
#root {
height: 100%;
font-family: BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;
font-weight: 200;
color: #484848;
}

View File

@ -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;
};

View File

@ -6,9 +6,9 @@
"main": "index.js",
"scripts": {
"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",
"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"
},
"repository": {
@ -29,39 +29,32 @@
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"dependencies": {
"@storybook/addon-info": "^5.3.18",
"@storybook/addon-actions": "^5.3.18",
"@storybook/addon-knobs": "^5.3.18",
"@storybook/addon-links": "^5.3.18",
"@storybook/addons": "^5.3.18",
"@storybook/react": "^5.3.18",
"@storybook/preset-typescript": "^3.0.0",
"@superset-ui/chart": "*",
"@superset-ui/color": "*",
"@superset-ui/connection": "*",
"@storybook/addon-actions": "^5.0.6",
"@storybook/addon-knobs": "^5.2.3",
"@storybook/addon-links": "^5.0.6",
"@storybook/addons": "^5.0.6",
"@storybook/react": "^5.2.3",
"@superset-ui/chart": "0.12.12",
"@superset-ui/color": "0.12.12",
"@superset-ui/connection": "0.12.12",
"@superset-ui/legacy-plugin-chart-sankey": "^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-table": "*",
"@superset-ui/legacy-preset-chart-big-number": "*",
"@superset-ui/number-format": "*",
"@superset-ui/legacy-preset-chart-big-number": "^0.11.15",
"@superset-ui/number-format": "0.12.12",
"@superset-ui/plugin-chart-word-cloud": "^0.11.15",
"@superset-ui/query": "*",
"@superset-ui/time-format": "*",
"@superset-ui/translation": "*",
"@superset-ui/query": "0.12.12",
"@superset-ui/time-format": "0.12.12",
"@types/storybook__react": "4.0.2",
"bootstrap": "^3.4.1",
"bootstrap": "^4.3.1",
"core-js": "3.6.4",
"gh-pages": "^2.2.0",
"jquery": "^3.4.1",
"react": "^16.6.0",
"storybook-addon-jsx": "^7.1.0"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"typescript": "^3.8.3",
"ts-loader": "^6.2.2",
"babel-loader": "^8.1.0",
"fork-ts-checker-webpack-plugin": "^4.1.2"
"@babel/core": "^7.8.4",
"@babel/plugin-transform-runtime": "^7.8.3",
"babel-loader": "^8.0.6",
"gh-pages": "^2.2.0"
}
}

View File

@ -26,7 +26,11 @@ export default class Expandable extends React.Component<Props, State> {
return (
<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}`}
</button>
<br />

View File

@ -73,7 +73,7 @@ export default class VerifyCORS extends React.Component<Props, State> {
.then(response => this.setState({ didVerify: true, error: undefined, payload: response }))
.catch((error: Response) => {
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
`@superset-ui/connection` story. <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
</button>
<br />

View File

@ -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);
});
}
});

View File

@ -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',
},
];

View File

@ -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',
},
];

View File

@ -0,0 +1,6 @@
import ChartDataProviderStories from './ChartDataProviderStories';
import SuperChartStories from './SuperChartStories';
export default {
examples: [...ChartDataProviderStories, ...SuperChartStories],
};

View File

@ -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',
},
];

View File

@ -1,6 +1,5 @@
/* eslint react/prop-types: 'off' */
import React from 'react';
import './color-styles.css';
export default function RenderPalettes({ title, palettes }) {
return (

View File

@ -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',
},
];

View File

@ -0,0 +1,7 @@
import CategoricalStories from './CategoricalStories';
import SequentialStories from './SequentialStories';
import './color-styles.css';
export default {
examples: [...CategoricalStories, ...SequentialStories],
};

View File

@ -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',
},
];

View File

@ -0,0 +1,5 @@
import ConnectionStories from './ConnectionStories';
export default {
examples: [...ConnectionStories],
};

View File

@ -93,7 +93,7 @@ class NumberFormatValidator extends React.PureComponent {
</tr>
</thead>
<tbody>
{testValues.map((v) => (
{testValues.map(v => (
<tr key={v}>
<td>
<code>{`${v}`}</code>
@ -115,8 +115,10 @@ class NumberFormatValidator extends React.PureComponent {
NumberFormatValidator.propTypes = propTypes;
NumberFormatValidator.defaultProps = defaultProps;
export default {
title: 'Core Packages|@superset-ui/number-format',
};
export const validator = () => <NumberFormatValidator />;
export default [
{
renderStory: () => <NumberFormatValidator />,
storyName: 'Validator',
storyPath: '@superset-ui/number-format',
},
];

View File

@ -0,0 +1,5 @@
import Stories from './Stories';
export default {
examples: [...Stories],
};

View File

@ -82,7 +82,7 @@ class TimeFormatValidator extends React.PureComponent {
</tr>
</thead>
<tbody>
{testValues.map((v) => (
{testValues.map(v => (
<tr key={v}>
<td>
<code>{v instanceof Date ? v.toUTCString() : `${v}`}</code>
@ -104,8 +104,10 @@ class TimeFormatValidator extends React.PureComponent {
TimeFormatValidator.propTypes = propTypes;
TimeFormatValidator.defaultProps = defaultProps;
export default {
title: 'Core Packages|@superset-ui/time-format',
};
export const validator = () => <TimeFormatValidator />;
export default [
{
renderStory: () => <TimeFormatValidator />,
storyName: 'Validator',
storyPath: '@superset-ui/time-format',
},
];

View File

@ -0,0 +1,5 @@
import Stories from './Stories';
export default {
examples: [...Stories],
};

View File

@ -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';

View File

@ -166,9 +166,9 @@ describe('mergeMargin(margin1, margin2, mode?)', () => {
it('if there are NaN or null, use another value', () => {
expect(
mergeMargin(
// @ts-ignore to let us pass `null` for testing
{
top: 10,
// @ts-ignore to let us pass `null` for testing
left: null,
bottom: 20,
right: NaN,

View File

@ -6,28 +6,6 @@ const siFormatter = d3Format(`.3~s`);
const float2PointFormatter = d3Format(`.2~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(
config: {
description?: string;
@ -39,6 +17,29 @@ export default function createSmartNumberFormatter(
const { description, signed = false, id, label } = config;
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({
description,
formatFunc: value => `${getSign(value)}${formatValue(value)}`,

View File

@ -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: {...},
}}
/>
```

View File

@ -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"
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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,
});
}
}

View File

@ -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,
};
}

View File

@ -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,
});
}
}

View File

@ -1,3 +0,0 @@
export { default as BigNumberChartPlugin } from './BigNumber/index';
export { default as BigNumberTotalChartPlugin } from './BigNumberTotal/index';
export { default as BigNumberChartPreset } from './preset';

View File

@ -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' }),
],
});
}
}

View File

@ -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');
});
});
});

View File

@ -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';

View File

@ -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;
}

View File

@ -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: {...},
}}
/>
```

View File

@ -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"
}
}

View File

@ -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"
/>
);
}

View File

@ -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

View File

@ -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,
});
}
}

View File

@ -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,
};
}

View File

@ -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');
});
});
});

View File

@ -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,
};

View File

@ -1,2 +0,0 @@
declare module 'datatables.net-bs/js/dataTables.bootstrap';
declare module '*.png';

View File

@ -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');
}

View File

@ -1,32 +1,23 @@
/* eslint-disable import/no-extraneous-dependencies, no-console */
const fg = require('fast-glob');
const fs = require('fs-extra');
const pkgGlob = process.argv[2] || '*';
const packages = fg.sync([`{packages,plugins}/${pkgGlob}`], {
const packages = fg.sync(['packages/*'], {
onlyDirectories: true,
});
console.log('Copying asset files from package {src} to {lib,esm}...');
packages.forEach(pkg => {
const assets = fg.sync([`${pkg}/src/**/*.{png,gif,jpg,css,geojson}`]);
assets.forEach(filePath => {
['lib', 'esm']
.map(dir => filePath.replace(`${pkg}/src`, `${pkg}/${dir}`))
.forEach(newFilePath => {
fs.copy(filePath, newFilePath, err => {
if (err) {
console.error(err);
}
});
const newPaths = ['lib', 'esm'].map(dir => filePath.replace(`${pkg}/src`, `${pkg}/${dir}`));
newPaths.forEach(p => {
fs.copy(filePath, p, err => {
if (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