Merge pull request #17445 from zhaoyongjie/monorepo_relocate_superset_ui

refactor(monorepo): relocate superset-ui
This commit is contained in:
Yongjie Zhao 2021-11-26 18:28:01 +08:00 committed by GitHub
commit 870d2ab16f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1430 changed files with 433152 additions and 3 deletions

1
.gitignore vendored
View File

@ -14,7 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
*.ipynb
*.bak
*.db
*.pyc

View File

@ -49,4 +49,5 @@ repos:
rev: v2.4.1 # Use the sha or tag you want to point at
hooks:
- id: prettier
args: ['--ignore-path=./superset-frontend/.prettierignore']
files: 'superset-frontend'

View File

@ -59,5 +59,5 @@ tsconfig.tsbuildinfo
.*iml
.esprintrc
.prettierignore
superset-frontend/packages/generator-superset
superset-frontend/temporary_superset_ui
generator-superset/*
temporary_superset_ui/*

View File

@ -28,3 +28,4 @@ src/dashboard/deprecated/*
src/temp/*
**/node_modules
*.d.ts
temporary_superset_ui/**/*

View File

@ -0,0 +1,26 @@
#
# 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.
#
coverage/
esm/
lib/
tmp/
node_modules/
tsconfig.json
CHANGELOG.md
*.geojson
*-topo.json
temporary_superset_ui/

View File

@ -26,6 +26,7 @@ module.exports = {
'^spec/(.*)$': '<rootDir>/spec/$1',
},
testEnvironment: 'jsdom',
modulePathIgnorePatterns: ['<rootDir>/temporary_superset_ui'],
setupFilesAfterEnv: ['<rootDir>/spec/helpers/setup.ts'],
testURL: 'http://localhost',
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!**/*.stories.*'],

View File

@ -0,0 +1,13 @@
{
"lerna": "3.2.1",
"npmClient": "npm",
"packages": ["packages/*", "plugins/*", "temporary_superset_ui/*"],
"useWorkspaces": true,
"version": "0.0.0",
"ignoreChanges": [
"**/*.md",
"**/*.spec.tsx?",
"**/*.spec.jsx?",
"**/__mocks__/**"
]
}

View File

@ -0,0 +1,9 @@
coverage/
node_modules/
public/
esm/
lib/
tmp/
dist/
temporary-plugins/
storybook-static/

View File

@ -0,0 +1,297 @@
/**
* 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.
*/
module.exports = {
extends: [
'airbnb',
'prettier',
'prettier/react',
'plugin:react-hooks/recommended',
],
parser: '@babel/eslint-parser',
parserOptions: {
ecmaFeatures: {
experimentalObjectRestSpread: true,
},
},
env: {
browser: true,
},
settings: {
'import/resolver': {
webpack: {},
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
// Allow core/src and core/test, not import modules from lib
'import/internal-regex': /^@superset-ui\/core\/(src|test)/,
'import/core-modules': [
'@superset-ui/core',
'@superset-ui/chart-controls',
'@superset-ui/legacy-plugin-chart-calendar',
'@superset-ui/legacy-plugin-chart-chord',
'@superset-ui/legacy-plugin-chart-country-map',
'@superset-ui/legacy-plugin-chart-event-flow',
'@superset-ui/legacy-plugin-chart-force-directed',
'@superset-ui/legacy-plugin-chart-heatmap',
'@superset-ui/legacy-plugin-chart-histogram',
'@superset-ui/legacy-plugin-chart-horizon',
'@superset-ui/legacy-plugin-chart-map-box',
'@superset-ui/legacy-plugin-chart-paired-t-test',
'@superset-ui/legacy-plugin-chart-parallel-coordinates',
'@superset-ui/legacy-plugin-chart-partition',
'@superset-ui/legacy-plugin-chart-pivot-table',
'@superset-ui/legacy-plugin-chart-rose',
'@superset-ui/legacy-plugin-chart-sankey',
'@superset-ui/legacy-plugin-chart-sankey-loop',
'@superset-ui/legacy-plugin-chart-sunburst',
'@superset-ui/legacy-plugin-chart-time-table',
'@superset-ui/legacy-plugin-chart-treemap',
'@superset-ui/legacy-plugin-chart-world-map',
'@superset-ui/legacy-preset-chart-big-number',
'@superset-ui/legacy-preset-chart-nvd3',
'@superset-ui/plugin-chart-echarts',
'@superset-ui/plugin-chart-table',
'@superset-ui/plugin-chart-word-cloud',
'@superset-ui/preset-chart-xy',
],
react: {
version: 'detect',
},
},
plugins: ['prettier', 'react'],
rules: {
camelcase: [
'error',
{
allow: ['^UNSAFE_'],
properties: 'never',
},
],
curly: 2,
'class-methods-use-this': 0,
'func-names': 0,
'guard-for-in': 0,
'import/extensions': [
'error',
{
'.js': 'always',
'.jsx': 'always',
'.ts': 'always',
'.tsx': 'always',
'.json': 'always',
},
],
'import/no-cycle': 0, // re-enable up for discussion, might require some major refactors
'import/prefer-default-export': 0,
indent: 0,
'jsx-a11y/anchor-is-valid': 0, // disabled temporarily
'jsx-a11y/click-events-have-key-events': 0, // re-enable up for discussion
'jsx-a11y/mouse-events-have-key-events': 0, // re-enable up for discussion
'new-cap': 0,
'no-bitwise': 0,
'no-continue': 0,
'no-mixed-operators': 0,
'no-multi-assign': 0,
'no-multi-spaces': 0,
'no-nested-ternary': 0,
'no-prototype-builtins': 0,
'no-restricted-properties': 0,
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'antd',
message:
'Please import Ant components from the index of common/components',
},
],
},
],
'no-shadow': 0, // re-enable up for discussion
'padded-blocks': 0,
'prefer-arrow-callback': 0,
'prefer-object-spread': 1,
'prefer-destructuring': ['error', { object: true, array: false }],
'react/destructuring-assignment': 0, // re-enable up for discussion
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
'react/jsx-fragments': 1,
'react/jsx-no-bind': 0,
'react/jsx-props-no-spreading': 0, // re-enable up for discussion
'react/no-array-index-key': 0,
'react/no-string-refs': 0,
'react/no-unescaped-entities': 0,
'react/no-unused-prop-types': 0,
'react/prop-types': 0,
'react/require-default-props': 0,
'react/static-property-placement': 0, // disabled temporarily
'prettier/prettier': 'error',
},
overrides: [
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
extends: [
'airbnb',
'plugin:@typescript-eslint/recommended',
'prettier',
'prettier/@typescript-eslint',
'prettier/react',
],
plugins: ['@typescript-eslint/eslint-plugin', 'prettier', 'react'],
rules: {
'@typescript-eslint/ban-ts-ignore': 0,
'@typescript-eslint/ban-ts-comment': 0, // disabled temporarily
'@typescript-eslint/ban-types': 0, // disabled temporarily
'@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-use-before-define': 1,
'@typescript-eslint/no-non-null-assertion': 0, // disabled temporarily
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/explicit-module-boundary-types': 0, // re-enable up for discussion
camelcase: 0,
'class-methods-use-this': 0,
'func-names': 0,
'guard-for-in': 0,
// there is a bug related to re-exports with this rule
// which doesn't seem to have been fixed: https://github.com/benmosher/eslint-plugin-import/issues/1460
'import/named': 0,
'import/no-cycle': 0, // re-enable up for discussion, might require some major refactors
'import/extensions': [
'error',
{
'.ts': 'always',
'.tsx': 'always',
'.json': 'always',
},
],
'import/no-named-as-default-member': 0,
'import/prefer-default-export': 0,
indent: 0,
'jsx-a11y/anchor-is-valid': 0, // disabled temporarily
'jsx-a11y/click-events-have-key-events': 0, // re-enable up for discussion
'jsx-a11y/mouse-events-have-key-events': 0, // re-enable up for discussion
'new-cap': 0,
'no-bitwise': 0,
'no-continue': 0,
'no-mixed-operators': 0,
'no-multi-assign': 0,
'no-multi-spaces': 0,
'no-nested-ternary': 0,
'no-prototype-builtins': 0,
'no-restricted-properties': 0,
'no-shadow': 0, // re-enable up for discussion
'no-use-before-define': 0, // disabled temporarily
'padded-blocks': 0,
'prefer-arrow-callback': 0,
'prefer-destructuring': ['error', { object: true, array: false }],
'react/destructuring-assignment': 0, // re-enable up for discussion
'react/forbid-prop-types': 0,
'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.tsx'] }],
'react/jsx-fragments': 1,
'react/jsx-no-bind': 0,
'react/jsx-props-no-spreading': 0, // re-enable up for discussion
'react/no-array-index-key': 0,
'react/no-string-refs': 0,
'react/no-unescaped-entities': 0,
'react/no-unused-prop-types': 0,
'react/prop-types': 0,
'react/require-default-props': 0,
'react/static-property-placement': 0, // re-enable up for discussion
'react/sort-comp': 0,
'prettier/prettier': 'error',
},
settings: {
'import/resolver': {
webpack: {},
typescript: {},
},
react: {
version: 'detect',
},
},
},
{
files: ['*.stories.jsx', '*.stories.tsx'],
rules: {
// this is to keep eslint from complaining about storybook addons,
// since they are included as dev dependencies rather than direct dependencies.
'import/no-extraneous-dependencies': [
'error',
{ devDependencies: true },
],
},
},
{
files: ['*.d.ts'],
rules: {
'max-classes-per-file': 0,
},
},
{
files: [
'*.test.ts',
'*.test.tsx',
'*.test.js',
'*.test.jsx',
'fixtures.*',
],
plugins: ['jest', 'jest-dom', 'no-only-tests', 'testing-library'],
env: {
'jest/globals': true,
},
extends: ['plugin:jest/recommended', 'plugin:testing-library/react'],
rules: {
'import/no-extraneous-dependencies': 0,
'jest/consistent-test-it': 'error',
'jest/no-try-expect': 0,
'max-classes-per-file': 0,
'no-only-tests/no-only-tests': 'error',
'prefer-promise-reject-errors': 0,
},
},
{
files: ['webpack*.js', '.*rc.js', '*.config.js'],
env: {
node: true,
},
settings: {
'import/resolver': {
node: {},
},
},
},
{
files: './packages/generator-superset/**/*.test.*',
env: {
node: true,
},
settings: {
'import/resolver': {
node: {},
},
},
rules: {
'jest/expect-expect': 0,
},
},
],
};

View File

@ -0,0 +1,12 @@
# See https://help.github.com/articles/about-codeowners/
# for more info about CODEOWNERS file
# It uses the same pattern rule for gitignore file
# https://git-scm.com/docs/gitignore#_pattern_format
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
# these users/team will be requested for review
# when someone opens a pull request.
* @apache-superset/embeddable-charts-team

View File

@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
---
### Describe the bug
A clear and concise description of what the bug is.
### To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected behavior
A clear and concise description of what you expected to happen.
### Screenshots
If applicable, add screenshots to help explain your problem.
### Environment (please complete the following information):
- superset-ui version: [e.g. v0.5.0]
- Node version: `node -v`
- npm version: `npm -v`
### Additional context
Add any other context about the problem here.

View File

@ -0,0 +1,18 @@
---
name: Feature request
about: Suggest an idea for this project
---
### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
### Describe the solution you'd like
A clear and concise description of what you want to happen.
### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
### Additional context\*\* Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,8 @@
---
name: Question
about: Ask for help with something.
---
Your question.
- How do I xxx?

View File

@ -0,0 +1,9 @@
💔 Breaking Changes
🏆 Enhancements
📜 Documentation
🐛 Bug Fix
🏠 Internal

View File

@ -0,0 +1,14 @@
# Configuration for request-info - https://github.com/behaviorbot/request-info
# *Required* Comment to reply with
requestInfoReplyComment: >
We would appreciate it if you could provide us with more info about this issue/pr!
# *OPTIONAL* default titles to check against for lack of descriptiveness
# MUST BE ALL LOWERCASE
requestInfoDefaultTitles:
- update readme.md
- updates
# *OPTIONAL* Label to be added to Issues and Pull Requests with insufficient information given
requestInfoLabelToAdd: needs-more-info

View File

@ -0,0 +1,53 @@
version: 2
updates:
- package-ecosystem: npm
directory: '/'
schedule:
interval: daily
open-pull-requests-limit: 10
ignore:
- dependency-name: bootstrap
versions:
- '>= 4.a, < 5'
- dependency-name: d3
versions:
- '>= 6.a, < 7'
- dependency-name: '@types/jsdom'
versions:
- '> 12.2.4'
- dependency-name: '@types/react'
versions:
- 17.0.1
- 17.0.2
- 17.0.3
- dependency-name: '@babel/preset-env'
versions:
- 7.12.13
- 7.13.10
- 7.13.12
- 7.13.9
- dependency-name: '@babel/compat-data'
versions:
- 7.12.13
- 7.13.0
- 7.13.11
- 7.13.12
- 7.13.6
- 7.13.8
- dependency-name: elliptic
versions:
- 6.5.3
- 6.5.4
- dependency-name: yeoman-test
versions:
- 4.0.0
- 5.0.1
- dependency-name: '@emotion/core'
versions:
- 11.0.0
- dependency-name: react-error-boundary
versions:
- 3.1.0
- dependency-name: jquery
versions:
- 3.5.0

View File

@ -0,0 +1,4 @@
label-alias:
bug: '#bug'
feature_request: '#enhancement'
question: '#question'

View File

@ -0,0 +1,2 @@
# Always validate the PR title, and ignore the commits
titleOnly: true

View File

@ -0,0 +1,44 @@
name: Upload Chomatic Build
on:
workflow_run:
workflows: ['build-and-test-workflow']
types:
- completed
jobs:
upload:
runs-on: ubuntu-latest
if: >
${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion
== 'success' }}
steps:
- name: 'Download artifact'
uses: actions/github-script@v3.1.0
with:
script: |
var artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "storybookBuild"
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/storybookBuild.zip', Buffer.from(download.data));
- run: unzip storybookBuild.zip
- name: Deploy to Chromatic
uses: chromaui/action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
storybookBuildDir: storybookBuild
# uncomment to automatically accept all chromatic changes
# autoAcceptChanges: true

View File

@ -0,0 +1,69 @@
name: build-and-test-workflow
on:
push:
branches:
- '*'
tags-ignore:
- 'trigger-patch-test.*'
pull_request:
jobs:
build:
name: Build and test
runs-on: ubuntu-20.04
env:
CODECOV_TOKEN: '${{ secrets.CODECOV_TOKEN }}'
strategy:
matrix:
node-version: [16]
steps:
- uses: actions/checkout@v2
with:
# pulls all commits (needed for lerna / semantic release to correctly version)
fetch-depth: '0'
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Cache npm
uses: actions/cache@v1
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-node-
${{ runner.OS }}-
- name: Get npm cache directory path
id: npm-cache-dir-path
run: echo "::set-output name=dir::$(npm config get cache)"
- name: Cache npm
uses: actions/cache@v1
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint --quiet
- name: Build packages
run: npm run build
- name: Run unit tests
run: npm run test
- name: Build Storybook
run: npm run build-storybook
- uses: actions/upload-artifact@v2
with:
name: storybookBuild
path: ./packages/superset-ui-demo/storybook-static/
- name: Report code coverage
run: .github/workflows/codecov.sh

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
name: release-workflow
on:
push:
branches:
- 'master'
jobs:
build:
name: Bump version and publish package(s)
runs-on: ubuntu-20.04
strategy:
matrix:
node-version: [16]
steps:
- uses: actions/checkout@v2
with:
# pulls all commits (needed for lerna / semantic release to correctly version)
fetch-depth: 0
- name: Get tags and filter trigger tags
run: |
git fetch --depth=1 origin "+refs/tags/*:refs/tags/*"
git fetch --prune --unshallow
git tag -d `git tag | grep -E '^trigger-'`
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Cache npm
uses: actions/cache@v1
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-node-
${{ runner.OS }}-
- name: Get npm cache directory path
id: npm-cache-dir-path
run: echo "::set-output name=dir::$(npm config get cache)"
- name: Cache npm
uses: actions/cache@v1
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
run: npm ci
- name: Build packages
run: npm run build
- name: Run unit tests
run: npm run test
- name: Configure npm and git
run: |
echo "@superset-ui:registry=https://registry.npmjs.org/" > .npmrc
echo "registry=https://registry.npmjs.org/" >> .npmrc
echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" >> $HOME/.npmrc 2> /dev/null
npm whoami
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git remote set-url origin "https://${GITHUB_TOKEN}@github.com/apache-superset/superset-ui.git" > /dev/null 2>&1
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Bump version and publish package(s)
run: |
git tag -d `git tag | grep -E '^trigger-'`
npm run ci:release-from-tag
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}

View File

@ -0,0 +1,49 @@
.DS_Store
*.DS_Store
# Logs
logs/
*.log
# Cache
.bundle/
.happo/
.idea/
.next/
.cache
.eslintcache
.idea
*.iml
.npm
.npmrc
.vscode
.yarnclean
# Directories
build/
coverage/
dist/
esm/
lib/
public/
node_modules/
tmp/
_gh-pages/
# Custom
*.map
*.min.js
test-changelog.md
*.tsbuildinfo
# Ignore package-lock in packages
plugins/*/package-lock.json
packages/*/package-lock.json
# For country map geojson conversion script
.ipynb_checkpoints/
scripts/*.zip
**/storybook-static
rat-results.txt

View File

@ -0,0 +1 @@
v14.15.5

View File

@ -0,0 +1,17 @@
_gh-pages/
coverage/
node_modules/
public/
esm/
lib/
tmp/
dist/
lerna.json
npm-shrinkwrap.json
package-lock.json
tsconfig.json
tsconfig.options.json
tsconfig.eslint.json
CHANGELOG.md
*.geojson
*-topo.json

View File

@ -0,0 +1,66 @@
# Note: these patterns are applied to single files or directories, not full paths
.gitignore
docs/README.md
.gitattributes
.gitkeep
.coverage
.coveragerc
.codecov.yml
.eslintrc
.eslintignore
.flake8
.nvmrc
.prettierrc
.rat-excludes
.*log
.*pyc
.*lock
.*geojson
DISCLAIMER
licenses/*
node_modules/*
rat-results.txt
babel-node
dist
superset/static/*
build
superset.egg-info
apache_superset.egg-info
.idea
.*sql
.*zip
.*lock
# json and csv in general cannot have comments
.*json
.*csv
# Generated doc files
env/*
docs/README.md
docs/.htaccess*
_build/*
_static/*
.buildinfo
searchindex.js
# auto generated
requirements/*
# vendorized
vendor/*
# github configuration
.github/*
.*mdx
coverage/*
.*.md
.*.txt
# skip license check in superset-ui
tmp/*
lib/*
esm/*
tsconfig.tsbuildinfo
.*ipynb
.*yml
.*iml
.esprintrc
.prettierignore
README.erb
package.erb

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,71 @@
## Contributing guidelines
### Setup local development
1. clone this repo
2. have `npm` install package dependencies and manage the symlinking between packages for you
```sh
git clone ...superset-ui && cd superset-ui
npm ci
npm build
```
To build only selected packages or plugins,
```bash
npm build "*chart-table"
```
### File organization
[lerna](https://github.com/lerna/lerna/) and [npm](https://www.npmjs.com/) are used to manage
versions and dependencies between packages in this monorepo.
```
superset-ui/
lerna.json
package.json
...
packages/
package1/
package.json
...
src/
test/ # unit tests
types/ # typescript type declarations
...
lib/ # commonjs output
esm/ # es module output
...
...
```
### Builds, linting, and testing
Each package defines its own build config, linting, and testing. You can have lerna run commands
across all packages using the syntax `npm run test` (or `npm run test:watch` for watch mode) from
the root `@superset-ui` directory.
- [Using Storybook](docs/storybook.md) - You can test your components independently from Superset
app.
- [Debugging Superset plugins in Superset app](docs/debugging.md) - Sometimes something went wrong
and you have to do it.
### Committing
This repository follows
[conventional commits](https://www.conventionalcommits.org/en/v1.0.0-beta.3/) guideline for commit
messages and has a `commitlint` hook which will require you to have the valid commit message before
committing. You can use `npm run commit` to help you create a commit message.
### Publishing
**Prerequisite:** You'll need to be a committer on the `apache-superset` organization to be able to
publish new versions of `superset-ui`.
1. Checkout the `master` branch from the main repo at `apache-superset/superset-ui` - NOT A FORK!
2. run `npm run ci:create-patch-version` to bump the patch version (the most common case) or
`npm run ci:create-minor-version` to bump the minor version. Once the process finishes and the
commit has been pushed to GitHub, CI will complete publishing the release to npm (takes some time
to complete).

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed 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.

View File

@ -0,0 +1,80 @@
# @superset-ui
[![Codecov branch](https://img.shields.io/codecov/c/github/apache-superset/superset-ui/master.svg?style=flat-square)](https://codecov.io/gh/apache-superset/superset-ui/branch/master)
[![Build Status](https://img.shields.io/travis/com/apache-superset/superset-ui/master.svg?style=flat-square)](https://travis-ci.com/apache-superset/superset-ui)
[![David](https://img.shields.io/david/dev/apache-superset/superset-ui.svg?style=flat-square)](https://david-dm.org/apache-superset/superset-ui?type=dev)
Collection of packages that power the
[Apache Superset](https://github.com/apache/incubator-superset) UI, and can be used to craft custom
data applications that leverage a Superset backend :chart_with_upwards_trend:
## Demo
Most recent release: https://apache-superset.github.io/superset-ui/
Current master: https://superset-ui.now.sh/
## Packages
### Core packages
| Package | Version |
| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [@superset-ui/core](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-core) | [![Version](https://img.shields.io/npm/v/@superset-ui/core.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/core) |
| [@superset-ui/chart-controls](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-chart-controls) | [![Version](https://img.shields.io/npm/v/@superset-ui/core.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/chart-controls) |
| [@superset-ui/generator-superset](https://github.com/apache-superset/superset-ui/tree/master/packages/generator-superset) | [![Version](https://img.shields.io/npm/v/@superset-ui/generator-superset.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/generator-superset) |
### Chart plugin packages
`@superset-ui/legacy-*` packages are extracted from the classic
[Apache Superset](https://github.com/apache/incubator-superset) and converted into plugins. These
packages are extracted with minimal changes (almost as-is). They also depend on legacy API
(`viz.py`) to function.
| Package | Version |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [@superset-ui/legacy-preset-chart-big-number](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-preset-chart-big-number) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-big-number.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-preset-chart-big-number) |
| [@superset-ui/legacy-preset-chart-nvd3](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-preset-chart-nvd3) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-preset-chart-nvd3.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-preset-chart-nvd3) |
| [@superset-ui/legacy-plugin-chart-calendar](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-calendar) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-calendar.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-calendar) |
| [@superset-ui/legacy-plugin-chart-chord](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-chord) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-chord.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-chord) |
| [@superset-ui/legacy-plugin-chart-country-map](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-country-map) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-country-map.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-country-map) |
| [@superset-ui/legacy-plugin-chart-event-flow](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-event-flow) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-event-flow.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-event-flow) |
| [@superset-ui/legacy-plugin-chart-force-directed](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-force-directed) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-force-directed.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-force-directed) |
| [@superset-ui/legacy-plugin-chart-heatmap](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-heatmap) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-heatmap.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-heatmap) |
| [@superset-ui/legacy-plugin-chart-histogram](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-histogram) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-histogram.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-histogram) |
| [@superset-ui/legacy-plugin-chart-horizon](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-horizon) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-horizon.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-horizon) |
| [@superset-ui/legacy-plugin-chart-iframe](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-iframe) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-iframe.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-iframe) |
| [@superset-ui/legacy-plugin-chart-markup](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-markup) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-markup.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-markup) |
| [@superset-ui/legacy-plugin-chart-map-box](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-map-box) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-map-box.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-map-box) |
| [@superset-ui/legacy-plugin-chart-paired-t-test](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-paired-t-test) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-paired-t-test.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-paired-t-test) |
| [@superset-ui/legacy-plugin-chart-parallel-coordinates](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-parallel-coordinates) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-parallel-coordinates.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-parallel-coordinates) |
| [@superset-ui/legacy-plugin-chart-partition](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-partition) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-partition.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-partition) |
| [@superset-ui/legacy-plugin-chart-pivot-table](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-pivot-table) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-pivot-table.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-pivot-table) |
| [@superset-ui/legacy-plugin-chart-rose](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-rose) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-rose.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-rose) |
| [@superset-ui/legacy-plugin-chart-sankey](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-sankey) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-sankey.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-sankey) |
| [@superset-ui/legacy-plugin-chart-sankey-loop](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-sankey-loop) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-sankey-loop.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-sankey-loop) |
| [@superset-ui/legacy-plugin-chart-sunburst](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-sunburst) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-sunburst.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-sunburst) |
| [@superset-ui/legacy-plugin-chart-treemap](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-treemap) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-treemap.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-treemap) |
| [@superset-ui/legacy-plugin-chart-world-map](https://github.com/apache-superset/superset-ui/tree/master/plugins/legacy-plugin-chart-world-map) | [![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-world-map.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-world-map) |
`@superset-ui/plugin-*` packages are newer and higher quality in general. A key difference that they
do not depend on `viz.py` (which contain visualization-specific python code) and interface with
`/api/v1/query/`, a new generic endpoint instead meant to serve all visualizations, instead. Also
should be written in Typescript.
| Package | Version | Note |
| ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- |
| [@superset-ui/plugin-chart-word-cloud](https://github.com/apache-superset/superset-ui/tree/master/plugins/plugin-chart-word-cloud) | [![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-word-cloud.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/plugin-chart-word-cloud) | |
| [@superset-ui/plugin-chart-table](https://github.com/apache-superset/superset-ui/tree/master/plugins/plugin-chart-table) | [![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-table.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/plugin-chart-table) | |
| [@superset-ui/preset-chart-xy](https://github.com/apache-superset/superset-ui/tree/master/plugins/preset-chart-xy) | [![Version](https://img.shields.io/npm/v/@superset-ui/preset-chart-xy.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/preset-chart-xy) | |
| [@superset-ui/plugin-chart-echarts](https://github.com/apache-superset/superset-ui/tree/master/plugins/plugin-chart-echarts) | [![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-echarts.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/plugin-chart-echarts) | |
| [@superset-ui/plugin-filter-antd](https://github.com/apache-superset/superset-ui/tree/master/plugins/plugin-filter-antd) | [![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-echarts.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/plugin-filter-antd) | |
## Contribution and development guide
Please read the [contributing guidelines](CONTRIBUTING.md) which include development environment
setup and other things you should know about coding in this repo.
### License
Apache-2.0

View File

@ -0,0 +1,49 @@
/*
* 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.
*/
const { getConfig } = require('@airbnb/config-babel');
const config = getConfig({
library: true,
react: true,
next: true,
esm: process.env.BABEL_OUTPUT === 'esm',
node: process.env.NODE_ENV === 'test',
typescript: true,
env: {
targets: false,
},
});
// Override to allow transpile es modules inside vega-lite
config.ignore = config.ignore.filter(item => item !== 'node_modules/');
config.ignore.push('node_modules/(?!(vega-lite|lodash-es))');
config.plugins = [
['babel-plugin-transform-dev', { evaluate: false }],
['babel-plugin-typescript-to-proptypes', { loose: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
];
config.presets.push([
'@emotion/babel-preset-css-prop',
{
autoLabel: 'dev-only',
labelFormat: '[local]',
},
]);
module.exports = config;

View File

@ -0,0 +1,22 @@
coverage:
status:
patch: off
project:
default:
target: 0%
threshold: 10%
core-packages-ts:
target: 100%
paths:
- 'packages'
- '!packages/**/*.jsx'
- '!packages/**/*.tsx'
core-packages-tsx:
target: 50%
paths:
- 'packages/**/*.jsx'
- 'packages/**/*.tsx'
plugins:
target: 0
paths:
- plugins

View File

@ -0,0 +1,25 @@
/*
* 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.
*/
module.exports = {
extends: [
'@commitlint/config-conventional',
'@commitlint/config-lerna-scopes',
],
};

View File

@ -0,0 +1,27 @@
# Debug Superset plugins in Superset app
## Activate plugins for local development
1. First, make sure you have run `npm ci` and `npm run build` in `superset-ui` or your own plugin
repo.
2. Go to [superset-frontend](https://github.com/apache/superset/tree/master/superset-frontend), use
`npm link` to create a symlink of the plugin source code in `node_modules`:
```sh
cd superset/superset-frontend
# npm link ~/path/to/your/plugin
npm link ../../superset-ui/plugins/plugin-chart-word-cloud
```
3. Start developing with webpack dev server:
```sh
npm run dev-server
```
The dev server will automatically build from the source code under `path/to/your-plugin/src` and
watch the changes.
## Deactivate plugins
To deactivate a plugin, simply run `npm ci` in `superset/superset-frontend` again.

View File

@ -0,0 +1,13 @@
## Using Storybook
You can demo your changes by running the storybook demo locally with the following commands:
```sh
npm ci
npm run build
npm run storybook
```
The Storybook will
[automatically build from the source code](https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-demo/.storybook/main.js#L49-L58)
when package names start with `@superset-ui/`.

View File

@ -0,0 +1,77 @@
/*
* 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.
*/
module.exports = {
bail: false,
collectCoverageFrom: [
'**/src/**/*.{ts,tsx,js,jsx}',
'**/test/**/*.{ts,tsx,js,jsx}',
],
coverageDirectory: './coverage',
coveragePathIgnorePatterns: [
'coverage/',
'node_modules/',
'public/',
'esm/',
'lib/',
'tmp/',
'dist/',
],
coverageReporters: ['lcov', 'json-summary', 'html'],
coverageThreshold: {
global: {
branches: 0,
functions: 0,
lines: 0,
statements: 0,
},
},
globals: {
__DEV__: true,
caches: true,
},
moduleFileExtensions: ['mock.js', 'ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
'\\.(css|less|geojson)$': '<rootDir>/test/__mocks__/mockExportObject.js',
'\\.(gif|ttf|eot|png|jpg)$': '<rootDir>/test/__mocks__/mockExportString.js',
'\\.svg$': '<rootDir>/test/__mocks__/svgrMock.tsx',
'@superset-ui/(((?!(legacy-preset-chart-deckgl|core/src)).)*)$':
'<rootDir>/node_modules/@superset-ui/$1/src',
'@superset-ui/core/src/(.*)$':
'<rootDir>/node_modules/@superset-ui/core/src/$1',
},
roots: ['<rootDir>/packages', '<rootDir>/plugins'],
setupFiles: ['<rootDir>/test/setup.ts'],
testEnvironment: 'jsdom',
testURL: 'http://localhost',
timers: 'real',
verbose: false,
transformIgnorePatterns: ['node_modules/(?!(vega-lite|lodash-es))'],
testPathIgnorePatterns: ['packages/generator-superset/generators'],
projects: [
'<rootDir>',
{
displayName: 'node',
rootDir: '<rootDir>/packages/generator-superset',
testMatch: ['<rootDir>/test/**/?(*.)+(spec|test).{js,jsx,ts,tsx}'],
testEnvironment: 'node',
},
],
snapshotSerializers: ['@emotion/jest/enzyme-serializer'],
};

View File

@ -0,0 +1,26 @@
{
"lerna": "3.2.1",
"npmClient": "npm",
"packages": [
"packages/*",
"plugins/*"
],
"useWorkspaces": true,
"version": "0.18.25",
"ignoreChanges": [
"**/*.md",
"**/*.spec.tsx?",
"**/*.spec.jsx?",
"**/__mocks__/**"
],
"command": {
"publish": {
"message": "chore: publish %s",
"graphType": "all"
},
"version": {
"message": "chore: publish %s",
"exact": true
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
{
"name": "@superset-ui/monorepo",
"version": "0.0.0",
"description": "Superset UI",
"private": true,
"scripts": {
"build": "node ./scripts/build.js",
"build:assets": "node ./scripts/copyAssets.js",
"babel": "npm run build --no-type",
"demo": "cd packages/superset-ui-demo && npm run demo:build",
"demo:clean": "cd packages/superset-ui-demo && npm run demo:clean",
"demo:build": "cd packages/superset-ui-demo && npm run demo:build",
"storybook": "cd packages/superset-ui-demo && npm run storybook",
"build-storybook": "cd packages/superset-ui-demo && npm run build-storybook",
"chromatic": "cd packages/superset-ui-demo && npm run chromatic",
"sb": "npm run storybook",
"clean": "npm run build --clean --no-babel --no-type",
"commit": "superset-commit",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 10",
"jest": "NODE_ENV=test jest --coverage --verbose",
"lint": "eslint --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx .",
"lint:fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx,.ts,.tsx .",
"format": "npm run prettier",
"prettier": "prettier --write './{plugins,packages}/**/*{.js,.jsx,.ts,.tsx,.css,.less,.scss,.sass}'",
"test": "npm run jest",
"test:watch": "npm run lint:fix && npm run jest --watch",
"type": "npm run build --no-babel",
"prepare-release": "git checkout master && git pull --rebase origin master && npm run install && npm run test",
"prerelease": "npm run build",
"release": "npm run prepare-release && lerna publish && npm run postrelease",
"postrelease": "lerna run deploy-demo",
"list-changed-packages": "lerna changed",
"manual-release": "lerna publish --force-publish && npm run postrelease",
"clean-npm-lock": "rm -rf ./{packages,plugins}/*/package-lock.json",
"prune": "rm -rf ./{packages,plugins}/*/{lib,esm,tsconfig.tsbuildinfo}",
"ci:create-patch-version": "npm run clean-npm-lock && ./scripts/lernaVersion.sh patch",
"ci:create-minor-version": "npm run clean-npm-lock && ./scripts/lernaVersion.sh minor",
"ci:create-conventional-version": "npm run clean-npm-lock && ./scripts/lernaVersion.sh '--conventional-commits --create-release github'",
"ci:release-from-tag": "npm run clean-npm-lock && lerna publish from-package --yes",
"ci:release-conventional": "npm run clean-npm-lock && lerna publish --conventional-commits --create-release github --yes"
},
"repository": "https://github.com/apache-superset/superset-ui.git",
"keywords": [
"apache",
"superset",
"data",
"analytics",
"analysis",
"visualization",
"react",
"d3",
"data-ui",
"vx"
],
"license": "Apache-2.0",
"dependencies": {
"@airbnb/config-babel": "^3.1.0",
"@airbnb/config-jest": "^3.0.2",
"@airbnb/config-webpack": "^3.3.1",
"@babel/cli": "^7.11.5",
"@babel/compat-data": "^7.9.6",
"@babel/core": "^7.8.7",
"@babel/eslint-parser": "^7.16.0",
"@babel/node": "^7.8.7",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/preset-env": "^7.8.7",
"@babel/preset-react": "^7.8.3",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.8.6",
"@emotion/babel-preset-css-prop": "^11.2.0",
"@emotion/jest": "^11.3.0",
"@storybook/preset-typescript": "^3.0.0",
"@superset-ui/commit-config": "^0.0.9",
"@types/enzyme": "^3.10.3",
"@types/jest": "^26.0.4",
"@types/jsdom": "^12.2.4",
"@types/react": "^16.13.1",
"@types/react-test-renderer": "^16.9.2",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"babel-loader": "^8.2.2",
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-jsx-remove-data-test-id": "^2.1.3",
"babel-plugin-lodash": "^3.3.4",
"babel-plugin-typescript-to-proptypes": "^1.4.2",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.1",
"enzyme-to-json": "^3.4.3",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^7.1.0",
"eslint-import-resolver-typescript": "^2.3.0",
"eslint-import-resolver-webpack": "^0.13.0",
"eslint-plugin-cypress": "^2.11.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.3",
"eslint-plugin-jest-dom": "^3.6.5",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-no-only-tests": "^2.4.0",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^3.10.1",
"eslint-plugin-unicorn": "^25.0.1",
"esprint": "^0.7.0",
"fast-glob": "^3.2.4",
"fs-extra": "^9.0.0",
"global-box": "^1.2.0",
"husky": "^4.2.5",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.6.3",
"jest-environment-enzyme": "^7.1.2",
"jest-enzyme": "^7.1.2",
"jest-mock-console": "^1.0.0",
"jsdom": "^16.4.0",
"lerna": "^3.15.0",
"lint-staged": "^10.0.3",
"npm": "^7.5.4",
"prettier": "^2.4.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-loadable": "^5.5.0",
"react-test-renderer": "^16.13.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"ts-jest": "^26.4.2",
"ts-loader": "^8.0.7",
"typescript": "^4.1.2",
"webpack": "^4.42.0",
"webpack-bundle-analyzer": "^3.6.1",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0",
"webpack-manifest-plugin": "^2.2.0",
"webpack-sources": "^1.4.3",
"yargs": "^15.4.1"
},
"engines": {
"node": "14.x || 16.x",
"npm": "^7.5.4"
},
"workspaces": [
"./packages/*",
"./plugins/*"
],
"browserslist": [
"last 3 chrome versions",
"last 3 firefox versions",
"last 3 safari versions",
"last 3 edge versions"
],
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "./scripts/commitlint.js HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,json,md}": [
"prettier --write"
]
}
}

View File

@ -0,0 +1,27 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30)
**Note:** Version bump only for package @superset-ui/generator-superset

View File

@ -0,0 +1,52 @@
<!--
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.
-->
# generator-superset
[![Version](https://img.shields.io/npm/v/@superset-ui/generator-superset.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/generator-superset)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fgenerator-superset&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/generator-superset)
> Scaffolder for Superset
## Installation
First, install [Yeoman](http://yeoman.io) and `generator-superset` using
[npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
```bash
npm install -g yo
npm install -g @superset-ui/generator-superset
```
## Usage
Generate a new package or visualization plugin in `@superset-ui`
```bash
cd superset-ui/packages
mkdir superset-ui-new-package
cd superset-ui-new-package
yo @superset-ui/superset
```
## License
Apache-2.0
[learn more about Yeoman](http://yeoman.io/).

View File

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable sort-keys */
const Generator = require('yeoman-generator');
const chalk = require('chalk');
const yosay = require('yosay');
module.exports = class extends Generator {
async prompting() {
// Have Yeoman greet the user.
this.log(
yosay(`Welcome to the rad ${chalk.red('generator-superset')} generator!`),
);
this.option('skipInstall');
this.answers = await this.prompt([
{
type: 'list',
name: 'subgenerator',
message: 'What do you want to do?',
choices: [
{
name: 'Create superset-ui core package',
value: 'package',
},
{
name: 'Create superset-ui chart plugin package',
value: 'plugin-chart',
},
],
},
]);
}
configuring() {
// Redirect the default 'app' generator
// to 'package' subgenerator
// until there are multiple subgenerators
// then this can be changed into a menu to select
// subgenerator.
this.composeWith(require.resolve(`../${this.answers.subgenerator}`));
}
};

View File

@ -0,0 +1,78 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable sort-keys */
const Generator = require('yeoman-generator');
const _ = require('lodash');
module.exports = class extends Generator {
async prompting() {
this.option('skipInstall');
this.answers = await this.prompt([
{
type: 'input',
name: 'name',
message: 'Package name:',
default: _.kebabCase(this.appname.replace('superset ui', '').trim()), // Default to current folder name
},
{
type: 'list',
name: 'language',
message: 'Choose language',
default: 'typescript',
choices: [
{
name: 'typescript',
value: 'typescript',
short: 't',
},
{
name: 'javascript',
value: 'javascript',
short: 'j',
},
],
},
]);
}
writing() {
this.fs.copyTpl(
this.templatePath('_package.json'),
this.destinationPath('package.json'),
this.answers,
);
this.fs.copyTpl(
this.templatePath('README.md'),
this.destinationPath('README.md'),
this.answers,
);
const ext = this.answers.language === 'typescript' ? 'ts' : 'js';
this.fs.copy(
this.templatePath('src/index.txt'),
this.destinationPath(`src/index.${ext}`),
);
this.fs.copy(
this.templatePath('test/index.txt'),
this.destinationPath(`test/index.test.${ext}`),
);
}
};

View File

@ -0,0 +1,46 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
## @superset-ui/<%= name %>
[![Version](https://img.shields.io/npm/v/@superset-ui/<%= name
%>.svg?style=flat)](https://img.shields.io/npm/v/@superset-ui/<%= name %>.svg?style=flat)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fsuperset-ui-<%=
name
%>&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/superset-ui-<%=
name %>)
Description
#### Example usage
```js
import { xxx } from '@superset-ui/<%= name %>';
```
#### API
`fn(args)`
- Do something
### Development
`@data-ui/build-config` is used to manage the build configuration for this package including babel
builds, jest testing, eslint, and prettier.

View File

@ -0,0 +1,23 @@
{
"name": "@superset-ui/<%= name %>",
"version": "0.0.0",
"description": "Superset UI <%= name %>",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"files": ["esm", "lib"],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"keywords": ["superset"],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"publishConfig": {
"access": "public"
}
}

View File

@ -0,0 +1,5 @@
describe('My Test', () => {
it('tests something', () => {
expect(1).toEqual(1);
});
});

View File

@ -0,0 +1,127 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-disable sort-keys */
const Generator = require('yeoman-generator');
const _ = require('lodash');
module.exports = class extends Generator {
async prompting() {
this.option('skipInstall');
this.answers = await this.prompt([
{
type: 'input',
name: 'packageName',
message: 'Package name:',
// Default to current folder name
default: _.kebabCase(this.appname.replace('plugin chart', '').trim()),
},
{
type: 'input',
name: 'description',
message: 'Description:',
// Default to current folder name
default: _.upperFirst(
_.startCase(this.appname.replace('plugin chart', '').trim()),
),
},
{
type: 'list',
name: 'componentType',
message: 'What type of React component would you like?',
choices: [
{
name: 'Class component',
value: 'class',
},
{
name: 'Function component (with hooks)',
value: 'function',
},
],
},
{
type: 'list',
name: 'chartType',
message: 'What type of chart would you like?',
choices: [
{
name: 'Time-series chart',
value: 'timeseries',
},
{
name: 'Regular chart',
value: 'regular',
},
],
},
{
type: 'confirm',
name: 'addBadges',
message: "Add superset-ui badges to your plugin's README.md",
default: true,
},
]);
}
writing() {
// 'hello-world' -> 'HelloWorld'
const packageLabel = _.upperFirst(_.camelCase(this.answers.packageName));
// 'hello-world' -> 'Hello World'
const pluginName = _.startCase(_.camelCase(this.answers.packageName));
const params = {
...this.answers,
packageLabel,
pluginName,
};
[
['package.erb', 'package.json'],
['tsconfig.json', 'tsconfig.json'],
['README.erb', 'README.md'],
['src/index.erb', 'src/index.ts'],
['src/plugin/buildQuery.erb', 'src/plugin/buildQuery.ts'],
['src/plugin/controlPanel.erb', 'src/plugin/controlPanel.ts'],
['src/plugin/index.erb', 'src/plugin/index.ts'],
['src/plugin/transformProps.erb', 'src/plugin/transformProps.ts'],
['src/types.erb', 'src/types.ts'],
['src/MyChart.erb', `src/${packageLabel}.tsx`],
['test/index.erb', 'test/index.test.ts'],
['test/plugin/buildQuery.test.erb', 'test/plugin/buildQuery.test.ts'],
[
'test/plugin/transformProps.test.erb',
'test/plugin/transformProps.test.ts',
],
].forEach(([src, dest]) => {
this.fs.copyTpl(
this.templatePath(src),
this.destinationPath(dest),
params,
);
});
['types/external.d.ts', 'src/images/thumbnail.png'].forEach(file => {
this.fs.copy(this.templatePath(file), this.destinationPath(file));
});
}
};

View File

@ -0,0 +1,54 @@
## @superset-ui/plugin-chart-<%= packageName %>
<%if (addBadges) { %>[![Version](https://img.shields.io/npm/v/@superset-ui/plugin-chart-<%= packageName %>.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/plugin-chart-<%= packageName %>)<% } %>
This plugin provides <%= description %> 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 <%= packageLabel %>ChartPlugin from '@superset-ui/plugin-chart-<%= packageName %>';
new <%= packageLabel %>ChartPlugin()
.configure({ key: '<%= packageName %>' })
.register();
```
Then use it via `SuperChart`. See [storybook](https://apache-superset.github.io/superset-ui/?selectedKind=plugin-chart-<%= packageName %>) for more details.
```js
<SuperChart
chartType="<%= packageName %>"
width={600}
height={600}
formData={...}
queriesData={[{
data: {...},
}]}
/>
```
### File structure generated
```
├── package.json
├── README.md
├── tsconfig.json
├── src
│   ├── <%= packageLabel %>.tsx
│   ├── images
│   │   └── thumbnail.png
│   ├── index.ts
│   ├── plugin
│   │   ├── buildQuery.ts
│   │   ├── controlPanel.ts
│   │   ├── index.ts
│   │   └── transformProps.ts
│   └── types.ts
├── test
│   └── index.test.ts
└── types
└── external.d.ts
```

View File

@ -0,0 +1,39 @@
{
"name": "@superset-ui/plugin-chart-<%= packageName %>",
"version": "0.0.0",
"description": "Superset Chart - <%= description %>",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@superset-ui/core": "^0.17.40",
"@superset-ui/chart-controls": "^0.17.41"
},
"peerDependencies": {
"react": "^16.13.1"
},
"devDependencies": {
"@types/jest": "^26.0.0",
"jest": "^26.0.1"
}
}

View File

@ -0,0 +1,113 @@
/**
* 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, { <%if (componentType == 'class') { %>PureComponent<% } %><%if (componentType == 'function') { %>useEffect<% } %>, createRef } from 'react';
import { styled } from '@superset-ui/core';
import { <%= packageLabel %>Props, <%= packageLabel %>StylesProps } from './types';
// The following Styles component is a <div> element, which has been styled using Emotion
// For docs, visit https://emotion.sh/docs/styled
// Theming variables are provided for your use via a ThemeProvider
// imported from @superset-ui/core. For variables available, please visit
// https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-core/src/style/index.ts
const Styles = styled.div<<%= packageLabel %>StylesProps>`
background-color: ${({ theme }) => theme.colors.secondary.light2};
padding: ${({ theme }) => theme.gridUnit * 4}px;
border-radius: ${({ theme }) => theme.gridUnit * 2}px;
height: ${({ height }) => height};
width: ${({ width }) => width};
overflow-y: scroll;
h3 {
/* You can use your props to control CSS! */
font-size: ${({ theme, headerFontSize }) => theme.typography.sizes[headerFontSize]};
font-weight: ${({ theme, boldText }) => theme.typography.weights[boldText ? 'bold' : 'normal']};
}
`;
/**
* ******************* WHAT YOU CAN BUILD HERE *******************
* In essence, a chart is given a few key ingredients to work with:
* * Data: provided via `props.data`
* * A DOM element
* * FormData (your controls!) provided as props by transformProps.ts
*/
<%if (componentType == 'class') { %>export default class <%= packageLabel %> extends PureComponent<<%= packageLabel %>Props> {
// Often, you just want to get a hold of the DOM and go nuts.
// Here, you can do that with createRef, and componentDidMount.
rootElem = createRef<HTMLDivElement>();
componentDidMount() {
const root = this.rootElem.current as HTMLElement;
console.log('Plugin element', root);
}
render() {
// height and width are the height and width of the DOM element as it exists in the dashboard.
// There is also a `data` prop, which is, of course, your DATA 🎉
console.log('Approach 1 props', this.props);
const { data, height, width } = this.props;
console.log('Plugin props', this.props);
return (
<Styles
ref={this.rootElem}
boldText={this.props.boldText}
headerFontSize={this.props.headerFontSize}
height={height}
width={width}
>
<h3>{this.props.headerText}</h3>
<pre>{JSON.stringify(data, null, 2)}</pre>
</Styles>
);
}
}<% } %><%if (componentType == 'function') { %>export default function <%= packageLabel %>(props: <%= packageLabel %>Props) {
// height and width are the height and width of the DOM element as it exists in the dashboard.
// There is also a `data` prop, which is, of course, your DATA 🎉
const { data, height, width } = props;
const rootElem = createRef<HTMLDivElement>();
// Often, you just want to get a hold of the DOM and go nuts.
// Here, you can do that with createRef, and the useEffect hook.
useEffect(() => {
const root = rootElem.current as HTMLElement;
console.log('Plugin element', root);
});
console.log('Plugin props', props);
return (
<Styles
ref={rootElem}
boldText={props.boldText}
headerFontSize={props.headerFontSize}
height={height}
width={width}
>
<h3>{props.headerText}</h3>
<pre>${JSON.stringify(data, null, 2)}</pre>
</Styles>
);
}<% } %>

View File

@ -0,0 +1,27 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// eslint-disable-next-line import/prefer-default-export
export { default as <%= packageLabel %>ChartPlugin } from './plugin';
/**
* Note: this file exports the default export from <%= packageLabel %>.tsx.
* If you want to export multiple visualization modules, you will need to
* either add additional plugin folders (similar in structure to ./plugin)
* OR export multiple instances of `ChartPlugin` extensions in ./plugin/index.ts
* which in turn load exports from <%= packageLabel %>.tsx
*/

View File

@ -0,0 +1,44 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { buildQueryContext, QueryFormData } from '@superset-ui/core';
/**
* The buildQuery function is used to create an instance of QueryContext that's
* sent to the chart data endpoint. In addition to containing information of which
* datasource to use, it specifies the type (e.g. full payload, samples, query) and
* format (e.g. CSV or JSON) of the result and whether or not to force refresh the data from
* the datasource as opposed to using a cached copy of the data, if available.
*
* More importantly though, QueryContext contains a property `queries`, which is an array of
* QueryObjects specifying individual data requests to be made. A QueryObject specifies which
* columns, metrics and filters, among others, to use during the query. Usually it will be enough
* to specify just one query based on the baseQueryObject, but for some more advanced use cases
* it is possible to define post processing operations in the QueryObject, or multiple queries
* if a viz needs multiple different result sets.
*/
export default function buildQuery(formData: QueryFormData) {
const { cols: groupby } = formData;
return buildQueryContext(formData, baseQueryObject => [
{
...baseQueryObject,
groupby,<%if (chartType === 'timeseries') { %>
is_timeseries: true,<% } %>
},
]);
}

View File

@ -0,0 +1,190 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t, validateNonEmpty } from '@superset-ui/core';
import { ControlPanelConfig, sections, sharedControls } from '@superset-ui/chart-controls';
const config: ControlPanelConfig = {
/**
* The control panel is split into two tabs: "Query" and
* "Chart Options". The controls that define the inputs to
* the chart data request, such as columns and metrics, usually
* reside within "Query", while controls that affect the visual
* appearance or functionality of the chart are under the
* "Chart Options" section.
*
* There are several predefined controls that can be used.
* Some examples:
* - groupby: columns to group by (tranlated to GROUP BY statement)
* - series: same as groupby, but single selection.
* - metrics: multiple metrics (translated to aggregate expression)
* - metric: sane as metrics, but single selection
* - adhoc_filters: filters (translated to WHERE or HAVING
* depending on filter type)
* - row_limit: maximum number of rows (translated to LIMIT statement)
*
* If a control panel has both a `series` and `groupby` control, and
* the user has chosen `col1` as the value for the `series` control,
* and `col2` and `col3` as values for the `groupby` control,
* the resulting query will contain three `groupby` columns. This is because
* we considered `series` control a `groupby` query field and its value
* will automatically append the `groupby` field when the query is generated.
*
* It is also possible to define custom controls by importing the
* necessary dependencies and overriding the default parameters, which
* can then be placed in the `controlSetRows` section
* of the `Query` section instead of a predefined control.
*
* import { validateNonEmpty } from '@superset-ui/core';
* import {
* sharedControls,
* ControlConfig,
* ControlPanelConfig,
* } from '@superset-ui/chart-controls';
*
* const myControl: ControlConfig<'SelectControl'> = {
* name: 'secondary_entity',
* config: {
* ...sharedControls.entity,
* type: 'SelectControl',
* label: t('Secondary Entity'),
* mapStateToProps: state => ({
* sharedControls.columnChoices(state.datasource)
* .columns.filter(c => c.groupby)
* })
* validators: [validateNonEmpty],
* },
* }
*
* In addition to the basic drop down control, there are several predefined
* control types (can be set via the `type` property) that can be used. Some
* commonly used examples:
* - SelectControl: Dropdown to select single or multiple values,
usually columns
* - MetricsControl: Dropdown to select metrics, triggering a modal
to define Metric details
* - AdhocFilterControl: Control to choose filters
* - CheckboxControl: A checkbox for choosing true/false values
* - SliderControl: A slider with min/max values
* - TextControl: Control for text data
*
* For more control input types, check out the `incubator-superset` repo
* and open this file: superset-frontend/src/explore/components/controls/index.js
*
* To ensure all controls have been filled out correctly, the following
* validators are provided
* by the `@superset-ui/core/lib/validator`:
* - validateNonEmpty: must have at least one value
* - validateInteger: must be an integer value
* - validateNumber: must be an intger or decimal value
*/
// For control input types, see: superset-frontend/src/explore/components/controls/index.js
controlPanelSections: [
<%if (chartType === 'timeseries') { %>sections.legacyTimeseriesTime,<% } else { %>sections.legacyRegularTime,<% } %>
{
label: t('Query'),
expanded: true,
controlSetRows: [
[
{
name: 'cols',
config: {
...sharedControls.groupby,
label: t('Columns'),
description: t('Columns to group by'),
},
},
],
[
{
name: 'metrics',
config: {
...sharedControls.metrics,
// it's possible to add validators to controls if
// certain selections/types need to be enforced
validators: [validateNonEmpty],
},
},
],
['adhoc_filters'],
[
{
name: 'row_limit',
config: sharedControls.row_limit,
},
],
],
},
{
label: t('Hello Controls!'),
expanded: true,
controlSetRows: [
[
{
name: 'header_text',
config: {
type: 'TextControl',
default: 'Hello, World!',
renderTrigger: true,
// ^ this makes it apply instantaneously, without triggering a "run query" button
label: t('Header Text'),
description: t('The text you want to see in the header'),
},
},
],
[
{
name: 'bold_text',
config: {
type: 'CheckboxControl',
label: t('Bold Text'),
renderTrigger: true,
default: true,
description: t('A checkbox to make the '),
},
},
],
[
{
name: 'header_font_size',
config: {
type: 'SelectControl',
label: t('Font Size'),
default: 'xl',
choices: [
// [value, label]
['xxs', 'xx-small'],
['xs', 'x-small'],
['s', 'small'],
['m', 'medium'],
['l', 'large'],
['xl', 'x-large'],
['xxl', 'xx-large'],
],
renderTrigger: true,
description: t('The size of your header font'),
},
},
],
],
},
],
};
export default config;

View File

@ -0,0 +1,51 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
import thumbnail from '../images/thumbnail.png';
export default class <%= packageLabel %>ChartPlugin extends ChartPlugin {
/**
* The constructor is used to pass relevant metadata and callbacks that get
* registered in respective registries that are used throughout the library
* and application. A more thorough description of each property is given in
* the respective imported file.
*
* It is worth noting that `buildQuery` and is optional, and only needed for
* advanced visualizations that require either post processing operations
* (pivoting, rolling aggregations, sorting etc) or submitting multiple queries.
*/
constructor() {
const metadata = new ChartMetadata({
description: '<%= description %>',
name: t('<%= pluginName %>'),
thumbnail,
});
super({
buildQuery,
controlPanel,
loadChart: () => import('../<%= packageLabel %>'),
metadata,
transformProps,
});
}
}

View File

@ -0,0 +1,72 @@
/**
* 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, TimeseriesDataRecord } from '@superset-ui/core';
export default function transformProps(chartProps: ChartProps) {
/**
* This function is called after a successful response has been
* received from the chart data endpoint, and is used to transform
* the incoming data prior to being sent to the Visualization.
*
* The transformProps function is also quite useful to return
* additional/modified props to your data viz component. The formData
* can also be accessed from your <%= packageLabel %>.tsx file, but
* doing supplying custom props here is often handy for integrating third
* party libraries that rely on specific props.
*
* A description of properties in `chartProps`:
* - `height`, `width`: the height/width of the DOM element in which
* the chart is located
* - `formData`: the chart data request payload that was sent to the
* backend.
* - `queriesData`: the chart data response payload that was received
* from the backend. Some notable properties of `queriesData`:
* - `data`: an array with data, each row with an object mapping
* the column/alias to its value. Example:
* `[{ col1: 'abc', metric1: 10 }, { col1: 'xyz', metric1: 20 }]`
* - `rowcount`: the number of rows in `data`
* - `query`: the query that was issued.
*
* Please note: the transformProps function gets cached when the
* application loads. When making changes to the `transformProps`
* function during development with hot reloading, changes won't
* be seen until restarting the development server.
*/
const { width, height, formData, queriesData } = chartProps;
const { boldText, headerFontSize, headerText } = formData;
const data = queriesData[0].data as TimeseriesDataRecord[];
console.log('formData via TransformProps.ts', formData);
return {
width,
height,
<%if (chartType === 'timeseries') { %>
data: data.map(item => ({
...item,
// convert epoch to native Date
// eslint-disable-next-line no-underscore-dangle
__timestamp: new Date(item.__timestamp as number),
})),<% } else { %> data,<% } %>
// and now your control data, manipulated as needed, and passed through as props!
boldText,
headerFontSize,
headerText,
};
}

View File

@ -0,0 +1,40 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { QueryFormData, supersetTheme, TimeseriesDataRecord } from '@superset-ui/core';
export interface <%= packageLabel %>StylesProps {
height: number;
width: number;
headerFontSize: keyof typeof supersetTheme.typography.sizes;
boldText: boolean;
}
interface <%= packageLabel %>CustomizeProps {
headerText: string;
}
export type <%= packageLabel %>QueryFormData = QueryFormData &
<%= packageLabel %>StylesProps &
<%= packageLabel %>CustomizeProps;
export type <%= packageLabel %>Props = <%= packageLabel %>StylesProps &
<%= packageLabel %>CustomizeProps & {
data: TimeseriesDataRecord[];
// add typing here for the props you pass in from transformProps.ts!
};

View File

@ -0,0 +1,33 @@
/**
* 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 { <%= packageLabel %>ChartPlugin } from '../src';
/**
* The example tests in this file act as a starting point, and
* we encourage you to build more. These tests check that the
* plugin loads properly, and focus on `transformProps`
* to ake sure that data, controls, and props are all
* treated correctly (e.g. formData from plugin controls
* properly transform the data and/or any resulting props).
*/
describe('@superset-ui/plugin-chart-<%= packageName %>', () => {
it('exists', () => {
expect(<%= packageLabel %>ChartPlugin).toBeDefined();
});
});

View File

@ -0,0 +1,34 @@
/**
* 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 buildQuery from '../../src/plugin/buildQuery';
describe('<%= packageLabel %> buildQuery', () => {
const formData = {
datasource: '5__table',
granularity_sqla: 'ds',
series: 'foo',
viz_type: 'my_chart',
};
it('should build groupby with series in form data', () => {
const queryContext = buildQuery(formData);
const [query] = queryContext.queries;
expect(query.groupby).toEqual(['foo']);
});
});

View File

@ -0,0 +1,52 @@
/**
* 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/core';
import transformProps from '../../src/plugin/transformProps';
describe('<%= packageLabel %> tranformProps', () => {
const formData = {
colorScheme: 'bnbColors',
datasource: '3__table',
granularity_sqla: 'ds',
metric: 'sum__num',
series: 'name',
boldText: true,
headerFontSize: 'xs',
headerText: 'my text',
};
const chartProps = new ChartProps({
formData,
width: 800,
height: 600,
queriesData: [{
data: [{ name: 'Hulk', sum__num: 1<%if (chartType === 'timeseries') { %>, __timestamp: 599616000000<% } %> }],
}],
});
it('should tranform chart props for viz', () => {
expect(transformProps(chartProps)).toEqual({
width: 800,
height: 600,
boldText: true,
headerFontSize: 'xs',
headerText: 'my text',
data: [{ name: 'Hulk', sum__num: 1<%if (chartType === 'timeseries') { %>, __timestamp: new Date(599616000000)<% } %> }],
});
});
});

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"declarationDir": "lib",
"outDir": "lib",
"rootDir": "src"
},
"exclude": [
"lib",
"test"
],
"extends": "../../tsconfig.json",
"include": [
"src/**/*",
"types/**/*",
"../../types/**/*"
],
"references": [
{
"path": "../../packages/superset-ui-chart-controls"
},
{
"path": "../../packages/superset-ui-core"
}
]
}

View File

@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
declare module '*.png' {
const value: any;
export default value;
}

View File

@ -0,0 +1,41 @@
{
"name": "@superset-ui/generator-superset",
"version": "0.18.25",
"description": "Scaffolder for Superset",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"author": "Superset",
"files": [
"generators"
],
"main": "generators/index.js",
"keywords": [
"yeoman",
"generator",
"superset",
"yeoman-generator"
],
"devDependencies": {
"yeoman-assert": "^3.1.0",
"yeoman-test": "^2.0.0"
},
"engines": {
"npm": ">= 4.0.0"
},
"dependencies": {
"chalk": "^4.0.0",
"lodash": "^4.17.11",
"yeoman-generator": "^4.0.0",
"yosay": "^2.0.2"
},
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.
*/
const path = require('path');
const assert = require('yeoman-assert');
const helpers = require('yeoman-test');
describe('generator-superset:app', () => {
let dir;
beforeAll(() => {
dir = process.cwd();
return helpers.run(path.join(__dirname, '../generators/app')).withPrompts({
subgenerator: 'package',
name: 'my-package',
});
});
/*
* Change working directory back to original working directory
* after the test has completed.
* yeoman tests switch to tmp directory and write files there.
* Usually this is fine for solo package.
* However, for a monorepo like this one,
* it made jest confuses with current directory
* (being in tmp directory instead of superset-ui root)
* and interferes with other tests in sibling packages
* that are run after the yeoman tests.
*/
afterAll(() => {
process.chdir(dir);
});
it('creates files', () => {
assert.file([
'package.json',
'README.md',
'src/index.ts',
'test/index.test.ts',
]);
});
});

View File

@ -0,0 +1,81 @@
/*
* 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.
*/
const path = require('path');
const assert = require('yeoman-assert');
const helpers = require('yeoman-test');
describe('generator-superset:package', () => {
let dir;
beforeAll(() => {
dir = process.cwd();
});
/*
* Change working directory back to original working directory
* after the test has completed.
* yeoman tests switch to tmp directory and write files there.
* Usually this is fine for solo package.
* However, for a monorepo like this one,
* it made jest confuses with current directory
* (being in tmp directory instead of superset-ui root)
* and interferes with other tests in sibling packages
* that are run after the yeoman tests.
*/
afterAll(() => {
process.chdir(dir);
});
describe('typescript', () => {
beforeAll(() =>
helpers
.run(path.join(__dirname, '../generators/package'))
.withPrompts({ name: 'my-package', language: 'typescript' })
.withOptions({ skipInstall: true }),
);
it('creates files', () => {
assert.file([
'package.json',
'README.md',
'src/index.ts',
'test/index.test.ts',
]);
});
});
describe('javascript', () => {
beforeAll(() =>
helpers
.run(path.join(__dirname, '../generators/package'))
.withPrompts({ name: 'my-package', language: 'javascript' })
.withOptions({ skipInstall: true }),
);
it('creates files', () => {
assert.file([
'package.json',
'README.md',
'src/index.js',
'test/index.test.js',
]);
});
});
});

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* eslint-env node */
const path = require('path');
const assert = require('yeoman-assert');
const helpers = require('yeoman-test');
describe('generator-superset:plugin-chart', () => {
let dir;
beforeAll(() => {
dir = process.cwd();
return helpers
.run(path.join(__dirname, '../generators/plugin-chart'))
.withPrompts({ packageName: 'cold-map', description: 'Cold Map' })
.withOptions({ skipInstall: true });
});
/*
* Change working directory back to original working directory
* after the test has completed.
* yeoman tests switch to tmp directory and write files there.
* Usually this is fine for solo package.
* However, for a monorepo like this one,
* it made jest confuses with current directory
* (being in tmp directory instead of superset-ui root)
* and interferes with other tests in sibling packages
* that are run after the yeoman tests.
*/
afterAll(() => {
process.chdir(dir);
});
it('creates files', () => {
assert.file([
'package.json',
'README.md',
'src/plugin/buildQuery.ts',
'src/plugin/controlPanel.ts',
'src/plugin/index.ts',
'src/plugin/transformProps.ts',
'src/ColdMap.tsx',
'src/index.ts',
'test/index.test.ts',
'test/plugin/buildQuery.test.ts',
'test/plugin/transformProps.test.ts',
'types/external.d.ts',
'src/images/thumbnail.png',
]);
});
});

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"composite": false,
"emitDeclarationOnly": false,
"noEmit": true,
"rootDir": "."
},
"extends": "../../../tsconfig.json",
"include": [
"**/*",
"../types/**/*",
"../../../types/**/*"
],
"references": [
{
"path": ".."
}
]
}

View File

@ -0,0 +1,30 @@
<!--
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.
-->
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30)
### Features
* add certified icon to columoption ([#1330](https://github.com/apache-superset/superset-ui/issues/1330)) ([a415c41](https://github.com/apache-superset/superset-ui/commit/a415c413954bc9c093ab5dfde62d458cf3224073))

View File

@ -0,0 +1,42 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
## @superset-ui/chart-controls
[![Version](https://img.shields.io/npm/v/@superset-ui/chart-controls.svg?style=flat)](https://www.npmjs.com/package/@superset-ui/chart-controls)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fsuperset-ui-chart-controls&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/superset-ui-chart-controls)
Description
#### Example usage
```js
import { xxx } from '@superset-ui/chart-controls';
```
#### API
`fn(args)`
- Do something
### Development
`@data-ui/build-config` is used to manage the build configuration for this package including babel
builds, jest testing, eslint, and prettier.

View File

@ -0,0 +1,41 @@
{
"name": "@superset-ui/chart-controls",
"version": "0.18.25",
"description": "Superset UI control-utils",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@react-icons/all-files": "^4.1.0",
"@superset-ui/core": "0.18.25",
"lodash": "^4.17.15",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@types/react": "*",
"antd": "^4.9.4",
"react": "^16.13.1",
"react-dom": "^16.13.1"
}
}

View File

@ -0,0 +1,71 @@
/**
* 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 { kebabCase } from 'lodash';
import { t, useTheme, styled } from '@superset-ui/core';
import Tooltip from './Tooltip';
interface CertifiedIconWithTooltipProps {
certifiedBy?: string | null;
details?: string | null;
metricName: string;
}
const StyledDiv = styled.div`
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
`;
function CertifiedIconWithTooltip({
certifiedBy,
details,
metricName,
}: CertifiedIconWithTooltipProps) {
const theme = useTheme();
return (
<Tooltip
id={`${kebabCase(metricName)}-tooltip`}
title={
<div>
{certifiedBy && (
<StyledDiv>{t('Certified by %s', certifiedBy)}</StyledDiv>
)}
<div>{details}</div>
</div>
}
>
{/* TODO: move Icons to superset-ui to remove duplicated icon code here */}
<svg
xmlns="http://www.w3.org/2000/svg"
enableBackground="new 0 0 24 24"
height="16"
viewBox="0 0 24 24"
width="16"
>
<g>
<path
fill={theme.colors.primary.base}
d="M23,12l-2.44-2.79l0.34-3.69l-3.61-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7 l3.61,0.82L8.6,22.5l3.4-1.47l3.4,1.46l1.89-3.19l3.61-0.82l-0.34-3.69L23,12z M9.38,16.01L7,13.61c-0.39-0.39-0.39-1.02,0-1.41 l0.07-0.07c0.39-0.39,1.03-0.39,1.42,0l1.61,1.62l5.15-5.16c0.39-0.39,1.03-0.39,1.42,0l0.07,0.07c0.39,0.39,0.39,1.02,0,1.41 l-5.92,5.94C10.41,16.4,9.78,16.4,9.38,16.01z"
/>
</g>
</svg>
</Tooltip>
);
}
export default CertifiedIconWithTooltip;

View File

@ -0,0 +1,101 @@
/**
* 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 { styled } from '@superset-ui/core';
import { Tooltip } from './Tooltip';
import { ColumnTypeLabel } from './ColumnTypeLabel';
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
import CertifiedIconWithTooltip from './CertifiedIconWithTooltip';
import { ColumnMeta } from '../types';
export type ColumnOptionProps = {
column: ColumnMeta;
showType?: boolean;
showTooltip?: boolean;
labelRef?: React.RefObject<any>;
};
const StyleOverrides = styled.span`
svg {
margin-right: ${({ theme }) => theme.gridUnit}px;
}
`;
export function ColumnOption({
column,
labelRef,
showType = false,
showTooltip = true,
}: ColumnOptionProps) {
const { expression, column_name, type_generic } = column;
const hasExpression = expression && expression !== column_name;
const type = hasExpression ? 'expression' : type_generic;
return (
<StyleOverrides>
{showType && type !== undefined && <ColumnTypeLabel type={type} />}
{column.is_certified && (
<CertifiedIconWithTooltip
metricName={column.metric_name}
certifiedBy={column.certified_by}
details={column.certification_details}
/>
)}
{showTooltip ? (
<Tooltip
id="metric-name-tooltip"
title={column.verbose_name || column.column_name}
trigger={['hover']}
placement="top"
>
<span
className="m-r-5 option-label column-option-label"
ref={labelRef}
>
{column.verbose_name || column.column_name}
</span>
</Tooltip>
) : (
<span className="m-r-5 option-label column-option-label" ref={labelRef}>
{column.verbose_name || column.column_name}
</span>
)}
{column.description && (
<InfoTooltipWithTrigger
className="m-r-5 text-muted"
icon="info"
tooltip={column.description}
label={`descr-${column.column_name}`}
placement="top"
/>
)}
{hasExpression && (
<InfoTooltipWithTrigger
className="m-r-5 text-muted"
icon="question-circle-o"
tooltip={column.expression}
label={`expr-${column.column_name}`}
placement="top"
/>
)}
</StyleOverrides>
);
}
export default ColumnOption;

View File

@ -0,0 +1,58 @@
/* eslint-disable no-nested-ternary */
/**
* 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 { GenericDataType } from '@superset-ui/core';
import React from 'react';
type StringIcon = '?' | 'ƒ' | 'AGG' | 'ABC' | '#' | 'T/F' | 'time';
export type ColumnLabelExtendedType = 'expression' | 'aggregate' | '';
export type ColumnTypeLabelProps = {
type?: ColumnLabelExtendedType | GenericDataType;
};
export function ColumnTypeLabel({ type }: ColumnTypeLabelProps) {
let stringIcon: StringIcon = '?';
if (type === '' || type === 'expression') {
stringIcon = 'ƒ';
} else if (type === 'aggregate') {
stringIcon = 'AGG';
} else if (type === GenericDataType.STRING) {
stringIcon = 'ABC';
} else if (type === GenericDataType.NUMERIC) {
stringIcon = '#';
} else if (type === GenericDataType.BOOLEAN) {
stringIcon = 'T/F';
} else if (type === GenericDataType.TEMPORAL) {
stringIcon = 'time';
}
const typeIcon =
stringIcon === 'time' ? (
<i className="fa fa-clock-o type-label" />
) : (
<div className="type-label">{stringIcon}</div>
);
return <span>{typeIcon}</span>;
}
export default ColumnTypeLabel;

View File

@ -0,0 +1,124 @@
/**
* 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, { useState, FunctionComponentElement, ChangeEvent } from 'react';
import { JsonValue, useTheme } from '@superset-ui/core';
import ControlHeader, { ControlHeaderProps } from '../ControlHeader';
import InfoTooltipWithTrigger from '../InfoTooltipWithTrigger';
import { ControlFormItemComponents, ControlFormItemSpec } from './controls';
export * from './controls';
export type ControlFormItemProps = ControlFormItemSpec & {
name: string;
onChange?: (fieldValue: JsonValue) => void;
};
export type ControlFormItemNode =
FunctionComponentElement<ControlFormItemProps>;
/**
* Accept `false` or `0`, but not empty string.
*/
function isEmptyValue(value?: JsonValue) {
return value == null || value === '';
}
export function ControlFormItem({
name,
label,
description,
width,
validators,
required,
onChange,
value: initialValue,
defaultValue,
controlType,
...props
}: ControlFormItemProps) {
const { gridUnit } = useTheme();
const [hovered, setHovered] = useState(false);
const [value, setValue] = useState(
initialValue === undefined ? defaultValue : initialValue,
);
const [validationErrors, setValidationErrors] =
useState<ControlHeaderProps['validationErrors']>();
const handleChange = (e: ChangeEvent<HTMLInputElement> | JsonValue) => {
const fieldValue =
e && typeof e === 'object' && 'target' in e
? e.target.type === 'checkbox' || e.target.type === 'radio'
? e.target.checked
: e.target.value
: e;
const errors =
(validators
?.map(validator =>
!required && isEmptyValue(fieldValue) ? false : validator(fieldValue),
)
.filter(x => !!x) as string[]) || [];
setValidationErrors(errors);
setValue(fieldValue);
if (errors.length === 0 && onChange) {
onChange(fieldValue as JsonValue);
}
};
const Control = ControlFormItemComponents[controlType];
return (
<div
css={{
margin: 2 * gridUnit,
width,
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{controlType === 'Checkbox' ? (
<ControlFormItemComponents.Checkbox
checked={value as boolean}
onChange={handleChange}
>
{label}{' '}
{hovered && description && (
<InfoTooltipWithTrigger tooltip={description} />
)}
</ControlFormItemComponents.Checkbox>
) : (
<>
{label && (
<ControlHeader
name={name}
label={label}
description={description}
validationErrors={validationErrors}
hovered={hovered}
required={required}
/>
)}
{/* @ts-ignore */}
<Control {...props} value={value} onChange={handleChange} />
</>
)}
</div>
);
}
export default ControlFormItem;

View File

@ -0,0 +1,92 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { ReactNode } from 'react';
import { Slider, InputNumber, Input } from 'antd';
import Checkbox, { CheckboxProps } from 'antd/lib/checkbox';
import Select, { SelectOption } from '../Select';
import RadioButtonControl, {
RadioButtonOption,
} from '../../shared-controls/components/RadioButtonControl';
export const ControlFormItemComponents = {
Slider,
InputNumber,
Input,
Select,
// Directly export Checkbox will result in "using name from external module" error
// ref: https://stackoverflow.com/questions/43900035/ts4023-exported-variable-x-has-or-is-using-name-y-from-external-module-but
Checkbox: Checkbox as React.ForwardRefExoticComponent<
CheckboxProps & React.RefAttributes<HTMLInputElement>
>,
RadioButtonControl,
};
export type ControlType = keyof typeof ControlFormItemComponents;
export type ControlFormValueValidator<V> = (value: V) => string | false;
export type ControlFormItemSpec<T extends ControlType = ControlType> = {
controlType: T;
label: ReactNode;
description: ReactNode;
placeholder?: string;
required?: boolean;
validators?: ControlFormValueValidator<any>[];
width?: number | string;
/**
* Time to delay change propagation.
*/
debounceDelay?: number;
} & (T extends 'Select'
? {
options: SelectOption<any>[];
value?: string;
defaultValue?: string;
creatable?: boolean;
minWidth?: number | string;
validators?: ControlFormValueValidator<string>[];
}
: T extends 'RadioButtonControl'
? {
options: RadioButtonOption[];
value?: string;
defaultValue?: string;
}
: T extends 'Checkbox'
? {
value?: boolean;
defaultValue?: boolean;
}
: T extends 'InputNumber' | 'Slider'
? {
min?: number;
max?: number;
step?: number;
value?: number;
defaultValue?: number;
validators?: ControlFormValueValidator<number>[];
}
: T extends 'Input'
? {
controlType: 'Input';
value?: string;
defaultValue?: string;
validators?: ControlFormValueValidator<string>[];
}
: {});

View File

@ -0,0 +1,135 @@
/**
* 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, { FunctionComponentElement, useMemo } from 'react';
import {
FAST_DEBOUNCE,
JsonObject,
JsonValue,
useTheme,
} from '@superset-ui/core';
import { debounce } from 'lodash';
import { ControlFormItemNode } from './ControlFormItem';
export * from './ControlFormItem';
export type ControlFormRowProps = {
children: ControlFormItemNode | ControlFormItemNode[];
};
export function ControlFormRow({ children }: ControlFormRowProps) {
const { gridUnit } = useTheme();
return (
<div
css={{
display: 'flex',
flexWrap: 'nowrap',
margin: -2 * gridUnit,
marginBottom: gridUnit,
}}
>
{children}
</div>
);
}
type ControlFormRowNode = FunctionComponentElement<ControlFormRowProps>;
export type ControlFormProps = {
/**
* Form field values dict.
*/
value?: JsonObject;
onChange: (value: JsonObject) => void;
children: ControlFormRowNode | ControlFormRowNode[];
};
/**
* Light weight form for control panel.
*/
export default function ControlForm({
onChange,
value,
children,
}: ControlFormProps) {
const theme = useTheme();
const debouncedOnChange = useMemo(
() =>
({
0: onChange,
[FAST_DEBOUNCE]: debounce(onChange, FAST_DEBOUNCE),
} as Record<number, typeof onChange>),
[onChange],
);
const updatedChildren = React.Children.map(children, row => {
if ('children' in row.props) {
const defaultWidth = Array.isArray(row.props.children)
? `${100 / row.props.children.length}%`
: undefined;
return React.cloneElement(row, {
children: React.Children.map(row.props.children, item => {
const {
name,
width,
debounceDelay = FAST_DEBOUNCE,
onChange: onItemValueChange,
} = item.props;
return React.cloneElement(item, {
width: width || defaultWidth,
value: value?.[name],
// remove `debounceDelay` from rendered control item props
// so React DevTools don't throw a `invalid prop` warning.
debounceDelay: undefined,
onChange(fieldValue: JsonValue) {
// call `onChange` on each FormItem
if (onItemValueChange) {
onItemValueChange(fieldValue);
}
// propagate to the form
if (!(debounceDelay in debouncedOnChange)) {
debouncedOnChange[debounceDelay] = debounce(
onChange,
debounceDelay,
);
}
debouncedOnChange[debounceDelay]({
...value,
[name]: fieldValue,
});
},
});
}),
});
}
return row;
});
return (
<div
css={{
label: {
textTransform: 'uppercase',
color: theme.colors.text.label,
fontSize: theme.typography.sizes.s,
},
}}
>
{updatedChildren}
</div>
);
}

View File

@ -0,0 +1,142 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { ReactNode } from 'react';
import { t } from '@superset-ui/core';
import { InfoTooltipWithTrigger } from './InfoTooltipWithTrigger';
import { Tooltip } from './Tooltip';
type ValidationError = string;
export type ControlHeaderProps = {
name?: string;
label?: ReactNode;
description?: ReactNode;
validationErrors?: ValidationError[];
renderTrigger?: boolean;
rightNode?: ReactNode;
leftNode?: ReactNode;
hovered?: boolean;
required?: boolean;
warning?: string;
danger?: string;
onClick?: () => void;
tooltipOnClick?: () => void;
};
export default function ControlHeader({
name,
description,
label,
tooltipOnClick,
onClick,
warning,
danger,
leftNode,
rightNode,
validationErrors = [],
renderTrigger = false,
hovered = false,
required = false,
}: ControlHeaderProps) {
const renderOptionalIcons = () => {
if (hovered) {
return (
<span>
{description && (
<span>
<InfoTooltipWithTrigger
label={t('description')}
tooltip={description}
placement="top"
onClick={tooltipOnClick}
/>{' '}
</span>
)}
{renderTrigger && (
<span>
<InfoTooltipWithTrigger
label={t('bolt')}
tooltip={t('Changing this control takes effect instantly')}
placement="top"
icon="bolt"
/>{' '}
</span>
)}
</span>
);
}
return null;
};
if (!label) {
return null;
}
const labelClass = validationErrors.length > 0 ? 'text-danger' : '';
return (
<div className="ControlHeader" data-test={`${name}-header`}>
<div className="pull-left">
<label className="control-label" htmlFor={name}>
{leftNode && <span>{leftNode}</span>}
<span
role="button"
tabIndex={0}
onClick={onClick}
className={labelClass}
style={{ cursor: onClick ? 'pointer' : '' }}
>
{label}
</span>{' '}
{warning && (
<span>
<Tooltip id="error-tooltip" placement="top" title={warning}>
<i className="fa fa-exclamation-circle text-warning" />
</Tooltip>{' '}
</span>
)}
{danger && (
<span>
<Tooltip id="error-tooltip" placement="top" title={danger}>
<i className="fa fa-exclamation-circle text-danger" />
</Tooltip>{' '}
</span>
)}
{validationErrors.length > 0 && (
<span>
<Tooltip
id="error-tooltip"
placement="top"
title={validationErrors.join(' ')}
>
<i className="fa fa-exclamation-circle text-danger" />
</Tooltip>{' '}
</span>
)}
{renderOptionalIcons()}
{required && (
<span className="text-danger m-l-4">
<strong>*</strong>
</span>
)}
</label>
</div>
{rightNode && <div className="pull-right">{rightNode}</div>}
<div className="clearfix" />
</div>
);
}

View File

@ -0,0 +1,79 @@
/**
* 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 { kebabCase } from 'lodash';
import { TooltipPlacement } from 'antd/lib/tooltip';
import { t } from '@superset-ui/core';
import { Tooltip, TooltipProps } from './Tooltip';
export interface InfoTooltipWithTriggerProps {
label?: string;
tooltip?: TooltipProps['title'];
icon?: string;
onClick?: () => void;
placement?: TooltipPlacement;
bsStyle?: string;
className?: string;
}
export function InfoTooltipWithTrigger({
label,
tooltip,
bsStyle,
onClick,
icon = 'info-circle',
className = 'text-muted',
placement = 'right',
}: InfoTooltipWithTriggerProps) {
const iconClass = `fa fa-${icon} ${className} ${
bsStyle ? `text-${bsStyle}` : ''
}`;
const iconEl = (
<i
role="button"
aria-label={t('Show info tooltip')}
tabIndex={0}
className={iconClass}
style={{ cursor: onClick ? 'pointer' : undefined }}
onClick={onClick}
onKeyPress={
onClick &&
(event => {
if (event.key === 'Enter' || event.key === ' ') {
onClick();
}
})
}
/>
);
if (!tooltip) {
return iconEl;
}
return (
<Tooltip
id={`${kebabCase(label)}-tooltip`}
title={tooltip}
placement={placement}
>
{iconEl}
</Tooltip>
);
}
export default InfoTooltipWithTrigger;

View File

@ -0,0 +1,117 @@
/**
* 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 { styled, Metric, SafeMarkdown } from '@superset-ui/core';
import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
import { ColumnTypeLabel } from './ColumnTypeLabel';
import CertifiedIconWithTooltip from './CertifiedIconWithTooltip';
import Tooltip from './Tooltip';
const FlexRowContainer = styled.div`
align-items: center;
display: flex;
> svg {
margin-right: ${({ theme }) => theme.gridUnit}px;
}
`;
export interface MetricOptionProps {
metric: Omit<Metric, 'id'> & { label?: string };
openInNewWindow?: boolean;
showFormula?: boolean;
showType?: boolean;
showTooltip?: boolean;
url?: string;
labelRef?: React.RefObject<any>;
}
export function MetricOption({
metric,
labelRef,
openInNewWindow = false,
showFormula = true,
showType = false,
showTooltip = true,
url = '',
}: MetricOptionProps) {
const verbose = metric.verbose_name || metric.metric_name || metric.label;
const link = url ? (
<a href={url} target={openInNewWindow ? '_blank' : ''} rel="noreferrer">
{verbose}
</a>
) : (
verbose
);
const warningMarkdown = metric.warning_markdown || metric.warning_text;
return (
<FlexRowContainer className="metric-option">
{showType && <ColumnTypeLabel type="expression" />}
{metric.is_certified && (
<CertifiedIconWithTooltip
metricName={metric.metric_name}
certifiedBy={metric.certified_by}
details={metric.certification_details}
/>
)}
{showTooltip ? (
<Tooltip
id="metric-name-tooltip"
title={verbose}
trigger={['hover']}
placement="top"
>
<span className="option-label metric-option-label" ref={labelRef}>
{link}
</span>
</Tooltip>
) : (
<span className="option-label metric-option-label" ref={labelRef}>
{link}
</span>
)}
{metric.description && (
<InfoTooltipWithTrigger
className="text-muted"
icon="info"
tooltip={metric.description}
label={`descr-${metric.metric_name}`}
/>
)}
{showFormula && (
<InfoTooltipWithTrigger
className="text-muted"
icon="question-circle-o"
tooltip={metric.expression}
label={`expr-${metric.metric_name}`}
/>
)}
{warningMarkdown && (
<InfoTooltipWithTrigger
className="text-warning"
icon="warning"
tooltip={<SafeMarkdown source={warningMarkdown} />}
label={`warn-${metric.metric_name}`}
/>
)}
</FlexRowContainer>
);
}

View File

@ -0,0 +1,107 @@
/**
* 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, { useState, ReactNode } from 'react';
import AntdSelect, { SelectProps as AntdSelectProps } from 'antd/lib/select';
export const { Option }: any = AntdSelect;
export type SelectOption<VT = string> = [VT, ReactNode];
export type SelectProps<VT> = Omit<AntdSelectProps<VT>, 'options'> & {
creatable?: boolean;
minWidth?: string | number;
options?: SelectOption<VT>[];
};
/**
* AntD select with creatable options.
*/
export default function Select<VT extends string | number>({
creatable,
onSearch,
dropdownMatchSelectWidth = false,
minWidth = '100%',
showSearch: showSearch_ = true,
onChange,
options,
children,
value,
...props
}: SelectProps<VT>) {
const [searchValue, setSearchValue] = useState<string>();
// force show search if creatable
const showSearch = showSearch_ || creatable;
const handleSearch = showSearch
? (input: string) => {
if (creatable) {
setSearchValue(input);
}
if (onSearch) {
onSearch(input);
}
}
: undefined;
const optionsHasSearchValue = options?.some(([val]) => val === searchValue);
const optionsHasValue = options?.some(([val]) => val === value);
const handleChange: SelectProps<VT>['onChange'] = showSearch
? (val, opt) => {
// reset input value once selected
setSearchValue('');
if (onChange) {
onChange(val, opt);
}
}
: onChange;
return (
<AntdSelect<VT>
dropdownMatchSelectWidth={dropdownMatchSelectWidth}
showSearch={showSearch}
onSearch={handleSearch}
onChange={handleChange}
value={value}
{...props}
css={{
minWidth,
}}
>
{options?.map(([val, label]) => (
<Option value={val}>{label}</Option>
))}
{children}
{value && !optionsHasValue && (
<Option key={value} value={value}>
{value}
</Option>
)}
{searchValue && !optionsHasSearchValue && (
<Option key={searchValue} value={searchValue}>
{/* Unfortunately AntD select does not support displaying different
label for option vs select value, so we can't use
`t('Create "%s"', searchValue)` here */}
{searchValue}
</Option>
)}
</AntdSelect>
);
}
Select.Option = Option;

View File

@ -0,0 +1,58 @@
/*
* 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 { useTheme, css } from '@superset-ui/core';
import { Tooltip as BaseTooltip } from 'antd';
import { TooltipProps } from 'antd/lib/tooltip';
import { Global } from '@emotion/react';
export { TooltipProps } from 'antd/lib/tooltip';
export const Tooltip = ({ overlayStyle, color, ...props }: TooltipProps) => {
const theme = useTheme();
const defaultColor = `${theme.colors.grayscale.dark2}e6`;
return (
<>
{/* Safari hack to hide browser default tooltips */}
<Global
styles={css`
.ant-tooltip-open {
display: inline-block;
&::after {
content: '';
display: block;
}
}
`}
/>
<BaseTooltip
overlayStyle={{
fontSize: theme.typography.sizes.s,
lineHeight: '1.6',
...overlayStyle,
}}
color={defaultColor || color}
{...props}
/>
</>
);
};
export default Tooltip;

View File

@ -0,0 +1,47 @@
/**
* 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, QueryMode, DTTM_ALIAS, GenericDataType } from '@superset-ui/core';
import { ColumnMeta } from './types';
// eslint-disable-next-line import/prefer-default-export
export const TIME_FILTER_LABELS = {
time_range: t('Time Range'),
granularity_sqla: t('Time Column'),
time_grain_sqla: t('Time Grain'),
druid_time_origin: t('Origin'),
granularity: t('Time Granularity'),
};
export const COLUMN_NAME_ALIASES: Record<string, string> = {
[DTTM_ALIAS]: t('Time'),
};
export const TIME_COLUMN_OPTION: ColumnMeta = {
verbose_name: COLUMN_NAME_ALIASES[DTTM_ALIAS],
column_name: DTTM_ALIAS,
type_generic: GenericDataType.TEMPORAL,
description: t(
'A reference to the [Time] configuration, taking granularity into account',
),
};
export const QueryModeLabel = {
[QueryMode.aggregate]: t('Aggregate'),
[QueryMode.raw]: t('Raw records'),
};

View File

@ -0,0 +1,43 @@
/**
* 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 sectionsModule from './sections';
export * from './utils';
export * from './constants';
export * from './operators';
// can't do `export * as sections from './sections'`, babel-transformer will fail
export const sections = sectionsModule;
export * from './components/InfoTooltipWithTrigger';
export * from './components/ColumnOption';
export * from './components/ColumnTypeLabel';
export * from './components/MetricOption';
// React control components
export {
sharedControls,
dndEntity,
dndColumnsControl,
} from './shared-controls';
export { default as sharedControlComponents } from './shared-controls/components';
export { legacySortBy } from './shared-controls/legacySortBy';
export * from './shared-controls/emitFilterControl';
export * from './shared-controls/components';
export * from './types';

View File

@ -0,0 +1,66 @@
/**
* 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 limitationsxw
* under the License.
*/
import {
ensureIsArray,
getColumnLabel,
getMetricLabel,
PostProcessingBoxplot,
} from '@superset-ui/core';
import { PostProcessingFactory } from './types';
type BoxPlotQueryObjectWhiskerType =
PostProcessingBoxplot['options']['whisker_type'];
const PERCENTILE_REGEX = /(\d+)\/(\d+) percentiles/;
export const boxplotOperator: PostProcessingFactory<
PostProcessingBoxplot | undefined
> = (formData, queryObject) => {
const { groupby, whiskerOptions } = formData;
if (whiskerOptions) {
let whiskerType: BoxPlotQueryObjectWhiskerType;
let percentiles: [number, number] | undefined;
const percentileMatch = PERCENTILE_REGEX.exec(whiskerOptions as string);
if (whiskerOptions === 'Tukey' || !whiskerOptions) {
whiskerType = 'tukey';
} else if (whiskerOptions === 'Min/max (no outliers)') {
whiskerType = 'min/max';
} else if (percentileMatch) {
whiskerType = 'percentile';
percentiles = [
parseInt(percentileMatch[1], 10),
parseInt(percentileMatch[2], 10),
];
} else {
throw new Error(`Unsupported whisker type: ${whiskerOptions}`);
}
return {
operation: 'boxplot',
options: {
whisker_type: whiskerType,
percentiles,
groupby: ensureIsArray(groupby).map(getColumnLabel),
metrics: ensureIsArray(queryObject.metrics).map(getMetricLabel),
},
};
}
return undefined;
};

View File

@ -0,0 +1,34 @@
/**
* 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 limitationsxw
* under the License.
*/
import { PostProcessingContribution } from '@superset-ui/core';
import { PostProcessingFactory } from './types';
export const contributionOperator: PostProcessingFactory<
PostProcessingContribution | undefined
> = (formData, queryObject) => {
if (formData.contributionMode) {
return {
operation: 'contribution',
options: {
orientation: formData.contributionMode,
},
};
}
return undefined;
};

View File

@ -0,0 +1,29 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
export { rollingWindowOperator } from './rollingWindowOperator';
export { timeCompareOperator } from './timeCompareOperator';
export { timeComparePivotOperator } from './timeComparePivotOperator';
export { sortOperator } from './sortOperator';
export { pivotOperator } from './pivotOperator';
export { resampleOperator } from './resampleOperator';
export { contributionOperator } from './contributionOperator';
export { prophetOperator } from './prophetOperator';
export { boxplotOperator } from './boxplotOperator';
export * from './utils';

View File

@ -0,0 +1,54 @@
/**
* 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 limitationsxw
* under the License.
*/
import {
ensureIsArray,
getColumnLabel,
getMetricLabel,
PostProcessingPivot,
} from '@superset-ui/core';
import { PostProcessingFactory } from './types';
import { TIME_COLUMN, isValidTimeCompare } from './utils';
import { timeComparePivotOperator } from './timeComparePivotOperator';
export const pivotOperator: PostProcessingFactory<
PostProcessingPivot | undefined
> = (formData, queryObject) => {
const metricLabels = ensureIsArray(queryObject.metrics).map(getMetricLabel);
if (queryObject.is_timeseries && metricLabels.length) {
if (isValidTimeCompare(formData, queryObject)) {
return timeComparePivotOperator(formData, queryObject);
}
return {
operation: 'pivot',
options: {
index: [TIME_COLUMN],
columns: ensureIsArray(queryObject.columns).map(getColumnLabel),
// Create 'dummy' mean aggregates to assign cell values in pivot table
// use the 'mean' aggregates to avoid drop NaN. PR: https://github.com/apache-superset/superset-ui/pull/1231
aggregates: Object.fromEntries(
metricLabels.map(metric => [metric, { operator: 'mean' }]),
),
drop_missing_columns: false,
},
};
}
return undefined;
};

View File

@ -0,0 +1,39 @@
/**
* 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 limitationsxw
* under the License.
*/
import { PostProcessingProphet } from '@superset-ui/core';
import { PostProcessingFactory } from './types';
export const prophetOperator: PostProcessingFactory<
PostProcessingProphet | undefined
> = (formData, queryObject) => {
if (formData.forecastEnabled) {
return {
operation: 'prophet',
options: {
time_grain: formData.time_grain_sqla,
periods: parseInt(formData.forecastPeriods, 10),
confidence_interval: parseFloat(formData.forecastInterval),
yearly_seasonality: formData.forecastSeasonalityYearly,
weekly_seasonality: formData.forecastSeasonalityWeekly,
daily_seasonality: formData.forecastSeasonalityDaily,
},
};
}
return undefined;
};

View File

@ -0,0 +1,42 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
import { PostProcessingResample } from '@superset-ui/core';
import { PostProcessingFactory } from './types';
import { TIME_COLUMN } from './utils';
export const resampleOperator: PostProcessingFactory<
PostProcessingResample | undefined
> = (formData, queryObject) => {
const resampleZeroFill = formData.resample_method === 'zerofill';
const resampleMethod = resampleZeroFill ? 'asfreq' : formData.resample_method;
const resampleRule = formData.resample_rule;
if (resampleMethod && resampleRule) {
return {
operation: 'resample',
options: {
method: resampleMethod,
rule: resampleRule,
fill_value: resampleZeroFill ? 0 : null,
time_column: TIME_COLUMN,
},
};
}
return undefined;
};

View File

@ -0,0 +1,93 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
import {
ensureIsInt,
ensureIsArray,
RollingType,
PostProcessingRolling,
PostProcessingCum,
ComparisionType,
} from '@superset-ui/core';
import {
getMetricOffsetsMap,
isValidTimeCompare,
TIME_COMPARISON_SEPARATOR,
} from './utils';
import { PostProcessingFactory } from './types';
export const rollingWindowOperator: PostProcessingFactory<
PostProcessingRolling | PostProcessingCum | undefined
> = (formData, queryObject) => {
let columns: (string | undefined)[];
if (isValidTimeCompare(formData, queryObject)) {
const metricsMap = getMetricOffsetsMap(formData, queryObject);
const comparisonType = formData.comparison_type;
if (comparisonType === ComparisionType.Values) {
// time compare type: actual values
columns = [
...Array.from(metricsMap.values()),
...Array.from(metricsMap.keys()),
];
} else {
// time compare type: difference / percentage / ratio
columns = Array.from(metricsMap.entries()).map(([offset, metric]) =>
[comparisonType, metric, offset].join(TIME_COMPARISON_SEPARATOR),
);
}
} else {
columns = ensureIsArray(queryObject.metrics).map(metric => {
if (typeof metric === 'string') {
return metric;
}
return metric.label;
});
}
const columnsMap = Object.fromEntries(columns.map(col => [col, col]));
if (formData.rolling_type === RollingType.Cumsum) {
return {
operation: 'cum',
options: {
operator: 'sum',
columns: columnsMap,
is_pivot_df: true,
},
};
}
if (
[RollingType.Sum, RollingType.Mean, RollingType.Std].includes(
formData.rolling_type,
)
) {
return {
operation: 'rolling',
options: {
rolling_type: formData.rolling_type,
window: ensureIsInt(formData.rolling_periods, 1),
min_periods: ensureIsInt(formData.min_periods, 0),
columns: columnsMap,
is_pivot_df: true,
},
};
}
return undefined;
};

View File

@ -0,0 +1,41 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
import { PostProcessingSort, RollingType } from '@superset-ui/core';
import { PostProcessingFactory } from './types';
import { TIME_COLUMN } from './utils';
export const sortOperator: PostProcessingFactory<
PostProcessingSort | undefined
> = (formData, queryObject) => {
if (
queryObject.is_timeseries &&
Object.values(RollingType).includes(formData.rolling_type)
) {
return {
operation: 'sort',
options: {
columns: {
[TIME_COLUMN]: true,
},
},
};
}
return undefined;
};

View File

@ -0,0 +1,46 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
import { ComparisionType, PostProcessingCompare } from '@superset-ui/core';
import { getMetricOffsetsMap, isValidTimeCompare } from './utils';
import { PostProcessingFactory } from './types';
export const timeCompareOperator: PostProcessingFactory<
PostProcessingCompare | undefined
> = (formData, queryObject) => {
const comparisonType = formData.comparison_type;
const metricOffsetMap = getMetricOffsetsMap(formData, queryObject);
if (
isValidTimeCompare(formData, queryObject) &&
comparisonType !== ComparisionType.Values
) {
return {
operation: 'compare',
options: {
source_columns: Array.from(metricOffsetMap.values()),
compare_columns: Array.from(metricOffsetMap.keys()),
compare_type: comparisonType,
drop_original_columns: true,
},
};
}
return undefined;
};

View File

@ -0,0 +1,70 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
import {
ComparisionType,
PostProcessingPivot,
NumpyFunction,
ensureIsArray,
getColumnLabel,
} from '@superset-ui/core';
import {
getMetricOffsetsMap,
isValidTimeCompare,
TIME_COMPARISON_SEPARATOR,
} from './utils';
import { PostProcessingFactory } from './types';
export const timeComparePivotOperator: PostProcessingFactory<
PostProcessingPivot | undefined
> = (formData, queryObject) => {
const comparisonType = formData.comparison_type;
const metricOffsetMap = getMetricOffsetsMap(formData, queryObject);
if (isValidTimeCompare(formData, queryObject)) {
const valuesAgg = Object.fromEntries(
[...metricOffsetMap.values(), ...metricOffsetMap.keys()].map(metric => [
metric,
// use the 'mean' aggregates to avoid drop NaN
{ operator: 'mean' as NumpyFunction },
]),
);
const changeAgg = Object.fromEntries(
[...metricOffsetMap.entries()]
.map(([offset, metric]) =>
[comparisonType, metric, offset].join(TIME_COMPARISON_SEPARATOR),
)
// use the 'mean' aggregates to avoid drop NaN
.map(metric => [metric, { operator: 'mean' as NumpyFunction }]),
);
return {
operation: 'pivot',
options: {
index: ['__timestamp'],
columns: ensureIsArray(queryObject.columns).map(getColumnLabel),
aggregates:
comparisonType === ComparisionType.Values ? valuesAgg : changeAgg,
drop_missing_columns: false,
},
};
}
return undefined;
};

View File

@ -0,0 +1,23 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitationsxw
* under the License.
*/
import { QueryFormData, QueryObject } from '@superset-ui/core';
export interface PostProcessingFactory<T> {
(formData: QueryFormData, queryObject: QueryObject): T;
}

View File

@ -0,0 +1,21 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
export const TIME_COMPARISON_SEPARATOR = '__';
export const TIME_COLUMN = '__timestamp';

View File

@ -0,0 +1,50 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
import { getMetricLabel, ensureIsArray } from '@superset-ui/core';
import { PostProcessingFactory } from '../types';
import { TIME_COMPARISON_SEPARATOR } from './constants';
export const getMetricOffsetsMap: PostProcessingFactory<Map<string, string>> = (
formData,
queryObject,
) => {
/*
return metric offset-label and metric-label hashmap, for instance:
{
"SUM(value)__1 year ago": "SUM(value)",
"SUM(value)__2 year ago": "SUM(value)"
}
*/
const queryMetrics = ensureIsArray(queryObject.metrics);
const timeOffsets = ensureIsArray(formData.time_compare);
const metricLabels = queryMetrics.map(getMetricLabel);
const metricOffsetMap = new Map<string, string>();
metricLabels.forEach((metric: string) => {
timeOffsets.forEach((offset: string) => {
metricOffsetMap.set(
[metric, offset].join(TIME_COMPARISON_SEPARATOR),
metric,
);
});
});
return metricOffsetMap;
};

View File

@ -0,0 +1,22 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
export { getMetricOffsetsMap } from './getMetricOffsetsMap';
export { isValidTimeCompare } from './isValidTimeCompare';
export { TIME_COMPARISON_SEPARATOR, TIME_COLUMN } from './constants';

View File

@ -0,0 +1,35 @@
/* eslint-disable camelcase */
/**
* 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 limitationsxw
* under the License.
*/
import { ComparisionType } from '@superset-ui/core';
import { getMetricOffsetsMap } from './getMetricOffsetsMap';
import { PostProcessingFactory } from '../types';
export const isValidTimeCompare: PostProcessingFactory<boolean> = (
formData,
queryObject,
) => {
const comparisonType = formData.comparison_type;
const metricOffsetMap = getMetricOffsetsMap(formData, queryObject);
return (
Object.values(ComparisionType).includes(comparisonType) &&
metricOffsetMap.size > 0
);
};

Some files were not shown because too many files have changed in this diff Show More