Compare commits

..

37 Commits

Author SHA1 Message Date
Christine Chambers
11bace3808 Merge pull request #6805 from lyft/xtinec--fix-sticky-tooltip
Fix sticky tooltips on nvd3 vizzes
2019-02-01 15:07:06 -08:00
Maxime Beauchemin
ba64ae6974 Add missing license (#6801) 2019-02-01 14:42:37 -08:00
Christine Chambers
7846bae8e0 Merge pull request #6781 from lyft/xtinec--merge-feature-flags
Merge default feature flags and user defined feature flags
2019-02-01 14:36:25 -08:00
Christine Chambers
501340b5db Fix sticky tooltips on nvd3 vizzes
Currently, we attempt to hide the nvd3 tooltips (if any were on screen) before we draw a new viz after rerunning a query. The hiding is done by selecting the first nvtooltip element and setting the opacity to 0.

This somtimes leave behind a trail of old tooltips if a tooltip is left behind by this nvd3 bug https://github.com/novus/nvd3/issues/1262. This PR modifies the behavior of how we clean up tooltips between rerun of queries by selecting all nvd3 tooltips and removing them all from the DOM before redrawing nvd3 vizzes.
2019-02-01 14:30:13 -08:00
Christine Chambers
19b3753d2c Move feature flag utility function into superset/__init__.py 2019-02-01 13:35:50 -08:00
AlexRRR
416534add5 fix: don't remove leading new line (#6800) 2019-02-01 12:03:34 -08:00
Maxime Beauchemin
713b0ae4f4 [cosmetic] TableSelector use <i> instead of <Button> for refresh (#6783)
* [cosmetic] TableSelector use <i> instead of <Button> for refresh

* Add ASF licenses

* css hover

* missing license

* remove license header
2019-02-01 12:01:30 -08:00
Christine Chambers
5669a82350 Merge pull request #6797 from lyft/VIZ-187
Fix play slider in deck.gl visualizations
2019-01-31 22:02:56 -08:00
Beto Dealmeida
a09348d0ec Fix playslider 2019-01-31 17:23:59 -08:00
Wonjin Kim
adc9a6b495 Add Korean Translation (#6790)
* fix #6760

* start adding Korean translation

* Add Korean translation
2019-01-31 14:13:43 -08:00
John Bodley
2631558ac4 Revert "Removing uniqueness constraints on tables table" (#6777)
* Revert "creating new circular-json safe stringify and replacing one call (#6772)"

This reverts commit 11a7ad00b7.

* Revert "Improve Unicode support for MSSQL (#6690)"

This reverts commit c44ae612df.

* Revert "Fix uniqueness constraints on tables table (#6718)"

This reverts commit c4fb7a0a87.
2019-01-31 09:57:51 -08:00
Christine Chambers
b70a9ae524 Merge default feature flags and user defined feature flags
- Rename the default feature flags key in `config.py` to DEFAULT_FEATURE_FLAGS
- Merge default feature flags with user defined ones allowing the latter to overwrite the former
- Expose feature_flags for both server and client to use
- Add a utility method for checking whether a feature flag is on on server side
2019-01-30 22:23:27 -08:00
agrawaldevesh
817783f466 Allow any column to be filterable or groupable (#6764)
This is not configurable and will be the default (and only !) behavior
going forward !
2019-01-30 11:18:49 -08:00
Maxime Beauchemin
36176f3e20 [sql lab] fix stuck offline (#6782) 2019-01-30 11:12:31 -08:00
Michael McDuffee
11a7ad00b7 creating new circular-json safe stringify and replacing one call (#6772) 2019-01-29 10:59:42 -08:00
Ville Brofeldt
c44ae612df Improve Unicode support for MSSQL (#6690)
* Implement unicode where cluases for mssql queries

* Add comment about unicode support on sqla 1.3+
2019-01-28 22:56:46 -08:00
agrawaldevesh
c4fb7a0a87 Fix uniqueness constraints on tables table (#6718)
Summary: Superset code enforces (in Table crud view pre_add) that the
table is unique within <database, schema, table_name). Indeed in commit
15b67b2c6c (in 2016), the model was
updated to reflect that. However, it was never ported over to a
migration.

I am fixing that in this diff. I am choosing to make this be a new
migration instead of fixing an existing one since I want to fix existing
installations also cleanly.

I also considered removing the uniqueness constraint, but that won't
work: First because anyway there are other places where the <database,
schema, table> uniqueness is enforced in code. But also, the .sql field
isn't a first citizen yet: The schema of the table is picked up from the
table-name and the sql part is only used when creating the explore
query. So indeed we want this uniqueness constraint. (Also it breaks the
unit tests in dict_import_export_tests.py)
[Perhaps it can be removed when we have true .sql support, but for now
the user would have to create a database view and he can use that as the
'table name'. That way he gets schema inference also]

Also added INFO logging to the alembic migration.
2019-01-28 22:49:31 -08:00
Wonjin Kim
5938ac30d6 fix #6760 (#6762) 2019-01-28 22:45:16 -08:00
Maxime Beauchemin
7c02587924 [docs] improve upgrading instructions (#6766) 2019-01-28 22:14:02 -08:00
Grace Guo
744135c7fe [fix] Add action for update chart id (#6769) 2019-01-28 17:42:35 -08:00
ziheng
83ee917832 [fix] pandas>=0.24.0 datetimelike API changes (#6765) 2019-01-27 17:29:56 -08:00
Maxime Beauchemin
a62a8d3d98 [docs] bump cryptography lib version in docs (#6755) 2019-01-25 14:26:08 -08:00
Maxime Beauchemin
a93219f291 [cosmetic] Align table sort icons to the left (#6754) 2019-01-25 14:25:41 -08:00
michellethomas
8100a8fa97 Fixing sort issue with area chart and adding tests (#6358) 2019-01-25 10:30:31 -08:00
Hugh A. Miles II
6b0ab2100d fix dashboard links in welcome page (#6756) 2019-01-25 08:20:00 -08:00
davidkohcw
f38cea3ee3 Update User List in README.md (#6758)
Added a new company into the company list
2019-01-25 08:19:19 -08:00
Marcus
01689c38ea bump PyHive version for prod requirements.txt (#6751) 2019-01-24 23:11:22 -08:00
Grace Guo
879c553b0a [fix] JS error out when rename a new chart (#6752) 2019-01-24 21:31:21 -08:00
michellethomas
97cb10dbc8 Make it easier to select dropdown options in control tests (#6546) 2019-01-24 10:10:22 -08:00
agrawaldevesh
594cd70960 A few fixes for Presto connection (#6720)
* Preserve existing configuration when modifying it in case of user impersonation

* Add logging when a table cannot be added due to some schema issues
2019-01-23 21:12:27 -08:00
Maxime Beauchemin
1ffee8b236 [cosmetic] remove 'List' prefix from list headers (#6725)
* [cosmetic] remove 'List' prefix from list headers

* Same change to connectors
2019-01-23 21:06:56 -08:00
Krist Wongsuphasawat
3ae7d32caa Add iframe and markup legacy plugin (#6741)
* Add iframe plugin

* Use lazy load and add description

* remove unintended files

* Add markup

* minor adjustment
2019-01-23 10:21:57 -08:00
Krist Wongsuphasawat
954e42bafe Update gitignore (#6742)
internal: update gitignore
2019-01-23 10:21:41 -08:00
michellethomas
bab7ee7ecf Adding a note about 0.30 to updating (#6730) 2019-01-22 14:38:01 -08:00
Beto Dealmeida
bbd781b66e Remove test URL (#6740) 2019-01-22 14:27:51 -08:00
Beto Dealmeida
cf1a35b94b Allow specifying custom width for logo (#6739) 2019-01-22 14:25:49 -08:00
bolkedebruin
d65059b06a Add disclaimer and remove counter (#6738) 2019-01-22 14:18:16 -08:00
129 changed files with 6757 additions and 4794 deletions

1
.gitignore vendored
View File

@@ -33,6 +33,7 @@ _modules
_static
build
app.db
apache_superset.egg-info/
changelog.sh
dist
dump.rdb

View File

@@ -35,7 +35,7 @@ little bit helps, and credit will always be given.
- [Pull Request Guidelines](#pull-request-guidelines)
- [Protocol](#protocol)
- [Managing Issues and PRs](#managing-issues-and-prs)
- [Local development](#local-development)
- [Setup Local Environment for Development](#setup-local-environment-for-development)
- [Documentation](#documentation)
- [Flask server](#flask-server)
- [Frontend assets](#frontend-assets)
@@ -410,6 +410,10 @@ export enum FeatureFlag {
}
```
`superset/config.py` contains `DEFAULT_FEATURE_FLAGS` which will be overwritten by
those specified under FEATURE_FLAGS in `superset_config.py`. For example, `DEFAULT_FEATURE_FLAGS = { 'FOO': True, 'BAR': False }` in `superset/config.py` and `FEATURE_FLAGS = { 'BAR': True, 'BAZ': True }` in `superset_config.py` will result
in combined feature flags of `{ 'FOO': True, 'BAR': True, 'BAZ': True }`.
## Linting
Lint the project with:

View File

@@ -231,3 +231,5 @@ BSD 3-Clause licenses
========================================================================
Creative Commons Attribution 4.0
========================================================================
(Creative Commons Attribution 4.0) diva-gis (http://www.diva-gis.org/Data)

View File

@@ -195,6 +195,7 @@ the world know they are using Superset. Join our growing community!
1. [Lyft](https://www.lyft.com/)
1. [Maieutical Labs](https://maieuticallabs.it)
1. [Myra Labs](http://www.myralabs.com/)
1. [Now](https://www.now.vn/)
1. [PeopleDoc](https://www.people-doc.com)
1. [Ona](https://ona.io)
1. [Pronto Tools](http://www.prontotools.io)

View File

@@ -46,80 +46,66 @@ git commit -a -m "New doc version"
git push origin master
```
# Apache Releases
## Publishing a PyPI release
You'll probably want to run these commands manually and understand what
they do prior to doing so.
We create a branch that goes along each minor release `0.24`
and micro releases get corresponding tags as in `0.24.0`. Git history should
never be altered in release branches.
Bug fixes and security-related patches get cherry-picked
(usually from master) as in `git cherry-pick -x {SHA}`.
First you need to setup a few things. This is a one-off and doesn't
need to be done at every release.
Following a set of cherries being picked, a release can be pushed to
PyPI as follows:
```bash
# Create PGP Key
gpg --gen-key
# Checkout ASF dist repo
# branching off of master
git checkout -b 0.25
svn checkout https://dist.apache.org/repos/dist/dev/incubator/superset/ ~/svn/superset_dev
# cherry-picking a SHA
git cherry-pick -x f9d85bd2e1fd9bc233d19c76bed09467522b968a
# repeat with other SHAs, don't forget the -x
svn checkout https://dist.apache.org/repos/dist/incubator/superset/ ~/svn/superset
cd ~/svn/superset
# Add your GPG pub key to KEYS file. Replace "Maxime Beauchemin" with your name
export FULLNAME="Maxime Beauchemin"
(gpg --list-sigs $FULLNAME && gpg --armor --export $FULLNAME ) >> KEYS
# Commit the changes
svn commit -m "Add PGP keys of new Superset committer"
# source of thruth for release numbers live in package.json
vi superset/assets/package.json
# hard code release in file, commit to the release branch
git commit -a -m "0.25.0"
# create the release tag in the release branch
git tag 0.25.0
git push apache 0.25 --tags
# check travis to confirm the build succeeded as
# you shouldn't assume that a clean cherry will be clean
# when landing on a new sundae
# compile the JS, and push to pypi
# to run this part you'll need a pypi account and rights on the
# superset package. Committers that want to ship releases
# should have this access.
# You'll also need a `.pypirc` as specified here:
# http://peterdowns.com/posts/first-time-with-pypi.html
./pypi_push.sh
# publish an update to the CHANGELOG.md for the right version range
# looking the latest CHANGELOG entry for the second argument
./gen_changelog.sh 0.22.1 0.25.0
# this will overwrite the CHANGELOG.md with only the version range
# so you'll want to copy paste that on top of the previous CHANGELOG.md
# open a PR against `master`
```
Now let's craft a source release
```bash
# Assuming these commands are executed from the root of the repo
# Setting a VERSION var will be useful
export VERSION=0.31.0rc18
In the future we'll start publishing release candidates for minor releases
only, but typically not for micro release.
The process will be similar to the process described above, expect the
tags will be formatted `0.25.0rc1`, `0.25.0rc2`, ..., until consensus
is reached.
# Let's create a git tag
git tag -f ${VERSION}
We should also have a Github PR label process to target the proper
release, and tooling helping keeping track of all the cherries and
target versions.
# [WARNING!] This command wipes everything in your repo that is
# gitignored in preparation for the source release.
# You may want to check that there's nothing your care about here first.
# Alternatively you could clone the repo into another location as in
# git clone git@github.com:apache/incubator-superset.git superset-releases
git clean -fxd
# Create the target folder
mkdir -p ~/svn/superset_dev/${VERSION}/
git archive \
--format=tar.gz ${VERSION} \
--prefix=apache-superset-${VERSION}/ \
-o ~/svn/superset_dev/${VERSION}/apache-superset-${VERSION}-source.tar.gz
cd ~/svn/superset_dev/
scripts/sign.sh apache-superset-${VERSION}-source.tar.gz
```
Now let's ship this RC into svn's dev folder
```bash
# cp or mv the files over to the svn repo
cd ~/svn/superset_dev/
svn add ${VERSION}
svn commit
```
Now you're ready to start the VOTE thread.
Upon a successful vote, you'll have to copy the folder into the non-"dev/"
folder.
```bash
cp -r ~/svn/superset_dev/${VERSION}/ ~/svn/superset/${VERSION}/
cd ~/svn/superset/
svn add ${VERSION}
svn commit
```
Now you can announce the release on the mailing list, make sure to use the
proper template
For Apache releases, the process will be a bit heavier and should get
documented here. There will be extra steps for signing the binaries,
with a PGP key and providing MD5, Apache voting, as well as
publishing to Apache's SVN repository. View the ASF docs for more
information.

View File

@@ -27,11 +27,6 @@ assists people when migrating to a new version.
run `pip install superset[presto]` and/or `pip install superset[hive]` as
required.
* [5445](https://github.com/apache/incubator-superset/pull/5445) : a change
which prevents encoding of empty string from form data in the datanbase.
This involves a non-schema changing migration which does potentially impact
a large number of records. Scheduled downtime may be advised.
## Superset 0.31.0
* boto3 / botocore was removed from the dependency list. If you use s3
as a place to store your SQL Lab result set or Hive uploads, you may
@@ -42,6 +37,12 @@ favor of good old `npm install`. While yarn should still work just fine,
you should probably align to guarantee builds similar to the ones we
use in testing and across the community in general.
## Superset 0.30.0
* 0.30.0 includes a db_migration that removes allow_run_sync. This may
require downtime because during the migration if the db is migrated first,
superset will get 500 errors when the code can't find the field (until
the deploy finishes).
## Superset 0.29.0
* India was removed from the "Country Map" visualization as the geojson
file included in the package was very large

View File

@@ -1,242 +0,0 @@
Metadata-Version: 2.1
Name: apache-superset
Version: 0.999.0.dev0
Summary: A modern, enterprise-ready business intelligence web application
Home-page: https://superset.apache.org/
Author: Apache Software Foundation
Author-email: dev@superset.incubator.apache.org
License: Apache License, Version 2.0
Download-URL: https://dist.apache.org/repos/dist/release/superset/0.999.0dev
Description: <!--
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
=========
[![Build Status](https://travis-ci.org/apache/incubator-superset.svg?branch=master)](https://travis-ci.org/apache/incubator-superset)
[![PyPI version](https://badge.fury.io/py/superset.svg)](https://badge.fury.io/py/superset)
[![Coverage Status](https://codecov.io/github/apache/incubator-superset/coverage.svg?branch=master)](https://codecov.io/github/apache/incubator-superset)
[![PyPI](https://img.shields.io/pypi/pyversions/superset.svg?maxAge=2592000)](https://pypi.python.org/pypi/superset)
[![Join the chat at https://gitter.im/airbnb/superset](https://badges.gitter.im/apache/incubator-superset.svg)](https://gitter.im/airbnb/superset?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation](https://img.shields.io/badge/docs-apache.org-blue.svg)](https://superset.incubator.apache.org)
[![dependencies Status](https://david-dm.org/apache/incubator-superset/status.svg?path=superset/assets)](https://david-dm.org/apache/incubator-superset?path=superset/assets)
<img
src="https://cloud.githubusercontent.com/assets/130878/20946612/49a8a25c-bbc0-11e6-8314-10bef902af51.png"
alt="Superset"
width="500"
/>
**Apache Superset** (incubating) is a modern, enterprise-ready
business intelligence web application
[this project used to be named **Caravel**, and **Panoramix** in the past]
Screenshots & Gifs
------------------
**View Dashboards**
<kbd><img title="View Dashboards" src="https://raw.githubusercontent.com/apache/incubator-superset/master/superset/assets/images/screenshots/bank_dash.png"></kbd><br/>
**Slice & dice your data**
<kbd><img title="Slice & dice your data" src="https://raw.githubusercontent.com/apache/incubator-superset/master/superset/assets/images/screenshots/explore.png"></kbd><br/>
**Query and visualize your data with SQL Lab**
<kbd><img title="SQL Lab" src="https://raw.githubusercontent.com/apache/incubator-superset/master/superset/assets/images/screenshots/sqllab.png"></kbd><br/>
**Visualize geospatial data with deck.gl**
<kbd><img title="Geospatial" src="https://raw.githubusercontent.com/apache/incubator-superset/master/superset/assets/images/screenshots/deckgl_dash.png"></kbd><br/>
**Choose from a wide array of visualizations**
<kbd><img title="Visualizations" src="https://raw.githubusercontent.com/apache/incubator-superset/master/superset/assets/images/screenshots/visualizations.png"></kbd><br/>
Apache Superset
---------------
Apache Superset is a data exploration and visualization web application.
Superset provides:
* An intuitive interface to explore and visualize datasets, and
create interactive dashboards.
* A wide array of beautiful visualizations to showcase your data.
* Easy, code-free, user flows to drill down and slice and dice the data
underlying exposed dashboards. The dashboards and charts acts as a starting
point for deeper analysis.
* A state of the art SQL editor/IDE exposing a rich metadata browser, and
an easy workflow to create visualizations out of any result set.
* An extensible, high granularity security model allowing intricate rules
on who can access which product features and datasets.
Integration with major
authentication backends (database, OpenID, LDAP, OAuth, REMOTE_USER, ...)
* A lightweight semantic layer, allowing to control how data sources are
exposed to the user by defining dimensions and metrics
* Out of the box support for most SQL-speaking databases
* Deep integration with Druid allows for Superset to stay blazing fast while
slicing and dicing large, realtime datasets
* Fast loading dashboards with configurable caching
Database Support
----------------
Superset speaks many SQL dialects through SQLAlchemy, a Python
ORM that is compatible with
[most common databases](https://docs.sqlalchemy.org/en/rel_1_2/core/engines.html).
Superset can be used to visualize data out of most databases:
* MySQL
* Postgres
* Vertica
* Oracle
* Microsoft SQL Server
* SQLite
* Greenplum
* Firebird
* MariaDB
* Sybase
* IBM DB2
* Exasol
* MonetDB
* Snowflake
* Redshift
* Clickhouse
* Apache Kylin
* Google BigQuery
* **more!** look for the availability of a SQLAlchemy dialect for your database
to find out whether it will work with Superset
Apache Druid (Incubating)!
------
On top of having the ability to query your relational databases,
Superset ships with deep integration with Druid (a real time distributed
column-store). When querying Druid,
Superset can query humongous amounts of data on top of real time dataset.
Note that Superset does not require Druid in any way to function, it's simply
another database backend that it can query.
Here's a description of Druid from the http://druid.io website:
*Druid is an open-source analytics data store designed for
business intelligence (OLAP) queries on event data. Druid provides low
latency (real-time) data ingestion, flexible data exploration,
and fast data aggregation. Existing Druid deployments have scaled to
trillions of events and petabytes of data. Druid is best used to
power analytic dashboards and applications.*
Installation & Configuration
----------------------------
[See in the documentation](https://superset.incubator.apache.org/installation.html)
Resources
-------------
* [Mailing list](https://lists.apache.org/list.html?dev@superset.apache.org)
* [Gitter (live chat) Channel](https://gitter.im/airbnb/superset)
* [Docker image](https://hub.docker.com/r/amancevice/superset/) (community contributed)
* [Slides from Strata (March 2016)](https://drive.google.com/open?id=0B5PVE0gzO81oOVJkdF9aNkJMSmM)
* [Stackoverflow tag](https://stackoverflow.com/questions/tagged/apache-superset)
* [Join our Slack](https://join.slack.com/t/apache-superset/shared_invite/enQtNDMxMDY5NjM4MDU0LTc2Y2QwYjE4NGYwNzQyZWUwYTExZTdiZDMzMWQwZjc2YmJmM2QyMDkwMGVjZTA4N2I2MzUxZTk2YmE5MWRhZWE)
* [DEPRECATED Google Group](https://groups.google.com/forum/#!forum/airbnb_superset)
Contributing
------------
Interested in contributing? Casual hacking? Check out
[Contributing.MD](https://github.com/airbnb/superset/blob/master/CONTRIBUTING.md)
Who uses Apache Superset (incubating)?
--------------------------------------
Here's a list of organizations who have taken the time to send a PR to let
the world know they are using Superset. Join our growing community!
1. [AiHello](https://www.aihello.com)
1. [Airbnb](https://github.com/airbnb)
1. [Airboxlab](https://foobot.io)
1. [Aktia Bank plc](https://www.aktia.com)
1. [Amino](https://amino.com)
1. [Apollo GraphQL](https://www.apollographql.com/)
1. [Ascendica Development](http://ascendicadevelopment.com)
1. [Astronomer](https://www.astronomer.io)
1. [Brilliant.org](https://brilliant.org/)
1. [Capital Service S.A.](http://capitalservice.pl)
1. [Clark.de](http://clark.de/)
1. [Cloudsmith](https://cloudsmith.io)
1. [CnOvit](http://www.cnovit.com/)
1. [Dial Once](https://www.dial-once.com/en/)
1. [Digit Game Studios](https://www.digitgaming.com/)
1. [Douban](https://www.douban.com/)
1. [Endress+Hauser](http://www.endress.com/)
1. [FBK - ICT center](http://ict.fbk.eu)
1. [Faasos](http://faasos.com/)
1. [Fordeal](http://www.fordeal.com)
1. [GfK Data Lab](http://datalab.gfk.com)
1. [Grassroot](https://www.grassrootinstitute.org/)
1. [Hostnfly](https://www.hostnfly.com/)
1. [HuiShouBao](http://www.huishoubao.com/)
1. [jampp](https://jampp.com/)
1. [Konfío](http://konfio.mx)
1. [Kuaishou](https://www.kuaishou.com/)
1. [Lime](https://www.limebike.com/)
1. [Living Goods](https://www.livinggoods.org)
1. [Lyft](https://www.lyft.com/)
1. [Maieutical Labs](https://maieuticallabs.it)
1. [Myra Labs](http://www.myralabs.com/)
1. [Now](https://www.now.vn/)
1. [PeopleDoc](https://www.people-doc.com)
1. [Ona](https://ona.io)
1. [Pronto Tools](http://www.prontotools.io)
1. [QPID Health](http://www.qpidhealth.com/ )
1. [Qunar](https://www.qunar.com/)
1. [Scoot](https://scoot.co/)
1. [ScopeAI](https://www.getscopeai.com)
1. [Shopee](https://shopee.sg)
1. [Shopkick](https://www.shopkick.com)
1. [Steamroot](https://streamroot.io/)
1. [Showmax](https://tech.showmax.com)
1. [Tails.com](https://tails.com)
1. [THEICONIC](http://theiconic.com.au/)
1. [Tobii](http://www.tobii.com/)
1. [Tooploox](https://www.tooploox.com/)
1. [TrustMedis](https://trustmedis.com)
1. [Twitter](https://twitter.com/)
1. [Udemy](https://www.udemy.com/)
1. [VIPKID](https://www.vipkid.com.cn/)
1. [Windsor.ai](https://www.windsor.ai/)
1. [Yahoo!](https://yahoo.com/)
1. [Zaihang](http://www.zaih.com/)
1. [Zalando](https://www.zalando.com)
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 3.6
Description-Content-Type: text/markdown
Provides-Extra: presto
Provides-Extra: hive
Provides-Extra: cors
Provides-Extra: gsheets

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@

View File

@@ -1,50 +0,0 @@
bleach<4.0.0,>=3.0.2
celery<5.0.0,>=4.2.0
click<7.0.0,>=6.0
colorama
contextlib2
croniter>=0.3.28
cryptography>=2.4.2
flask<2.0.0,>=1.0.0
flask-appbuilder<2.3.0,>=2.0.0
flask-caching
flask-compress
flask-talisman
flask-migrate
flask-wtf
geopy
gunicorn
humanize
idna
isodate
markdown>=3.0
pandas<0.24.0,>=0.18.0
parsedatetime
pathlib2
polyline
pydruid>=0.5.2
python-dateutil
python-geohash
pyyaml>=3.13
requests>=2.20.0
retry>=0.9.2
selenium>=3.141.0
simplejson>=3.15.0
sqlalchemy<2.0,>=1.3.1
sqlalchemy-utils
sqlparse
unicodecsv
wtforms-json
[cors]
flask-cors>=2.0.0
[gsheets]
gsheetsdb>=0.1.9
[hive]
pyhive[hive]>=0.6.1
tableschema
[presto]
pyhive[presto]>=0.4.0

View File

@@ -1,2 +0,0 @@
superset
tests

View File

@@ -117,7 +117,7 @@ that the required dependencies are installed: ::
**OSX**, system python is not recommended. brew's python also ships with pip ::
brew install pkg-config libffi openssl python
env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography==1.9
env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography==2.4.2
**Windows** isn't officially supported at this point, but if you want to
attempt it, download `get-pip.py <https://bootstrap.pypa.io/get-pip.py>`_, and run ``python get-pip.py`` which may need admin access. Then run the following: ::
@@ -405,6 +405,11 @@ You can also use `PyAthena` library(no java required) like this ::
See `PyAthena <https://github.com/laughingman7743/PyAthena#sqlalchemy>`_.
MSSQL
-----
Full Unicode support requires SQLAlchemy 1.3 or later.
Snowflake
---------
@@ -616,6 +621,18 @@ Upgrading should be as straightforward as running::
superset db upgrade
superset init
We recommend to follow standard best practices when upgrading Superset, such
as taking a database backup prior to the upgrade, upgrading a staging
environment prior to upgrading production, and upgrading production while less
users are active on the platform.
.. note ::
Some upgrades may contain backward-incompatible changes, or require
scheduling downtime, when that is the case, contributors attach notes in
``UPDATING.md`` in the repository. It's recommended to review this
file prior to running an upgrade.
Celery Tasks
------------
On large analytic databases, it's common to run background jobs, reports

View File

@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
{{- if .Values.ingress.enabled -}}
{{ if .Values.ingress.enabled -}}
{{- $fullName := include "superset.fullname" . -}}
{{- $ingressPath := .Values.ingress.path -}}
apiVersion: extensions/v1beta1

View File

@@ -17,7 +17,7 @@ chardet==3.0.4 # via requests
click==6.7
colorama==0.3.9
contextlib2==0.5.5
croniter==0.3.29
croniter==0.3.26
cryptography==2.4.2
decorator==4.3.0 # via retry
defusedxml==0.5.0 # via python3-openid
@@ -42,8 +42,8 @@ kombu==4.2.1 # via celery
mako==1.0.7 # via alembic
markdown==3.0
markupsafe==1.0 # via jinja2, mako
numpy==1.15.2 # via pandas
pandas==0.23.4
numpy==1.15.2 # via numpy
pandas==0.23.4 # via pandas
parsedatetime==2.0.0
pathlib2==2.3.0
polyline==1.3.2
@@ -60,7 +60,7 @@ requests==2.20.0
retry==0.9.2
selenium==3.141.0
simplejson==3.15.0
six==1.11.0 # via bleach, cryptography, isodate, pathlib2, polyline, pydruid, python-dateutil, sqlalchemy-utils, wtforms-json
six==1.11.0 # via bleach, cryptography, isodate, pathlib2, polyline, pydruid, python-dateutil, sqlalchemy-utils
sqlalchemy-utils==0.32.21
sqlalchemy==1.2.2
sqlparse==0.2.4
@@ -69,5 +69,4 @@ urllib3==1.22 # via requests, selenium
vine==1.1.4 # via amqp
webencodings==0.5.1 # via bleach
werkzeug==0.14.1 # via flask
wtforms-json==0.3.3
wtforms==2.2.1 # via flask-wtf, wtforms-json
wtforms==2.2.1 # via flask-wtf

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env bash
#
# 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.
#
# Use this to sign the tar balls generated from
# python setup.py sdist --formats=gztar
# ie. sign.sh <my_tar_ball>
# you will still be required to type in your signing key password
# or it needs to be available in your keychain
NAME=${1}
gpg --armor --output ${NAME}.asc --detach-sig ${NAME}
gpg --print-md SHA512 ${NAME} > ${NAME}.sha512

View File

@@ -71,10 +71,10 @@ setup(
install_requires=[
'bleach>=3.0.2, <4.0.0',
'celery>=4.2.0, <5.0.0',
'click>=6.0, <7.0.0', # click >=7 forces "-" instead of "_"
'click>=6.0, <7.0.0', # `click`>=7 forces "-" instead of "_"
'colorama',
'contextlib2',
'croniter>=0.3.28',
'croniter>=0.3.26',
'cryptography>=2.4.2',
'flask>=1.0.0, <2.0.0',
'flask-appbuilder>=1.12.1, <2.0.0',
@@ -88,7 +88,7 @@ setup(
'idna',
'isodate',
'markdown>=3.0',
'pandas>=0.18.0',
'pandas>=0.18.0, <0.24.0', # `pandas`>=0.24.0 changes datetimelike API
'parsedatetime',
'pathlib2',
'polyline',
@@ -104,13 +104,12 @@ setup(
'sqlalchemy-utils',
'sqlparse',
'unicodecsv',
'wtforms-json',
],
extras_require={
'cors': ['flask-cors>=2.0.0'],
'console_log': ['console_log==0.2.10'],
'hive': [
'pyhive>=0.4.0',
'pyhive>=0.6.1',
'tableschema',
'thrift-sasl>=0.2.1',
'thrift>=0.9.3',

View File

@@ -28,7 +28,6 @@ from flask_compress import Compress
from flask_migrate import Migrate
from flask_wtf.csrf import CSRFProtect
from werkzeug.contrib.fixers import ProxyFix
import wtforms_json
from superset import config
from superset.connectors.connector_registry import ConnectorRegistry
@@ -36,15 +35,13 @@ from superset.security import SupersetSecurityManager
from superset.utils.core import (
get_update_perms_flag, pessimistic_connection_handling, setup_cache)
wtforms_json.init()
APP_DIR = os.path.dirname(__file__)
CONFIG_MODULE = os.environ.get('SUPERSET_CONFIG', 'superset.config')
if not os.path.exists(config.DATA_DIR):
os.makedirs(config.DATA_DIR)
with open(APP_DIR + '/static/assets/backendSync.json', 'r', encoding='utf-8') as f:
with open(APP_DIR + '/static/assets/backendSync.json', 'r') as f:
frontend_config = json.load(f)
app = Flask(__name__)
@@ -211,6 +208,18 @@ security_manager = appbuilder.sm
results_backend = app.config.get('RESULTS_BACKEND')
# Merge user defined feature flags with default feature flags
feature_flags = app.config.get('DEFAULT_FEATURE_FLAGS')
feature_flags.update(app.config.get('FEATURE_FLAGS') or {})
def is_feature_enabled(feature):
"""
Utility function for checking whether a feature is turned on
"""
return feature_flags.get(feature)
# Registering sources
module_datasource_map = app.config.get('DEFAULT_MODULE_DS_MAP')
module_datasource_map.update(app.config.get('ADDITIONAL_MODULE_DS_MAP'))

View File

@@ -89,8 +89,8 @@ describe('AdhocMetrics', () => {
cy.get('[data-test=metrics]').within(() => {
cy.get('.select-clear').click();
cy.get('.Select-control').click({ force: true });
cy.get('input').type('num{downarrow}', { force: true });
cy.get('.VirtualizedSelectFocusedOption')
cy.get('input').type('num', { force: true });
cy.get('.VirtualizedSelectOption[data-test=_col_num]')
.trigger('mousedown')
.click();
});

View File

@@ -29,7 +29,6 @@ flask run -p 8081 --with-threads --reload --debugger &
#block on the longer running javascript process
time npm ci
time npm run install-cypress
time npm run build
echo "[completed js build steps]"

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "superset",
"version": "0.32.0rc2.dev2",
"version": "0.999.0dev",
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
"license": "Apache-2.0",
"directories": {
@@ -19,8 +19,7 @@
"lint-fix": "eslint --fix --ignore-path=.eslintignore --ext .js,.jsx . && tslint -c tslint.json --fix ./{src,spec}/**/*.ts{,x}",
"sync-backend": "babel-node --preset=@babel/preset-env src/syncBackend.js",
"cypress": "cypress",
"cypress-debug": "cypress open --config watchForFileChanges=true",
"install-cypress": "npm install cypress@3.1.5"
"cypress-debug": "cypress open --config watchForFileChanges=true"
},
"repository": {
"type": "git",
@@ -63,7 +62,6 @@
"@vx/responsive": "0.0.172",
"@vx/scale": "^0.0.165",
"abortcontroller-polyfill": "^1.1.9",
"bignumber.js": "^8.1.1",
"bootstrap": "^3.3.6",
"bootstrap-slider": "^10.0.0",
"brace": "^0.11.1",
@@ -117,10 +115,9 @@
"react-select": "1.2.1",
"react-select-fast-filter-options": "^0.2.1",
"react-sortable-hoc": "^0.8.3",
"react-split": "^2.0.4",
"react-split-pane": "^0.1.66",
"react-sticky": "^6.0.2",
"react-syntax-highlighter": "^7.0.4",
"react-transition-group": "^2.5.3",
"react-virtualized": "9.19.1",
"react-virtualized-select": "^3.1.3",
"reactable-arc": "0.14.42",
@@ -159,6 +156,7 @@
"cache-loader": "^1.2.2",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^1.0.0",
"cypress": "^3.0.3",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "^4.19.1",
@@ -194,7 +192,7 @@
"terser-webpack-plugin": "^1.1.0",
"thread-loader": "^1.2.0",
"transform-loader": "^0.2.3",
"ts-jest": "^24.0.0",
"ts-jest": "^23.10.4",
"ts-loader": "^5.2.0",
"tslint": "^5.11.0",
"tslint-react": "^3.6.0",

View File

@@ -1,53 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import BigNumber from 'bignumber.js';
import transform from 'src/chart/transformBigNumber';
describe('transformBigNumber', () => {
it('should transform BigNumber on its own', () => {
expect(transform(new BigNumber(123.456))).toBe(123.456);
});
it('should transform BigNumber in objects', () => {
expect(transform({
foo: new BigNumber(123),
bar: 456,
baz: null,
})).toEqual({ foo: 123, bar: 456, baz: null });
});
it('should transform BigNumber in arrays', () => {
expect(transform([
{ foo: new BigNumber(123) },
{ bar: 456 },
])).toEqual([{ foo: 123 }, { bar: 456 }]);
});
it('should transform BigNumber in nested structures', () => {
expect(transform([{
x: new BigNumber(123),
y: [{ foo: new BigNumber(456) }, { bar: 'str' }],
z: { some: [new BigNumber(789)] },
}])).toEqual([{
x: 123,
y: [{ foo: 456 }, { bar: 'str' }],
z: { some: [789] },
}]);
});
});

View File

@@ -24,7 +24,7 @@ import { shallow } from 'enzyme';
import { STATUS_OPTIONS } from '../../../src/SqlLab/constants';
import { initialState } from './fixtures';
import SouthPaneContainer, { SouthPane } from '../../../src/SqlLab/components/SouthPane';
import SouthPane from '../../../src/SqlLab/components/SouthPane';
describe('SouthPane', () => {
const middlewares = [thunk];
@@ -42,16 +42,11 @@ describe('SouthPane', () => {
};
const getWrapper = () => (
shallow(<SouthPaneContainer {...mockedProps} />, {
shallow(<SouthPane {...mockedProps} />, {
context: { store },
}).dive());
let wrapper;
beforeAll(() => {
jest.spyOn(SouthPane.prototype, 'getSouthPaneHeight').mockImplementation(() => 500);
});
it('should render offline when the state is offline', () => {
wrapper = getWrapper();
wrapper.setProps({ offline: true });

View File

@@ -38,11 +38,6 @@ describe('SqlEditor', () => {
defaultQueryLimit: 1000,
maxRow: 100000,
};
beforeAll(() => {
jest.spyOn(SqlEditor.prototype, 'getSqlEditorHeight').mockImplementation(() => 500);
});
it('is valid', () => {
expect(
React.isValidElement(<SqlEditor {...mockedProps} />),

View File

@@ -30,7 +30,7 @@ describe('getBreakPoints', () => {
});
it('returns sorted break points', () => {
const fd = { breakPoints: ['0', '10', '100', '50', '1000'] };
const fd = { break_points: ['0', '10', '100', '50', '1000'] };
const result = getBreakPoints(fd, [], metricAccessor);
const expected = ['0', '10', '50', '100', '1000'];
expect(result).toEqual(expected);
@@ -45,7 +45,7 @@ describe('getBreakPoints', () => {
});
it('formats number with proper precision', () => {
const fd = { metric: 'count', numBuckets: 2 };
const fd = { metric: 'count', num_buckets: 2 };
const features = [0, 1 / 3, 2 / 3, 1].map(count => ({ count }));
const result = getBreakPoints(fd, features, metricAccessor);
const expected = ['0.0', '0.5', '1.0'];
@@ -53,7 +53,7 @@ describe('getBreakPoints', () => {
});
it('works with a zero range', () => {
const fd = { metric: 'count', numBuckets: 1 };
const fd = { metric: 'count', num_buckets: 1 };
const features = [1, 1, 1].map(count => ({ count }));
const result = getBreakPoints(fd, features, metricAccessor);
const expected = ['1', '1'];
@@ -69,7 +69,7 @@ describe('getBreakPointColorScaler', () => {
it('returns linear color scaler if there are no break points', () => {
const fd = {
metric: 'count',
linearColorScheme: ['#000000', '#ffffff'],
linear_color_scheme: ['#000000', '#ffffff'],
opacity: 100,
};
const features = [10, 20, 30].map(count => ({ count }));
@@ -82,8 +82,8 @@ describe('getBreakPointColorScaler', () => {
it('returns bucketing scaler if there are break points', () => {
const fd = {
metric: 'count',
linearColorScheme: ['#000000', '#ffffff'],
breakPoints: ['0', '1', '10'],
linear_color_scheme: ['#000000', '#ffffff'],
break_points: ['0', '1', '10'],
opacity: 100,
};
const features = [];
@@ -97,8 +97,8 @@ describe('getBreakPointColorScaler', () => {
it('mask values outside the break points', () => {
const fd = {
metric: 'count',
linearColorScheme: ['#000000', '#ffffff'],
breakPoints: ['0', '1', '10'],
linear_color_scheme: ['#000000', '#ffffff'],
break_points: ['0', '1', '10'],
opacity: 100,
};
const features = [];
@@ -116,8 +116,8 @@ describe('getBuckets', () => {
it('computes buckets for break points', () => {
const fd = {
metric: 'count',
linearColorScheme: ['#000000', '#ffffff'],
breakPoints: ['0', '1', '10'],
linear_color_scheme: ['#000000', '#ffffff'],
break_points: ['0', '1', '10'],
opacity: 100,
};
const features = [];

View File

@@ -84,7 +84,7 @@ class App extends React.PureComponent {
content = (
<div>
<QueryAutoRefresh />
<TabbedSqlEditors />
<TabbedSqlEditors getHeight={this.getHeight} />
</div>
);
}

View File

@@ -184,21 +184,19 @@ class ExploreResultsButton extends React.PureComponent {
render() {
const allowsSubquery = this.props.database && this.props.database.allows_subquery;
return (
<React.Fragment>
<Button
bsSize="small"
onClick={this.onClick}
disabled={!allowsSubquery}
tooltip={t('Explore the result set in the data exploration view')}
>
<InfoTooltipWithTrigger icon="line-chart" placement="top" label="explore" /> {t('Explore')}
</Button>
<Button
bsSize="small"
onClick={this.onClick}
disabled={!allowsSubquery}
tooltip={t('Explore the result set in the data exploration view')}
>
<Dialog
ref={(el) => {
this.dialog = el;
}}
/>
</React.Fragment>
<InfoTooltipWithTrigger icon="line-chart" placement="top" label="explore" /> {t('Explore')}
</Button>
);
}
}

View File

@@ -31,13 +31,11 @@ const propTypes = {
dbId: PropTypes.number,
animation: PropTypes.bool,
onSave: PropTypes.func,
saveQueryWarning: PropTypes.string,
};
const defaultProps = {
defaultLabel: t('Undefined'),
animation: true,
onSave: () => {},
saveQueryWarning: null,
};
class SaveQuery extends React.PureComponent {
@@ -110,18 +108,6 @@ class SaveQuery extends React.PureComponent {
</Col>
</Row>
<br />
{this.props.saveQueryWarning && (
<div>
<Row>
<Col md={12}>
<small>
{this.props.saveQueryWarning}
</small>
</Col>
</Row>
<br />
</div>
)}
<Row>
<Col md={12}>
<Button

View File

@@ -48,24 +48,7 @@ const defaultProps = {
offline: false,
};
export class SouthPane extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
height: props.height,
};
this.southPaneRef = React.createRef();
this.getSouthPaneHeight = this.getSouthPaneHeight.bind(this);
this.switchTab = this.switchTab.bind(this);
}
componentWillReceiveProps() {
// south pane expands the entire height of the tab content on mount
this.setState({ height: this.getSouthPaneHeight() });
}
// One layer of abstraction for easy spying in unit tests
getSouthPaneHeight() {
return this.southPaneRef.current ? this.southPaneRef.current.clientHeight : 0;
}
class SouthPane extends React.PureComponent {
switchTab(id) {
this.props.actions.setActiveSouthPaneTab(id);
}
@@ -76,7 +59,7 @@ export class SouthPane extends React.PureComponent {
{ STATUS_OPTIONS.offline }
</Label>);
}
const innerTabHeight = this.state.height - 55;
const innerTabHeight = this.props.height - 55;
let latestQuery;
const props = this.props;
if (props.editorQueries.length > 0) {
@@ -115,12 +98,12 @@ export class SouthPane extends React.PureComponent {
));
return (
<div className="SouthPane" ref={this.southPaneRef}>
<div className="SouthPane">
<Tabs
bsStyle="tabs"
id={shortid.generate()}
activeKey={this.props.activeSouthPaneTab}
onSelect={this.switchTab}
onSelect={this.switchTab.bind(this)}
>
<Tab
title={t('Results')}

View File

@@ -17,18 +17,21 @@
* under the License.
*/
import React from 'react';
import { CSSTransition } from 'react-transition-group';
import PropTypes from 'prop-types';
import { throttle } from 'lodash';
import {
Col,
FormGroup,
InputGroup,
Form,
FormControl,
Label,
OverlayTrigger,
Row,
Tooltip,
Collapse,
} from 'react-bootstrap';
import Split from 'react-split';
import SplitPane from 'react-split-pane';
import { t } from '@superset-ui/translation';
import Button from '../../components/Button';
@@ -44,13 +47,9 @@ import AceEditorWrapper from './AceEditorWrapper';
import { STATE_BSSTYLE_MAP } from '../constants';
import RunQueryActionButton from './RunQueryActionButton';
const SQL_TOOLBAR_HEIGHT = 51;
const GUTTER_HEIGHT = 5;
const INITIAL_NORTH_PERCENT = 30;
const INITIAL_SOUTH_PERCENT = 70;
const propTypes = {
actions: PropTypes.object.isRequired,
getHeight: PropTypes.func.isRequired,
database: PropTypes.object,
latestQuery: PropTypes.object,
tables: PropTypes.array.isRequired,
@@ -60,14 +59,12 @@ const propTypes = {
hideLeftBar: PropTypes.bool,
defaultQueryLimit: PropTypes.number.isRequired,
maxRow: PropTypes.number.isRequired,
saveQueryWarning: PropTypes.string,
};
const defaultProps = {
database: null,
latestQuery: null,
hideLeftBar: false,
saveQueryWarning: null,
};
class SqlEditor extends React.PureComponent {
@@ -78,18 +75,13 @@ class SqlEditor extends React.PureComponent {
ctas: '',
sql: props.queryEditor.sql,
};
this.sqlEditorRef = React.createRef();
this.northPaneRef = React.createRef();
this.onResizeStart = this.onResizeStart.bind(this);
this.onResizeEnd = this.onResizeEnd.bind(this);
this.onResize = this.onResize.bind(this);
this.throttledResize = throttle(this.onResize, 250);
this.runQuery = this.runQuery.bind(this);
this.stopQuery = this.stopQuery.bind(this);
this.onSqlChanged = this.onSqlChanged.bind(this);
this.setQueryEditorSql = this.setQueryEditorSql.bind(this);
this.queryPane = this.queryPane.bind(this);
this.getAceEditorAndSouthPaneHeights = this.getAceEditorAndSouthPaneHeights.bind(this);
this.getSqlEditorHeight = this.getSqlEditorHeight.bind(this);
}
componentWillMount() {
if (this.state.autorun) {
@@ -99,41 +91,29 @@ class SqlEditor extends React.PureComponent {
}
}
componentDidMount() {
// We need to measure the height of the sql editor post render to figure the height of
// the south pane so it gets rendered properly
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({ height: this.getSqlEditorHeight() });
this.onResize();
window.addEventListener('resize', this.throttledResize);
}
onResizeStart() {
// Set the heights on the ace editor and the ace content area after drag starts
// to smooth out the visual transition to the new heights when drag ends
document.getElementById('brace-editor').style.height = `calc(100% - ${SQL_TOOLBAR_HEIGHT}px)`;
document.getElementsByClassName('ace_content')[0].style.height = '100%';
componentWillUnmount() {
window.removeEventListener('resize', this.throttledResize);
}
onResizeEnd([northPercent, southPercent]) {
this.setState(this.getAceEditorAndSouthPaneHeights(
this.state.height, northPercent, southPercent));
onResize() {
const height = this.sqlEditorHeight();
const editorPaneHeight = this.props.queryEditor.height || 200;
const splitPaneHandlerHeight = 8; // 4px of height + 4px of top-margin
this.setState({
editorPaneHeight,
southPaneHeight: height - editorPaneHeight - splitPaneHandlerHeight,
height,
});
if (this.northPaneRef.current && this.northPaneRef.current.clientHeight) {
this.props.actions.persistEditorHeight(this.props.queryEditor,
this.northPaneRef.current.clientHeight);
if (this.refs.ace && this.refs.ace.clientHeight) {
this.props.actions.persistEditorHeight(this.props.queryEditor, this.refs.ace.clientHeight);
}
}
onSqlChanged(sql) {
this.setState({ sql });
}
// One layer of abstraction for easy spying in unit tests
getSqlEditorHeight() {
return this.sqlEditorRef.current ? this.sqlEditorRef.current.clientHeight : 0;
}
// Return the heights for the ace editor and the south pane as an object
// given the height of the sql editor, north pane percent and south pane percent.
getAceEditorAndSouthPaneHeights(height, northPercent, southPercent) {
return {
aceEditorHeight: height * northPercent / 100 - SQL_TOOLBAR_HEIGHT - GUTTER_HEIGHT / 2,
southPaneHeight: height * southPercent / 100,
};
}
getHotkeyConfig() {
return [
{
@@ -207,42 +187,9 @@ class SqlEditor extends React.PureComponent {
ctasChanged(event) {
this.setState({ ctas: event.target.value });
}
queryPane() {
const hotkeys = this.getHotkeyConfig();
const { aceEditorHeight, southPaneHeight } = this.getAceEditorAndSouthPaneHeights(
this.state.height, INITIAL_NORTH_PERCENT, INITIAL_SOUTH_PERCENT);
return (
<div className="queryPane">
<Split
sizes={[INITIAL_NORTH_PERCENT, INITIAL_SOUTH_PERCENT]}
minSize={200}
direction="vertical"
gutterSize={GUTTER_HEIGHT}
onDragStart={this.onResizeStart}
onDragEnd={this.onResizeEnd}
>
<div ref={this.northPaneRef}>
<AceEditorWrapper
actions={this.props.actions}
onBlur={this.setQueryEditorSql}
onChange={this.onSqlChanged}
queryEditor={this.props.queryEditor}
sql={this.props.queryEditor.sql}
tables={this.props.tables}
height={`${this.state.aceEditorHeight || aceEditorHeight}px`}
hotkeys={hotkeys}
/>
{this.renderEditorBottomBar(hotkeys)}
</div>
<SouthPane
editorQueries={this.props.editorQueries}
dataPreviewQueries={this.props.dataPreviewQueries}
actions={this.props.actions}
height={this.state.southPaneHeight || southPaneHeight}
/>
</Split>
</div>
);
sqlEditorHeight() {
const horizontalScrollbarHeight = 25;
return parseInt(this.props.getHeight(), 10) - horizontalScrollbarHeight;
}
renderEditorBottomBar(hotkeys) {
let ctasControls;
@@ -311,7 +258,6 @@ class SqlEditor extends React.PureComponent {
onSave={this.props.actions.saveQuery}
schema={qe.schema}
dbId={qe.dbId}
saveQueryWarning={this.props.saveQueryWarning}
/>
</span>
<span className="m-r-5">
@@ -359,23 +305,74 @@ class SqlEditor extends React.PureComponent {
);
}
render() {
const height = this.sqlEditorHeight();
const defaultNorthHeight = this.props.queryEditor.height || 200;
const hotkeys = this.getHotkeyConfig();
return (
<div ref={this.sqlEditorRef} className="SqlEditor">
<CSSTransition
classNames="schemaPane"
in={!this.props.hideLeftBar}
timeout={300}
>
<div className="schemaPane">
<SqlEditorLeftBar
database={this.props.database}
queryEditor={this.props.queryEditor}
tables={this.props.tables}
actions={this.props.actions}
/>
</div>
</CSSTransition>
{this.queryPane()}
<div
className="SqlEditor"
style={{
height: height + 'px',
}}
>
<Row>
<Collapse
in={!this.props.hideLeftBar}
>
<Col
xs={6}
sm={5}
md={4}
lg={3}
>
<SqlEditorLeftBar
height={height}
database={this.props.database}
queryEditor={this.props.queryEditor}
tables={this.props.tables}
actions={this.props.actions}
/>
</Col>
</Collapse>
<Col
xs={this.props.hideLeftBar ? 12 : 6}
sm={this.props.hideLeftBar ? 12 : 7}
md={this.props.hideLeftBar ? 12 : 8}
lg={this.props.hideLeftBar ? 12 : 9}
style={{ height: this.state.height }}
>
<SplitPane
split="horizontal"
defaultSize={defaultNorthHeight}
minSize={100}
onChange={this.onResize}
>
<div ref="ace" style={{ width: '100%' }}>
<div>
<AceEditorWrapper
actions={this.props.actions}
onBlur={this.setQueryEditorSql}
onChange={this.onSqlChanged}
queryEditor={this.props.queryEditor}
sql={this.props.queryEditor.sql}
tables={this.props.tables}
height={((this.state.editorPaneHeight || defaultNorthHeight) - 50) + 'px'}
hotkeys={hotkeys}
/>
{this.renderEditorBottomBar(hotkeys)}
</div>
</div>
<div ref="south">
<SouthPane
editorQueries={this.props.editorQueries}
dataPreviewQueries={this.props.dataPreviewQueries}
actions={this.props.actions}
height={this.state.southPaneHeight || 0}
/>
</div>
</SplitPane>
</Col>
</Row>
</div>
);
}

View File

@@ -20,6 +20,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Button } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import TableElement from './TableElement';
import TableSelector from '../../components/TableSelector';
@@ -105,7 +106,7 @@ export default class SqlEditorLeftBar extends React.PureComponent {
const tableMetaDataHeight = this.props.height - 130; // 130 is the height of the selects above
const qe = this.props.queryEditor;
return (
<div className="sqlEditorLeftBar">
<div className="clearfix">
<TableSelector
dbId={qe.dbId}
schema={qe.schema}

View File

@@ -39,13 +39,12 @@ const propTypes = {
queryEditors: PropTypes.array,
tabHistory: PropTypes.array.isRequired,
tables: PropTypes.array.isRequired,
getHeight: PropTypes.func.isRequired,
offline: PropTypes.bool,
saveQueryWarning: PropTypes.string,
};
const defaultProps = {
queryEditors: [],
offline: false,
saveQueryWarning: null,
};
let queryCount = 1;
@@ -239,6 +238,7 @@ class TabbedSqlEditors extends React.PureComponent {
<div className="panel-body">
{isSelected && (
<SqlEditor
getHeight={this.props.getHeight}
tables={this.props.tables.filter(xt => xt.queryEditorId === qe.id)}
queryEditor={qe}
editorQueries={this.state.queriesArray}
@@ -249,7 +249,6 @@ class TabbedSqlEditors extends React.PureComponent {
hideLeftBar={this.state.hideLeftBar}
defaultQueryLimit={this.props.defaultQueryLimit}
maxRow={this.props.maxRow}
saveQueryWarning={this.props.saveQueryWarning}
/>
)}
</div>
@@ -292,7 +291,6 @@ function mapStateToProps({ sqlLab, common }) {
offline: sqlLab.offline,
defaultQueryLimit: common.conf.DEFAULT_SQLLAB_LIMIT,
maxRow: common.conf.SQL_MAX_ROW,
saveQueryWarning: common.conf.SQLLAB_SAVE_WARNING_MESSAGE,
};
}
function mapDispatchToProps(dispatch) {

View File

@@ -135,8 +135,7 @@ div.Workspace {
background-color: #e8e8e8;
display: flex;
justify-content: space-between;
border: 1px solid #ccc;
border-top: 0;
border-bottom: 2px solid #ccc;
form {
margin-block-end: 0;
@@ -194,67 +193,21 @@ div.Workspace {
background-color: transparent !important;
}
.SqlLab {
.tab-content {
height: 100%;
}
#brace-editor {
height: calc(100% - 51px);
}
.ace_content {
height: 100%;
}
.SouthPane {
height: 100%;
}
}
.SqlEditor {
display: flex;
flex-direction: row;
height: 100%;
.schemaPane {
flex-grow: 1;
transition: all .3s ease-in-out;
.Resizer {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.schemaPane-enter-done, .schemaPane-exit {
transform: translateX(0);
}
.schemaPane-enter-active, .schemaPane-exit-active {
transform: translateX(-50%);
}
.schemaPane-enter, .schemaPane-exit-done {
transform: translateX(-100%);
max-width: 0;
overflow: hidden;
}
.queryPane {
flex-grow: 8;
position: relative;
margin-left: 15px;
}
.schemaPane-exit-done + .queryPane {
margin-left: 0;
}
.gutter {
.Resizer.horizontal {
height: 4px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
width: 3%;
margin: 3px 47%;
}
.gutter.gutter-vertical {
cursor: row-resize;
width: 4%;
margin-top: 4px;
margin-left: 47%;
}
}
@@ -345,6 +298,9 @@ a.Link {
.tooltip-inner {
max-width: 500px;
}
.SplitPane.horizontal {
padding-right: 4px;
}
.SouthPane {
margin-top: 10px;
position: absolute;

View File

@@ -24,7 +24,6 @@ import { ChartProps } from '@superset-ui/chart';
import { Tooltip } from 'react-bootstrap';
import { Logger, LOG_ACTIONS_RENDER_CHART } from '../logger';
import SuperChart from '../visualizations/core/components/SuperChart';
import transformBigNumber from './transformBigNumber';
const propTypes = {
annotationData: PropTypes.object,
@@ -68,10 +67,9 @@ class ChartRenderer extends React.Component {
this.handleAddFilter = this.handleAddFilter.bind(this);
this.handleRenderSuccess = this.handleRenderSuccess.bind(this);
this.handleRenderFailure = this.handleRenderFailure.bind(this);
this.preTransformProps = this.preTransformProps.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
shouldComponentUpdate(nextProps) {
if (
nextProps.queryResponse &&
['success', 'rendered'].indexOf(nextProps.chartStatus) > -1 &&
@@ -81,7 +79,6 @@ class ChartRenderer extends React.Component {
nextProps.queryResponse !== this.props.queryResponse ||
nextProps.height !== this.props.height ||
nextProps.width !== this.props.width ||
nextState.tooltip !== this.state.tooltip ||
nextProps.triggerRender)
) {
return true;
@@ -152,18 +149,6 @@ class ChartRenderer extends React.Component {
});
}
preTransformProps(chartProps) {
const payload = chartProps.payload;
const data = transformBigNumber(payload.data);
return new ChartProps({
...chartProps,
payload: {
...payload,
data,
},
});
}
renderTooltip() {
const { tooltip } = this.state;
if (tooltip && tooltip.content) {
@@ -207,7 +192,6 @@ class ChartRenderer extends React.Component {
className={`${snakeCase(vizType)}`}
chartType={vizType}
chartProps={skipChartRendering ? null : this.prepareChartProps()}
preTransformProps={this.preTransformProps}
onRenderSuccess={this.handleRenderSuccess}
onRenderFailure={this.handleRenderFailure}
/>

View File

@@ -246,10 +246,7 @@ export function runQuery(formData, force = false, timeout = 60, key) {
export function redirectSQLLab(formData) {
return (dispatch) => {
const { url } = getExploreUrlAndPayload({ formData, endpointType: 'query' });
return SupersetClient.post({
url,
postPayload: { form_data: formData },
})
return SupersetClient.get({ url })
.then(({ json }) => {
const redirectUrl = new URL(window.location);
redirectUrl.pathname = '/superset/sqllab';

View File

@@ -1,45 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// This method transforms any BigNumber object in the given payload to its
// 64-bit float representation. It is a temporary fix so charts receive
// floats instead of BigNumber instances in their props to properly render.
import BigNumber from 'bignumber.js';
export default function transform(payload) {
if (!payload) {
return payload;
} else if (BigNumber.isBigNumber(payload)) {
return payload.toNumber();
} else if (payload.constructor === Object) {
for (const key in payload) {
if (payload.hasOwnProperty(key)) {
// Modify in place to prevent creating large payloads
// eslint-disable-next-line no-param-reassign
payload[key] = transform(payload[key]);
}
}
} else if (payload.constructor === Array) {
payload.forEach((elem, idx) => {
// Modify in place to prevent creating large payloads
// eslint-disable-next-line no-param-reassign
payload[idx] = transform(elem);
});
}
return payload;
}

View File

@@ -83,15 +83,17 @@ class AsyncSelect extends React.PureComponent {
render() {
return (
<Select
placeholder={this.props.placeholder}
options={this.state.options}
value={this.props.value}
isLoading={this.state.isLoading}
onChange={this.onChange}
valueRenderer={this.props.valueRenderer}
{...this.props}
/>
<div>
<Select
placeholder={this.props.placeholder}
options={this.state.options}
value={this.props.value}
isLoading={this.state.isLoading}
onChange={this.onChange}
valueRenderer={this.props.valueRenderer}
{...this.props}
/>
</div>
);
}
}

View File

@@ -55,6 +55,7 @@ export default class EditableTitle extends React.PureComponent {
this.handleClick = this.handleClick.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
// Used so we can access the DOM element if a user clicks on this component.
@@ -111,16 +112,21 @@ export default class EditableTitle extends React.PureComponent {
}
}
// this entire method exists to support using EditableTitle as the title of a
// react-bootstrap Tab, as a workaround for this line in react-bootstrap https://goo.gl/ZVLmv4
//
// tl;dr when a Tab EditableTitle is being edited, typically the Tab it's within has been
// clicked and is focused/active. for accessibility, when focused the Tab <a /> intercepts
// the ' ' key (among others, including all arrows) and onChange() doesn't fire. somehow
// keydown is still called so we can detect this and manually add a ' ' to the current title
handleKeyDown(event) {
if (event.key === ' ') {
event.stopPropagation();
handleKeyUp(ev) {
// this entire method exists to support using EditableTitle as the title of a
// react-bootstrap Tab, as a workaround for this line in react-bootstrap https://goo.gl/ZVLmv4
//
// tl;dr when a Tab EditableTitle is being edited, typically the Tab it's within has been
// clicked and is focused/active. for accessibility, when focused the Tab <a /> intercepts
// the ' ' key (among others, including all arrows) and onChange() doesn't fire. somehow
// keydown is still called so we can detect this and manually add a ' ' to the current title
if (ev.key === ' ') {
let title = ev.target.value;
const titleLength = (title || '').length;
if (title && title[titleLength - 1] !== ' ') {
title = `${title} `;
this.setState(() => ({ title }));
}
}
}
@@ -164,7 +170,7 @@ export default class EditableTitle extends React.PureComponent {
required
value={value}
className={!title ? 'text-muted' : null}
onKeyDown={this.handleKeyDown}
onKeyUp={this.handleKeyUp}
onChange={this.handleChange}
onBlur={this.handleBlur}
onClick={this.handleClick}
@@ -178,7 +184,7 @@ export default class EditableTitle extends React.PureComponent {
type={isEditing ? 'text' : 'button'}
value={value}
className={!title ? 'text-muted' : null}
onKeyDown={this.handleKeyDown}
onKeyUp={this.handleKeyUp}
onChange={this.handleChange}
onBlur={this.handleBlur}
onClick={this.handleClick}

View File

@@ -17,22 +17,8 @@
* under the License.
*/
.TableSelector .fa-refresh {
padding-left: 9px;
padding-top: 7px
}
.TableSelector .refresh-col {
display: flex;
align-items: center;
width: 30px;
}
.TableSelector .section {
padding-bottom: 5px;
display: flex;
flex-direction: row;
}
.TableSelector .select {
flex-grow: 1;
}
.TableSelector .divider {
border-bottom: 1px solid #f2f2f2;
margin: 10px 0;
padding-left: 0px;
}

View File

@@ -20,7 +20,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-virtualized-select';
import createFilterOptions from 'react-select-fast-filter-options';
import { ControlLabel, Label } from 'react-bootstrap';
import { ControlLabel, Col, Label, Row } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection';
@@ -38,6 +38,7 @@ const propTypes = {
tableNameSticky: PropTypes.bool,
tableName: PropTypes.string,
database: PropTypes.object,
horizontal: PropTypes.bool,
sqlLabMode: PropTypes.bool,
onChange: PropTypes.func,
clearable: PropTypes.bool,
@@ -51,6 +52,7 @@ const defaultProps = {
onTableChange: () => {},
onChange: () => {},
tableNameSticky: true,
horizontal: false,
sqlLabMode: true,
clearable: true,
};
@@ -111,11 +113,7 @@ export default class TableSelector extends React.PureComponent {
if (data.result.length === 0) {
this.props.handleError(t("It seems you don't have access to any database"));
}
return data.result.map(row => ({
...row,
// label is used for the typeahead
label: `${row.backend} ${row.database_name}`,
}));
return data.result;
}
fetchTables(force, substr) {
// This can be large so it shouldn't be put in the Redux store
@@ -201,10 +199,10 @@ export default class TableSelector extends React.PureComponent {
}
renderSelectRow(select, refreshBtn) {
return (
<div className="section">
<span className="select">{select}</span>
<span className="refresh-col">{refreshBtn}</span>
</div>
<Row>
<Col md={11}>{select}</Col>
<Col md={1} className="refresh-col">{refreshBtn}</Col>
</Row>
);
}
renderDatabaseSelect() {
@@ -234,25 +232,29 @@ export default class TableSelector extends React.PureComponent {
/>);
}
renderSchema() {
return this.renderSelectRow(
<Select
name="select-schema"
placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
options={this.state.schemaOptions}
value={this.props.schema}
valueRenderer={o => (
<div>
<span className="text-muted">{t('Schema:')}</span> {o.label}
</div>
return (
<div className="m-t-5">
{this.renderSelectRow(
<Select
name="select-schema"
placeholder={t('Select a schema (%s)', this.state.schemaOptions.length)}
options={this.state.schemaOptions}
value={this.props.schema}
valueRenderer={o => (
<div>
<span className="text-muted">{t('Schema:')}</span> {o.label}
</div>
)}
isLoading={this.state.schemaLoading}
autosize={false}
onChange={this.changeSchema}
/>,
<RefreshLabel
onClick={() => this.onDatabaseChange({ id: this.props.dbId }, true)}
tooltipContent={t('Force refresh schema list')}
/>,
)}
isLoading={this.state.schemaLoading}
autosize={false}
onChange={this.changeSchema}
/>,
<RefreshLabel
onClick={() => this.onDatabaseChange({ id: this.props.dbId }, true)}
tooltipContent={t('Force refresh schema list')}
/>,
</div>
);
}
renderTable() {
@@ -288,16 +290,20 @@ export default class TableSelector extends React.PureComponent {
value={this.state.tableName}
loadOptions={this.getTableNamesBySubStr}
/>);
return this.renderSelectRow(
select,
<RefreshLabel
onClick={() => this.changeSchema({ value: this.props.schema }, true)}
tooltipContent={t('Force refresh table list')}
/>);
return (
<div className="m-t-5">
{this.renderSelectRow(
select,
<RefreshLabel
onClick={() => this.changeSchema({ value: this.props.schema }, true)}
tooltipContent={t('Force refresh table list')}
/>)}
</div>);
}
renderSeeTableLabel() {
return (
<div className="section">
<div>
<hr />
<ControlLabel>
{t('See table schema')}{' '}
<small>
@@ -313,11 +319,18 @@ export default class TableSelector extends React.PureComponent {
render() {
return (
<div className="TableSelector">
{this.renderDatabaseSelect()}
{this.renderSchema()}
<div className="divider" />
{this.props.sqlLabMode && this.renderSeeTableLabel()}
{this.renderTable()}
{this.props.horizontal ?
<div>
<Col md={4}>{this.renderDatabaseSelect()}</Col>
<Col md={4}>{this.renderSchema()}</Col>
<Col md={4}>{this.renderTable()}</Col>
</div> :
<div>
<div>{this.renderDatabaseSelect()}</div>
<div className="m-t-5">{this.renderSchema()}</div>
{this.props.sqlLabMode && this.renderSeeTableLabel()}
<div className="m-t-5">{this.renderTable()}</div>
</div>}
</div>
);
}

View File

@@ -55,6 +55,7 @@ export default function VirtualizedRendererWrap(renderer) {
key={key}
style={{ ...(option.style || {}), ...style }}
title={option.title}
data-test={option.optionName}
{...events}
>
{renderer(option)}

View File

@@ -253,10 +253,7 @@ export function addSliceToDashboard(id) {
),
);
}
const form_data = {
...selectedSlice.form_data,
slice_id: selectedSlice.slice_id,
};
const form_data = selectedSlice.form_data;
const newChart = {
...initChart,
id,

View File

@@ -448,14 +448,6 @@ export class DatasourceEditor extends React.PureComponent {
label={t('Hours offset')}
control={<TextControl />}
/>
{ this.state.isSqla &&
<Field
fieldKey="template_params"
label={t('Template parameters')}
descr={t('A set of parameters that become available in the query using Jinja templating syntax')}
control={<TextControl />}
/>
}
</Fieldset>);
}

View File

@@ -49,8 +49,10 @@ class DatasourceModal extends React.PureComponent {
super(props);
this.state = {
errors: [],
showDatasource: false,
datasource: props.datasource,
};
this.toggleShowDatasource = this.toggleShowDatasource.bind(this);
this.setSearchRef = this.setSearchRef.bind(this);
this.onDatasourceChange = this.onDatasourceChange.bind(this);
this.onClickSave = this.onClickSave.bind(this);
@@ -109,6 +111,10 @@ class DatasourceModal extends React.PureComponent {
this.dialog = ref;
}
toggleShowDatasource() {
this.setState({ showDatasource: !this.state.showDatasource });
}
renderSaveDialog() {
return (
<div>

View File

@@ -19,19 +19,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Col,
Collapse,
Label,
OverlayTrigger,
Row,
Tooltip,
Well,
} from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import ControlHeader from '../ControlHeader';
import ColumnOption from '../../../components/ColumnOption';
import MetricOption from '../../../components/MetricOption';
import DatasourceModal from '../../../datasource/DatasourceModal';
const propTypes = {
@@ -58,51 +52,25 @@ class DatasourceControl extends React.PureComponent {
};
this.toggleShowDatasource = this.toggleShowDatasource.bind(this);
this.toggleEditDatasourceModal = this.toggleEditDatasourceModal.bind(this);
this.renderDatasource = this.renderDatasource.bind(this);
}
onChange(vizType) {
this.props.onChange(vizType);
this.setState({ showModal: false });
}
toggleShowDatasource() {
this.setState(({ showDatasource }) => ({ showDatasource: !showDatasource }));
}
toggleModal() {
this.setState(({ showModal }) => ({ showModal: !showModal }));
}
toggleEditDatasourceModal() {
this.setState(({ showEditDatasourceModal }) => ({
showEditDatasourceModal: !showEditDatasourceModal,
}));
}
renderDatasource() {
const datasource = this.props.datasource;
return (
<div className="m-t-10">
<Well className="m-t-0">
<div className="m-b-10">
<Label>
<i className="fa fa-database" /> {datasource.database.backend}
</Label>
{` ${datasource.database.name} `}
</div>
<Row className="datasource-container">
<Col md={6}>
<strong>Columns</strong>
{datasource.columns.map(col => (
<div key={col.column_name}>
<ColumnOption showType column={col} />
</div>
))}
</Col>
<Col md={6}>
<strong>Metrics</strong>
{datasource.metrics.map(m => (
<div key={m.metric_name}>
<MetricOption metric={m} showType />
</div>
))}
</Col>
</Row>
</Well>
</div>
);
}
render() {
return (
<div>
@@ -117,21 +85,6 @@ class DatasourceControl extends React.PureComponent {
{this.props.datasource.name}
</Label>
</OverlayTrigger>
<OverlayTrigger
placement="right"
overlay={
<Tooltip id={'toggle-datasource-tooltip'}>
{t('Expand/collapse datasource configuration')}
</Tooltip>
}
>
<a href="#">
<i
className={`fa fa-${this.state.showDatasource ? 'minus' : 'plus'}-square m-r-5`}
onClick={this.toggleShowDatasource}
/>
</a>
</OverlayTrigger>
{this.props.datasource.type === 'table' &&
<OverlayTrigger
placement="right"
@@ -149,7 +102,6 @@ class DatasourceControl extends React.PureComponent {
<i className="fa fa-flask m-r-5" />
</a>
</OverlayTrigger>}
<Collapse in={this.state.showDatasource}>{this.renderDatasource()}</Collapse>
<DatasourceModal
datasource={this.props.datasource}
show={this.state.showEditDatasourceModal}

View File

@@ -60,21 +60,6 @@ function isDictionaryForAdhocMetric(value) {
return value && !(value instanceof AdhocMetric) && value.expressionType;
}
function columnsContainAllMetrics(value, nextProps) {
const columnNames = new Set(
[...nextProps.columns, ...nextProps.savedMetrics]
// eslint-disable-next-line camelcase
.map(({ column_name, metric_name }) => (column_name || metric_name)),
);
return (Array.isArray(value) ? value : [value])
.filter(metric => metric)
// find column names
.map(metric => metric.column ? metric.column.column_name : metric.column_name || metric)
.filter(name => name)
.every(name => columnNames.has(name));
}
// adhoc metrics are stored as dictionaries in URL params. We convert them back into the
// AdhocMetric class for typechecking, consistency and instance method access.
function coerceAdhocMetrics(value) {
@@ -150,22 +135,14 @@ export default class MetricsControl extends React.PureComponent {
}
componentWillReceiveProps(nextProps) {
const { value } = this.props;
if (
!isEqual(this.props.columns, nextProps.columns) ||
!isEqual(this.props.savedMetrics, nextProps.savedMetrics)
isEqual(this.props.columns) !== isEqual(nextProps.columns) ||
isEqual(this.props.savedMetrics) !== isEqual(nextProps.savedMetrics)
) {
this.setState({ options: this.optionsForSelect(nextProps) });
// Remove metrics if selected value no longer a column
const containsAllMetrics = columnsContainAllMetrics(value, nextProps);
if (!containsAllMetrics) {
this.props.onChange([]);
}
this.props.onChange([]);
}
if (value !== nextProps.value) {
if (this.props.value !== nextProps.value) {
this.setState({ value: coerceAdhocMetrics(nextProps.value) });
}
}

View File

@@ -22,43 +22,16 @@ import {
Row, Col, FormControl, OverlayTrigger, Popover,
} from 'react-bootstrap';
import Select from 'react-select';
import { t } from '@superset-ui/translation';
import InfoTooltipWithTrigger from '../../../components/InfoTooltipWithTrigger';
import BoundsControl from './BoundsControl';
import CheckboxControl from './CheckboxControl';
const propTypes = {
label: PropTypes.string,
tooltip: PropTypes.string,
colType: PropTypes.string,
width: PropTypes.string,
height: PropTypes.string,
timeLag: PropTypes.string,
timeRatio: PropTypes.string,
comparisonType: PropTypes.string,
showYAxis: PropTypes.bool,
yAxisBounds: PropTypes.array,
bounds: PropTypes.array,
d3format: PropTypes.string,
dateFormat: PropTypes.string,
onChange: PropTypes.func,
};
const defaultProps = {
label: t('Time Series Columns'),
tooltip: '',
colType: '',
width: '',
height: '',
timeLag: '',
timeRatio: '',
comparisonType: '',
showYAxis: false,
yAxisBounds: [null, null],
bounds: [null, null],
d3format: '',
dateFormat: '',
onChange: () => {},
};
@@ -79,21 +52,7 @@ const colTypeOptions = [
export default class TimeSeriesColumnControl extends React.Component {
constructor(props) {
super(props);
const state = {
label: this.props.label,
tooltip: this.props.tooltip,
colType: this.props.colType,
width: this.props.width,
height: this.props.height,
timeLag: this.props.timeLag,
timeRatio: this.props.timeRatio,
comparisonType: this.props.comparisonType,
showYAxis: this.props.showYAxis,
yAxisBounds: this.props.yAxisBounds,
bounds: this.props.bounds,
d3format: this.props.d3format,
dateFormat: this.props.dateFormat,
};
const state = { ...props };
delete state.onChange;
this.state = state;
this.onChange = this.onChange.bind(this);

View File

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

View File

@@ -22,7 +22,6 @@
*/
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import * as sections from './sections';
import extraOverrides from './extraOverrides';
import Area from './Area';
import Bar from './Bar';
@@ -73,7 +72,7 @@ import DeckPolygon from './DeckPolygon';
import DeckScatter from './DeckScatter';
import DeckScreengrid from './DeckScreengrid';
export const controlPanelConfigs = extraOverrides({
export const controlPanelConfigs = {
area: Area,
bar: Bar,
big_number: BigNumber,
@@ -123,7 +122,7 @@ export const controlPanelConfigs = extraOverrides({
deck_scatter: DeckScatter,
deck_screengrid: DeckScreengrid,
});
};
export default controlPanelConfigs;

View File

@@ -2311,7 +2311,7 @@ export const controls = {
type: 'CollectionControl',
label: 'Filters',
description: t('Filter configuration for the filter box'),
validators: [],
validators: [v.nonEmpty],
controlName: 'FilterBoxItemControl',
mapStateToProps: ({ datasource }) => ({ datasource }),
},

View File

@@ -23,7 +23,6 @@ import { scaleLinear } from 'd3-scale';
import { Table, Thead, Th, Tr, Td } from 'reactable-arc';
import { formatNumber } from '@superset-ui/number-format';
import { formatTime } from '@superset-ui/time-format';
import moment from 'moment';
import MetricOption from '../../components/MetricOption';
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
@@ -149,7 +148,7 @@ class TimeTable extends React.PureComponent {
renderTooltip={({ index }) => (
<div>
<strong>{formatNumber(column.d3format, sparkData[index])}</strong>
<div>{formatTime(column.dateFormat, moment.utc(entries[index].time).toDate())}</div>
<div>{formatTime(column.dateFormat, entries[index].time)}</div>
</div>
)}
/>

View File

@@ -31,9 +31,9 @@ import { fitViewport } from './layers/common';
const { getScale } = CategoricalColorNamespace;
function getCategories(fd, data) {
const c = fd.colorPicker || { r: 0, g: 0, b: 0, a: 1 };
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
const colorFn = getScale(fd.colorScheme);
const colorFn = getScale(fd.color_scheme);
const categories = {};
data.forEach((d) => {
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
@@ -108,7 +108,7 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
// the granularity has to be read from the payload form_data, not the
// props formData which comes from the instantaneous controls state
const granularity = (
props.payload.form_data.timeGrainSqla ||
props.payload.form_data.time_grain_sqla ||
props.payload.form_data.granularity ||
'P1D'
);
@@ -154,8 +154,8 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
features = this.addColor(features, fd);
// Apply user defined data mutator if defined
if (fd.jsDataMutator) {
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
if (fd.js_data_mutator) {
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
features = jsFnMutator(features);
}
@@ -180,8 +180,8 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
return [getLayer(fd, filteredPayload, onAddFilter, setTooltip)];
}
addColor(data, fd) {
const c = fd.colorPicker || { r: 0, g: 0, b: 0, a: 1 };
const colorFn = getScale(fd.colorScheme);
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const colorFn = getScale(fd.color_scheme);
return data.map((d) => {
let color;
if (fd.dimension) {
@@ -229,14 +229,14 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
viewport={this.state.viewport}
onViewportChange={this.onViewportChange}
mapboxApiAccessToken={this.props.mapboxApiKey}
mapStyle={this.props.formData.mapboxStyle}
mapStyle={this.props.formData.mapbox_style}
setControlValue={this.props.setControlValue}
>
<Legend
categories={this.state.categories}
toggleCategory={this.toggleCategory}
showSingleCategory={this.showSingleCategory}
position={this.props.formData.legendPosition}
position={this.props.formData.legend_position}
/>
</AnimatableDeckGLContainer>
</div>

View File

@@ -56,7 +56,7 @@ class DeckMulti extends React.PureComponent {
const filters = [
...(subslice.form_data.filters || []),
...(formData.filters || []),
...(formData.extraFilters || []),
...(formData.extra_filters || []),
];
const subsliceCopy = {
...subslice,
@@ -70,7 +70,7 @@ class DeckMulti extends React.PureComponent {
endpoint: getExploreLongUrl(subsliceCopy.form_data, 'json'),
})
.then(({ json }) => {
const layer = layerGenerators[subsliceCopy.form_data.vizType](
const layer = layerGenerators[subsliceCopy.form_data.viz_type](
subsliceCopy.form_data,
json,
);
@@ -96,7 +96,7 @@ class DeckMulti extends React.PureComponent {
mapboxApiAccessToken={payload.data.mapboxApiKey}
viewport={viewport}
layers={layers}
mapStyle={formData.mapboxStyle}
mapStyle={formData.mapbox_style}
setControlValue={setControlValue}
/>
);

View File

@@ -90,7 +90,7 @@ export function createDeckGLComponent(getLayer, getPoints) {
mapboxApiAccessToken={payload.data.mapboxApiKey}
viewport={viewport}
layers={[layer]}
mapStyle={formData.mapboxStyle}
mapStyle={formData.mapbox_style}
setControlValue={setControlValue}
onViewportChange={this.onViewportChange}
/>);

View File

@@ -31,14 +31,14 @@ function getPoints(data) {
export function getLayer(fd, payload, onAddFilter, setTooltip) {
const data = payload.data.features;
const sc = fd.colorPicker;
const tc = fd.targetColorPicker;
const sc = fd.color_picker;
const tc = fd.target_color_picker;
return new ArcLayer({
id: `path-layer-${fd.sliceId}`,
id: `path-layer-${fd.slice_id}`,
data,
getSourceColor: d => d.sourceColor || d.color || [sc.r, sc.g, sc.b, 255 * sc.a],
getTargetColor: d => d.targetColor || d.color || [tc.r, tc.g, tc.b, 255 * tc.a],
strokeWidth: (fd.strokeWidth) ? fd.strokeWidth : 3,
strokeWidth: (fd.stroke_width) ? fd.stroke_width : 3,
...commonLayerProps(fd, setTooltip),
});
}

View File

@@ -77,8 +77,8 @@ const recurseGeoJson = (node, propOverrides, extraProps) => {
export function getLayer(formData, payload, onAddFilter, setTooltip) {
const fd = formData;
const fc = fd.fillColorPicker;
const sc = fd.strokeColorPicker;
const fc = fd.fill_color_picker;
const sc = fd.stroke_color_picker;
const fillColor = [fc.r, fc.g, fc.b, 255 * fc.a];
const strokeColor = [sc.r, sc.g, sc.b, 255 * sc.a];
const propOverrides = {};
@@ -93,19 +93,19 @@ export function getLayer(formData, payload, onAddFilter, setTooltip) {
recurseGeoJson(payload.data, propOverrides);
let jsFnMutator;
if (fd.jsDataMutator) {
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
jsFnMutator = sandboxedEval(fd.jsDataMutator);
jsFnMutator = sandboxedEval(fd.js_data_mutator);
features = jsFnMutator(features);
}
return new GeoJsonLayer({
id: `geojson-layer-${fd.sliceId}`,
id: `geojson-layer-${fd.slice_id}`,
filled: fd.filled,
data: features,
stroked: fd.stroked,
extruded: fd.extruded,
pointRadiusScale: fd.pointRadiusScale,
pointRadiusScale: fd.point_radius_scale,
...commonLayerProps(fd, setTooltip),
});
}
@@ -145,7 +145,7 @@ function deckGeoJson(props) {
mapboxApiAccessToken={payload.data.mapboxApiKey}
viewport={viewport}
layers={[layer]}
mapStyle={formData.mapboxStyle}
mapStyle={formData.mapbox_style}
setControlValue={setControlValue}
/>
);

View File

@@ -24,24 +24,24 @@ import { createDeckGLComponent } from '../../factory';
export function getLayer(formData, payload, onAddFilter, setTooltip) {
const fd = formData;
const c = fd.colorPicker;
const c = fd.color_picker;
let data = payload.data.features.map(d => ({
...d,
color: [c.r, c.g, c.b, 255 * c.a],
}));
if (fd.jsDataMutator) {
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const aggFunc = getAggFunc(fd.jsAggFunction, p => p.weight);
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);
return new GridLayer({
id: `grid-layer-${fd.sliceId}`,
id: `grid-layer-${fd.slice_id}`,
data,
pickable: true,
cellSize: fd.gridSize,
cellSize: fd.grid_size,
minColor: [0, 0, 0, 0],
extruded: fd.extruded,
maxColor: [c.r, c.g, c.b, 255 * c.a],

View File

@@ -24,23 +24,23 @@ import { createDeckGLComponent } from '../../factory';
export function getLayer(formData, payload, onAddFilter, setTooltip) {
const fd = formData;
const c = fd.colorPicker;
const c = fd.color_picker;
let data = payload.data.features.map(d => ({
...d,
color: [c.r, c.g, c.b, 255 * c.a],
}));
if (fd.jsDataMutator) {
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
const aggFunc = getAggFunc(fd.jsAggFunction, p => p.weight);
const aggFunc = getAggFunc(fd.js_agg_function, p => p.weight);
return new HexagonLayer({
id: `hex-layer-${fd.sliceId}`,
id: `hex-layer-${fd.slice_id}`,
data,
pickable: true,
radius: fd.gridSize,
radius: fd.grid_size,
minColor: [0, 0, 0, 0],
extruded: fd.extruded,
maxColor: [c.r, c.g, c.b, 255 * c.a],

View File

@@ -23,22 +23,22 @@ import { createDeckGLComponent } from '../../factory';
export function getLayer(formData, payload, onAddFilter, setTooltip) {
const fd = formData;
const c = fd.colorPicker;
const c = fd.color_picker;
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
let data = payload.data.features.map(feature => ({
...feature,
path: feature.path,
width: fd.lineWidth,
width: fd.line_width,
color: fixedColor,
}));
if (fd.jsDataMutator) {
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
if (fd.js_data_mutator) {
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
return new PathLayer({
id: `path-layer-${fd.sliceId}`,
id: `path-layer-${fd.slice_id}`,
data,
rounded: true,
widthScale: 1,

View File

@@ -50,8 +50,8 @@ function getElevation(d, colorScaler) {
export function getLayer(formData, payload, setTooltip, selected, onSelect, filters) {
const fd = formData;
const fc = fd.fillColorPicker;
const sc = fd.strokeColorPicker;
const fc = fd.fill_color_picker;
const sc = fd.stroke_color_picker;
let data = [...payload.data.features];
if (filters != null) {
@@ -60,9 +60,9 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
});
}
if (fd.jsDataMutator) {
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
@@ -76,13 +76,13 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
// when polygons are selected, reduce the opacity of non-selected polygons
const colorScaler = (d) => {
const baseColor = baseColorScaler(d);
if (selected.length > 0 && selected.indexOf(d[fd.lineColumn]) === -1) {
if (selected.length > 0 && selected.indexOf(d[fd.line_column]) === -1) {
baseColor[3] /= 2;
}
return baseColor;
};
return new PolygonLayer({
id: `path-layer-${fd.sliceId}`,
id: `path-layer-${fd.slice_id}`,
data,
pickable: true,
filled: fd.filled,
@@ -90,7 +90,7 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
getPolygon: d => d.polygon,
getFillColor: colorScaler,
getLineColor: [sc.r, sc.g, sc.b, 255 * sc.a],
getLineWidth: fd.lineWidth,
getLineWidth: fd.line_width,
extruded: fd.extruded,
getElevation: d => getElevation(d, colorScaler),
elevationScale: fd.multiplier,
@@ -138,7 +138,7 @@ class DeckGLPolygon extends React.Component {
// the granularity has to be read from the payload form_data, not the
// props formData which comes from the instantaneous controls state
const granularity = (
props.payload.form_data.timeGrainSqla ||
props.payload.form_data.time_grain_sqla ||
props.payload.form_data.granularity ||
'P1D'
);
@@ -177,7 +177,7 @@ class DeckGLPolygon extends React.Component {
const selected = [...this.state.selected];
if (doubleClick) {
selected.splice(0, selected.length, polygon);
} else if (formData.togglePolygons) {
} else if (formData.toggle_polygons) {
const i = selected.indexOf(polygon);
if (i === -1) {
selected.push(polygon);
@@ -189,8 +189,8 @@ class DeckGLPolygon extends React.Component {
}
this.setState({ selected, lastClick: now });
if (formData.tableFilter) {
onAddFilter(formData.lineColumn, selected, false, true);
if (formData.table_filter) {
onAddFilter(formData.line_column, selected, false, true);
}
}
onValuesChange(values) {
@@ -249,14 +249,14 @@ class DeckGLPolygon extends React.Component {
viewport={viewport}
onViewportChange={this.onViewportChange}
mapboxApiAccessToken={payload.data.mapboxApiKey}
mapStyle={formData.mapboxStyle}
mapStyle={formData.mapbox_style}
setControlValue={setControlValue}
aggregation
>
{formData.metric !== null &&
<Legend
categories={buckets}
position={formData.legendPosition}
position={formData.legend_position}
/>}
</AnimatableDeckGLContainer>
</div>

View File

@@ -27,24 +27,24 @@ function getPoints(data) {
export function getLayer(fd, payload, onAddFilter, setTooltip) {
const dataWithRadius = payload.data.features.map((d) => {
let radius = unitToRadius(fd.pointUnit, d.radius) || 10;
let radius = unitToRadius(fd.point_unit, d.radius) || 10;
if (fd.multiplier) {
radius *= fd.multiplier;
}
if (d.color) {
return { ...d, radius };
}
const c = fd.colorPicker || { r: 0, g: 0, b: 0, a: 1 };
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const color = [c.r, c.g, c.b, c.a * 255];
return { ...d, radius, color };
});
return new ScatterplotLayer({
id: `scatter-layer-${fd.sliceId}`,
id: `scatter-layer-${fd.slice_id}`,
data: dataWithRadius,
fp64: true,
radiusMinPixels: fd.minRadius || null,
radiusMaxPixels: fd.maxRadius || null,
radiusMinPixels: fd.min_radius || null,
radiusMaxPixels: fd.max_radius || null,
outline: false,
...commonLayerProps(fd, setTooltip),
});

View File

@@ -32,15 +32,15 @@ function getPoints(data) {
export function getLayer(formData, payload, onAddFilter, setTooltip, filters) {
const fd = formData;
const c = fd.colorPicker;
const c = fd.color_picker;
let data = payload.data.features.map(d => ({
...d,
color: [c.r, c.g, c.b, 255 * c.a],
}));
if (fd.jsDataMutator) {
if (fd.js_data_mutator) {
// Applying user defined data mutator if defined
const jsFnMutator = sandboxedEval(fd.jsDataMutator);
const jsFnMutator = sandboxedEval(fd.js_data_mutator);
data = jsFnMutator(data);
}
@@ -53,10 +53,10 @@ export function getLayer(formData, payload, onAddFilter, setTooltip, filters) {
// Passing a layer creator function instead of a layer since the
// layer needs to be regenerated at each render
return new ScreenGridLayer({
id: `screengrid-layer-${fd.sliceId}`,
id: `screengrid-layer-${fd.slice_id}`,
data,
pickable: true,
cellSizePixels: fd.gridSize,
cellSizePixels: fd.grid_size,
minColor: [c.r, c.g, c.b, 0],
maxColor: [c.r, c.g, c.b, 255 * c.a],
outline: false,
@@ -102,7 +102,7 @@ class DeckGLScreenGrid extends React.PureComponent {
// the granularity has to be read from the payload form_data, not the
// props formData which comes from the instantaneous controls state
const granularity = (
props.payload.form_data.timeGrainSqla ||
props.payload.form_data.time_grain_sqla ||
props.payload.form_data.granularity ||
'P1D'
);
@@ -176,7 +176,7 @@ class DeckGLScreenGrid extends React.PureComponent {
viewport={this.state.viewport}
onViewportChange={this.onViewportChange}
mapboxApiAccessToken={payload.data.mapboxApiKey}
mapStyle={formData.mapboxStyle}
mapStyle={formData.mapbox_style}
setControlValue={setControlValue}
aggregation
/>

View File

@@ -54,13 +54,13 @@ export function commonLayerProps(formData, setTooltip, onSelect) {
const fd = formData;
let onHover;
let tooltipContentGenerator;
if (fd.jsTooltip) {
tooltipContentGenerator = sandboxedEval(fd.jsTooltip);
} else if (fd.lineColumn && fd.metric && ['geohash', 'zipcode'].indexOf(fd.lineType) >= 0) {
if (fd.js_tooltip) {
tooltipContentGenerator = sandboxedEval(fd.js_tooltip);
} else if (fd.line_column && fd.metric && ['geohash', 'zipcode'].indexOf(fd.line_type) >= 0) {
const metricLabel = fd.metric.label || fd.metric;
tooltipContentGenerator = o => (
<div>
<div>{fd.lineColumn}: <strong>{o.object[fd.lineColumn]}</strong></div>
<div>{fd.line_column}: <strong>{o.object[fd.line_column]}</strong></div>
{fd.metric &&
<div>{metricLabel}: <strong>{o.object[metricLabel]}</strong></div>}
</div>);
@@ -71,7 +71,7 @@ export function commonLayerProps(formData, setTooltip, onSelect) {
setTooltip({
content: tooltipContentGenerator(o),
x: o.x,
y: o.y + 30,
y: o.y,
});
} else {
setTooltip(null);
@@ -79,13 +79,13 @@ export function commonLayerProps(formData, setTooltip, onSelect) {
};
}
let onClick;
if (fd.jsOnclickHref) {
if (fd.js_onclick_href) {
onClick = (o) => {
const href = sandboxedEval(fd.jsOnclickHref)(o);
const href = sandboxedEval(fd.js_onclick_href)(o);
window.open(href);
};
} else if (fd.tableFilter && onSelect !== undefined) {
onClick = o => onSelect(o.object[fd.lineColumn]);
} else if (fd.table_filter && onSelect !== undefined) {
onClick = o => onSelect(o.object[fd.line_column]);
}
return {
onClick,

View File

@@ -24,8 +24,8 @@ import { hexToRGB } from '../../modules/colors';
const DEFAULT_NUM_BUCKETS = 10;
export function getBreakPoints({
breakPoints: formDataBreakPoints,
numBuckets: formDataNumBuckets,
break_points: formDataBreakPoints,
num_buckets: formDataNumBuckets,
}, features, accessor) {
if (!features) {
return [];
@@ -46,15 +46,15 @@ export function getBreakPoints({
}
export function getBreakPointColorScaler({
breakPoints: formDataBreakPoints,
numBuckets: formDataNumBuckets,
linearColorScheme,
break_points: formDataBreakPoints,
num_buckets: formDataNumBuckets,
linear_color_scheme: linearColorScheme,
opacity,
}, features, accessor) {
const breakPoints = formDataBreakPoints || formDataNumBuckets
? getBreakPoints({
breakPoints: formDataBreakPoints,
numBuckets: formDataNumBuckets,
break_points: formDataBreakPoints,
num_buckets: formDataNumBuckets,
}, features, accessor)
: null;
const colorScheme = Array.isArray(linearColorScheme)

View File

@@ -94,9 +94,9 @@ class LineMulti extends React.Component {
const combinedFormData = {
...subslice.form_data,
filters: (subsliceFormData.filters || [])
.concat(filters || []),
.concat(filters || [])
.concat(extraFilters || []),
time_range: timeRange,
extra_filters: extraFilters || [],
};
const addPrefix = prefixMetricWithSliceName;
return getJson(getExploreLongUrl(combinedFormData, 'json'))

View File

@@ -641,15 +641,15 @@ function nvd3Vis(element, props) {
// If x bounds are shown, we need a right margin
margins.right = Math.max(20, maxXAxisLabelHeight / 2) + marginPad;
}
if (xLabelRotation === 45) {
if (staggerLabels) {
margins.bottom = 40;
} else {
margins.bottom = (
maxXAxisLabelHeight * Math.sin(Math.PI * xLabelRotation / 180)
) + marginPad;
margins.right = (
maxXAxisLabelHeight * Math.cos(Math.PI * xLabelRotation / 180)
) + marginPad;
} else if (staggerLabels) {
margins.bottom = 40;
}
if (isVizTypes(['dual_line', 'line_multi'])) {
@@ -711,9 +711,8 @@ function nvd3Vis(element, props) {
.attr('height', height)
.call(chart);
// On scroll, hide (not remove) tooltips so they can reappear on hover.
// Throttle to only 4x/second.
window.addEventListener('scroll', throttle(() => hideTooltips(false), 250));
// on scroll, hide tooltips. throttle to only 4x/second.
window.addEventListener('scroll', throttle(hideTooltips, 250));
// The below code should be run AFTER rendering because chart is updated in call()
if (isTimeSeries && activeAnnotationLayers.length > 0) {
@@ -934,10 +933,10 @@ function nvd3Vis(element, props) {
return chart;
};
// Remove tooltips before rendering chart, if the chart is being re-rendered sometimes
// hide tooltips before rendering chart, if the chart is being re-rendered sometimes
// there are left over tooltips in the dom,
// this will clear them before rendering the chart again.
hideTooltips(true);
hideTooltips();
nv.addGraph(drawGraph);
}

View File

@@ -165,19 +165,10 @@ export function generateBubbleTooltipContent({
return s;
}
// shouldRemove indicates whether the nvtooltips should be removed from the DOM
export function hideTooltips(shouldRemove) {
export function hideTooltips() {
const targets = document.querySelectorAll('.nvtooltip');
if (targets.length > 0) {
// Only set opacity to 0 when hiding tooltips so they would reappear
// on hover, which sets the opacity to 1
for (const t of targets) {
if (shouldRemove) {
t.remove();
} else {
t.style.opacity = 0;
}
}
targets.forEach(t => t.remove());
}
}

View File

@@ -475,20 +475,17 @@ g.annotation-container {
position:relative;
padding-right: 40px;
}
.reactable-header-sortable::before{
.reactable-header-sortable::after {
font: normal normal normal 14px/1 FontAwesome;
content: "\f0dc";
position: absolute;
top: 6px;
right: 5px;
margin-left: 10px;
color: @brand-primary;
}
.reactable-header-sort-asc::before{
.reactable-header-sort-asc::after{
content: "\f0de";
color: @brand-primary;
}
.reactable-header-sort-desc::before{
.reactable-header-sort-desc::after{
content: "\f0dd";
color: @brand-primary;
}

View File

@@ -14,247 +14,30 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=C,R,W
from datetime import datetime, timedelta
import logging
import pickle as pkl
import traceback
# pylint: disable=R
from typing import Dict, List
import numpy as np
import pandas as pd
from superset import app, cache
from superset import db
from superset.connectors.connector_registry import ConnectorRegistry
from superset.utils import core as utils
from superset.utils.core import DTTM_ALIAS
from .query_object import QueryObject
config = app.config
stats_logger = config.get('STATS_LOGGER')
class QueryContext:
"""
The query context contains the query object and additional fields necessary
to retrieve the data payload for a given viz.
"""
default_fillna = 0
cache_type = 'df'
enforce_numerical_metrics = True
# TODO: Type datasource and query_object dictionary with TypedDict when it becomes
# a vanilla python type https://github.com/python/mypy/issues/5288
def __init__(
self,
datasource: Dict,
queries: List[Dict],
force: bool = False,
custom_cache_timeout: int = None,
):
self.datasource = ConnectorRegistry.get_datasource(datasource.get('type'),
int(datasource.get('id')),
db.session)
self.queries = list(map(lambda query_obj: QueryObject(**query_obj), queries))
self.force = force
self.custom_cache_timeout = custom_cache_timeout
self.enforce_numerical_metrics = True
def get_query_result(self, query_object):
"""Returns a pandas dataframe based on the query object"""
# Here, we assume that all the queries will use the same datasource, which is
# is a valid assumption for current setting. In a long term, we may or maynot
# support multiple queries from different data source.
timestamp_format = None
if self.datasource.type == 'table':
dttm_col = self.datasource.get_col(query_object.granularity)
if dttm_col:
timestamp_format = dttm_col.python_date_format
# The datasource here can be different backend but the interface is common
result = self.datasource.query(query_object.to_dict())
df = result.df
# Transform the timestamp we received from database to pandas supported
# datetime format. If no python_date_format is specified, the pattern will
# be considered as the default ISO date format
# If the datetime format is unix, the parse will use the corresponding
# parsing logic
if df is not None and not df.empty:
if DTTM_ALIAS in df.columns:
if timestamp_format in ('epoch_s', 'epoch_ms'):
# Column has already been formatted as a timestamp.
df[DTTM_ALIAS] = df[DTTM_ALIAS].apply(pd.Timestamp)
else:
df[DTTM_ALIAS] = pd.to_datetime(
df[DTTM_ALIAS], utc=False, format=timestamp_format)
if self.datasource.offset:
df[DTTM_ALIAS] += timedelta(hours=self.datasource.offset)
df[DTTM_ALIAS] += query_object.time_shift
if self.enforce_numerical_metrics:
self.df_metrics_to_num(df, query_object)
df.replace([np.inf, -np.inf], np.nan)
df = self.handle_nulls(df)
return {
'query': result.query,
'status': result.status,
'error_message': result.error_message,
'df': df,
}
def df_metrics_to_num(self, df, query_object):
"""Converting metrics to numeric when pandas.read_sql cannot"""
metrics = [metric for metric in query_object.metrics]
for col, dtype in df.dtypes.items():
if dtype.type == np.object_ and col in metrics:
df[col] = pd.to_numeric(df[col], errors='coerce')
def handle_nulls(self, df):
fillna = self.get_fillna_for_columns(df.columns)
return df.fillna(fillna)
def get_fillna_for_col(self, col):
"""Returns the value to use as filler for a specific Column.type"""
if col and col.is_string:
return ' NULL'
return self.default_fillna
def get_fillna_for_columns(self, columns=None):
"""Returns a dict or scalar that can be passed to DataFrame.fillna"""
if columns is None:
return self.default_fillna
columns_dict = {col.column_name: col for col in self.datasource.columns}
fillna = {
c: self.get_fillna_for_col(columns_dict.get(c))
for c in columns
}
return fillna
def get_data(self, df):
return df.to_dict(orient='records')
def get_single_payload(self, query_obj):
"""Returns a payload of metadata and data"""
payload = self.get_df_payload(query_obj)
df = payload.get('df')
status = payload.get('status')
if status != utils.QueryStatus.FAILED:
if df is not None and df.empty:
payload['error'] = 'No data'
else:
payload['data'] = self.get_data(df)
if 'df' in payload:
del payload['df']
return payload
def get_payload(self):
"""Get all the paylaods from the arrays"""
return [self.get_single_payload(query_ojbect) for query_ojbect in self.queries]
@property
def cache_timeout(self):
if self.custom_cache_timeout is not None:
return self.custom_cache_timeout
if self.datasource.cache_timeout is not None:
return self.datasource.cache_timeout
if (
hasattr(self.datasource, 'database') and
self.datasource.database.cache_timeout) is not None:
return self.datasource.database.cache_timeout
return config.get('CACHE_DEFAULT_TIMEOUT')
def get_df_payload(self, query_obj, **kwargs):
"""Handles caching around the df paylod retrieval"""
cache_key = query_obj.cache_key(
datasource=self.datasource.uid, **kwargs) if query_obj else None
logging.info('Cache key: {}'.format(cache_key))
is_loaded = False
stacktrace = None
df = None
cached_dttm = datetime.utcnow().isoformat().split('.')[0]
cache_value = None
status = None
query = ''
error_message = None
if cache_key and cache and not self.force:
cache_value = cache.get(cache_key)
if cache_value:
stats_logger.incr('loaded_from_cache')
try:
cache_value = pkl.loads(cache_value)
df = cache_value['df']
query = cache_value['query']
status = utils.QueryStatus.SUCCESS
is_loaded = True
except Exception as e:
logging.exception(e)
logging.error('Error reading cache: ' +
utils.error_msg_from_exception(e))
logging.info('Serving from cache')
if query_obj and not is_loaded:
try:
query_result = self.get_query_result(query_obj)
status = query_result['status']
query = query_result['query']
error_message = query_result['error_message']
df = query_result['df']
if status != utils.QueryStatus.FAILED:
stats_logger.incr('loaded_from_source')
is_loaded = True
except Exception as e:
logging.exception(e)
if not error_message:
error_message = '{}'.format(e)
status = utils.QueryStatus.FAILED
stacktrace = traceback.format_exc()
if (
is_loaded and
cache_key and
cache and
status != utils.QueryStatus.FAILED):
try:
cache_value = dict(
dttm=cached_dttm,
df=df if df is not None else None,
query=query,
)
cache_binary = pkl.dumps(
cache_value, protocol=pkl.HIGHEST_PROTOCOL)
logging.info('Caching {} chars at key {}'.format(
len(cache_binary), cache_key))
stats_logger.incr('set_cache_key')
cache.set(
cache_key,
cache_value=cache_binary,
timeout=self.cache_timeout)
except Exception as e:
# cache.set call can fail if the backend is down or if
# the key is too large or whatever other reasons
logging.warning('Could not cache key {}'.format(cache_key))
logging.exception(e)
cache.delete(cache_key)
return {
'cache_key': cache_key,
'cached_dttm': cache_value['dttm'] if cache_value is not None else None,
'cache_timeout': self.cache_timeout,
'df': df,
'error': error_message,
'is_cached': cache_key is not None,
'query': query,
'status': status,
'stacktrace': stacktrace,
'rowcount': len(df.index) if df is not None else 0,
}
def get_data(self):
raise NotImplementedError()

View File

@@ -15,17 +15,15 @@
# specific language governing permissions and limitations
# under the License.
# pylint: disable=R
import hashlib
from typing import Dict, List, Optional, Union
import simplejson as json
from typing import Dict, List, Optional
from superset import app
from superset.utils import core as utils
# TODO: Type Metrics dictionary with TypedDict when it becomes a vanilla python type
# https://github.com/python/mypy/issues/5288
Metric = Dict
class QueryObject:
"""
@@ -35,87 +33,31 @@ class QueryObject:
def __init__(
self,
granularity: str,
metrics: List[Union[Dict, str]],
groupby: List[str] = None,
metrics: List[Metric] = None,
filters: List[str] = None,
time_range: Optional[str] = None,
time_shift: Optional[str] = None,
is_timeseries: bool = False,
timeseries_limit: int = 0,
row_limit: int = app.config.get('ROW_LIMIT'),
timeseries_limit_metric: Optional[Dict] = None,
limit: int = 0,
timeseries_limit_metric: Optional[Metric] = None,
order_desc: bool = True,
extras: Optional[Dict] = None,
prequeries: Optional[Dict] = None,
is_prequery: bool = False,
columns: List[str] = None,
orderby: List[List] = None,
):
self.granularity = granularity
self.from_dttm, self.to_dttm = utils.get_since_until(time_range, time_shift)
self.is_timeseries = is_timeseries
self.time_range = time_range
self.time_shift = utils.parse_human_timedelta(time_shift)
self.groupby = groupby if groupby is not None else []
# Temporal solution for backward compatability issue
# due the new format of non-ad-hoc metric.
self.metrics = [metric if 'expressionType' in metric else metric['label']
for metric in metrics]
self.groupby = groupby or []
self.metrics = metrics or []
self.filter = filters or []
self.row_limit = row_limit
self.filter = filters if filters is not None else []
self.timeseries_limit = timeseries_limit
self.timeseries_limit = int(limit)
self.timeseries_limit_metric = timeseries_limit_metric
self.order_desc = order_desc
self.prequeries = prequeries
self.is_prequery = is_prequery
self.extras = extras if extras is not None else {}
self.columns = columns if columns is not None else []
self.orderby = orderby if orderby is not None else []
self.prequeries = []
self.is_prequery = False
self.extras = extras
def to_dict(self):
query_object_dict = {
'granularity': self.granularity,
'from_dttm': self.from_dttm,
'to_dttm': self.to_dttm,
'is_timeseries': self.is_timeseries,
'groupby': self.groupby,
'metrics': self.metrics,
'row_limit': self.row_limit,
'filter': self.filter,
'timeseries_limit': self.timeseries_limit,
'timeseries_limit_metric': self.timeseries_limit_metric,
'order_desc': self.order_desc,
'prequeries': self.prequeries,
'is_prequery': self.is_prequery,
'extras': self.extras,
'columns': self.columns,
'orderby': self.orderby,
}
return query_object_dict
def cache_key(self, **extra):
"""
The cache key is made out of the key/values in `query_obj`, plus any
other key/values in `extra`
We remove datetime bounds that are hard values, and replace them with
the use-provided inputs to bounds, which may be time-relative (as in
"5 days ago" or "now").
"""
cache_dict = self.to_dict()
cache_dict.update(extra)
for k in ['from_dttm', 'to_dttm']:
del cache_dict[k]
if self.time_range:
cache_dict['time_range'] = self.time_range
json_data = self.json_dumps(cache_dict, sort_keys=True)
return hashlib.md5(json_data.encode('utf-8')).hexdigest()
def json_dumps(self, obj, sort_keys=False):
return json.dumps(
obj,
default=utils.json_int_dttm_ser,
ignore_nan=True,
sort_keys=sort_keys,
)
raise NotImplementedError()

View File

@@ -181,14 +181,18 @@ LANGUAGES = {
'pt': {'flag': 'pt', 'name': 'Portuguese'},
'pt_BR': {'flag': 'br', 'name': 'Brazilian Portuguese'},
'ru': {'flag': 'ru', 'name': 'Russian'},
'ko': {'flag': 'kr', 'name': 'Korean'},
}
# ---------------------------------------------------
# Feature flags
# ---------------------------------------------------
# Feature flags that are on by default go here. Their
# values can be overridden by those in super_config.py
FEATURE_FLAGS = {}
# Feature flags that are set by default go here. Their values can be
# overwritten by those specified under FEATURE_FLAGS in super_config.py
# For example, DEFAULT_FEATURE_FLAGS = { 'FOO': True, 'BAR': False } here
# and FEATURE_FLAGS = { 'BAR': True, 'BAZ': True } in superset_config.py
# will result in combined feature flags of { 'FOO': True, 'BAR': True, 'BAZ': True }
DEFAULT_FEATURE_FLAGS = {}
# ---------------------------------------------------
# Image and file configuration
@@ -326,9 +330,6 @@ DEFAULT_SQLLAB_LIMIT = 1000
# Maximum number of tables/views displayed in the dropdown window in SQL Lab.
MAX_TABLE_NAMES = 3000
# Adds a warning message on sqllab save query modal.
SQLLAB_SAVE_WARNING_MESSAGE = None
# If defined, shows this text in an alert-warning box in the navbar
# one example use case may be "STAGING" to make it clear that this is
# not the production version of the site.

View File

@@ -86,7 +86,7 @@ class BaseDatasource(AuditMixinNullable, ImportMixin):
@property
def column_names(self):
return sorted([c.column_name for c in self.columns], key=lambda x: x or '')
return sorted([c.column_name for c in self.columns])
@property
def columns_types(self):
@@ -166,9 +166,7 @@ class BaseDatasource(AuditMixinNullable, ImportMixin):
def data(self):
"""Data representation of the datasource sent to the frontend"""
order_by_choices = []
# self.column_names return sorted column_names
for s in self.column_names:
s = str(s or '')
for s in sorted(self.column_names):
order_by_choices.append((json.dumps([s, True]), s + ' [asc]'))
order_by_choices.append((json.dumps([s, False]), s + ' [desc]'))

View File

@@ -280,7 +280,7 @@ class DruidColumn(Model, BaseColumn):
export_parent = 'datasource'
def __repr__(self):
return self.column_name or str(self.id)
return self.column_name
@property
def expression(self):
@@ -1144,9 +1144,7 @@ class DruidDatasource(Model, BaseDatasource):
pre_qry['aggregations'] = aggs_dict
pre_qry['post_aggregations'] = post_aggs_dict
else:
agg_keys = qry['aggregations'].keys()
order_by = list(agg_keys)[0] if agg_keys else None
order_by = list(qry['aggregations'].keys())[0]
# Limit on the number of timeseries, doing a two-phases query
pre_qry['granularity'] = 'all'
pre_qry['threshold'] = min(row_limit,

View File

@@ -41,7 +41,7 @@ from . import models
class DruidColumnInlineView(CompactCRUDMixin, SupersetModelView): # noqa
datamodel = SQLAInterface(models.DruidColumn)
list_title = _('List Druid Column')
list_title = _('Columns')
show_title = _('Show Druid Column')
add_title = _('Add Druid Column')
edit_title = _('Edit Druid Column')
@@ -109,7 +109,7 @@ appbuilder.add_view_no_menu(DruidColumnInlineView)
class DruidMetricInlineView(CompactCRUDMixin, SupersetModelView): # noqa
datamodel = SQLAInterface(models.DruidMetric)
list_title = _('List Druid Metric')
list_title = _('Metrics')
show_title = _('Show Druid Metric')
add_title = _('Add Druid Metric')
edit_title = _('Edit Druid Metric')
@@ -160,7 +160,7 @@ appbuilder.add_view_no_menu(DruidMetricInlineView)
class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): # noqa
datamodel = SQLAInterface(models.DruidCluster)
list_title = _('List Druid Cluster')
list_title = _('Druid Clusters')
show_title = _('Show Druid Cluster')
add_title = _('Add Druid Cluster')
edit_title = _('Edit Druid Cluster')
@@ -212,7 +212,7 @@ appbuilder.add_view(
class DruidDatasourceModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): # noqa
datamodel = SQLAInterface(models.DruidDatasource)
list_title = _('List Druid Datasource')
list_title = _('Druid Datasources')
show_title = _('Show Druid Datasource')
add_title = _('Add Druid Datasource')
edit_title = _('Edit Druid Datasource')

View File

@@ -115,7 +115,9 @@ class TableColumn(Model, BaseColumn):
label = label if label else self.column_name
label = self.table.get_label(label)
if not self.expression:
col = column(self.column_name).label(label)
db_engine_spec = self.table.database.db_engine_spec
type_ = db_engine_spec.get_sqla_column_type(self.type)
col = column(self.column_name, type_=type_).label(label)
else:
col = literal_column(self.expression).label(label)
return col
@@ -422,7 +424,6 @@ class SqlaTable(Model, BaseDatasource):
d['time_grain_sqla'] = grains
d['main_dttm_col'] = self.main_dttm_col
d['fetch_values_predicate'] = self.fetch_values_predicate
d['template_params'] = self.template_params
return d
def values_for_column(self, column_name, limit=10000):
@@ -730,11 +731,15 @@ class SqlaTable(Model, BaseDatasource):
ob = inner_main_metric_expr
if timeseries_limit_metric:
ob = self._get_timeseries_orderby(
timeseries_limit_metric,
metrics_dict,
cols,
)
if utils.is_adhoc_metric(timeseries_limit_metric):
ob = self.adhoc_metric_to_sqla(timeseries_limit_metric, cols)
elif timeseries_limit_metric in metrics_dict:
timeseries_limit_metric = metrics_dict.get(
timeseries_limit_metric,
)
ob = timeseries_limit_metric.get_sqla_col()
else:
raise Exception(_("Metric '{}' is not valid".format(m)))
direction = desc if order_desc else asc
subq = subq.order_by(direction(ob))
subq = subq.limit(timeseries_limit)
@@ -749,16 +754,6 @@ class SqlaTable(Model, BaseDatasource):
tbl = tbl.join(subq.alias(), and_(*on_clause))
else:
if timeseries_limit_metric:
orderby = [(
self._get_timeseries_orderby(
timeseries_limit_metric,
metrics_dict,
cols,
),
False,
)]
# run subquery to get top groups
subquery_obj = {
'prequeries': prequeries,
@@ -787,19 +782,6 @@ class SqlaTable(Model, BaseDatasource):
return qry.select_from(tbl)
def _get_timeseries_orderby(self, timeseries_limit_metric, metrics_dict, cols):
if utils.is_adhoc_metric(timeseries_limit_metric):
ob = self.adhoc_metric_to_sqla(timeseries_limit_metric, cols)
elif timeseries_limit_metric in metrics_dict:
timeseries_limit_metric = metrics_dict.get(
timeseries_limit_metric,
)
ob = timeseries_limit_metric.get_sqla_col()
else:
raise Exception(_("Metric '{}' is not valid".format(timeseries_limit_metric)))
return ob
def _get_top_groups(self, df, dimensions):
cols = {col.column_name: col for col in self.columns}
groups = []
@@ -876,13 +858,13 @@ class SqlaTable(Model, BaseDatasource):
dbcol = dbcols.get(col.name, None)
if not dbcol:
dbcol = TableColumn(column_name=col.name, type=datatype)
dbcol.groupby = dbcol.is_string
dbcol.filterable = dbcol.is_string
dbcol.sum = dbcol.is_num
dbcol.avg = dbcol.is_num
dbcol.is_dttm = dbcol.is_time
else:
dbcol.type = datatype
dbcol.groupby = True
dbcol.filterable = True
self.columns.append(dbcol)
if not any_date_col and dbcol.is_time:
any_date_col = col.name

View File

@@ -16,6 +16,8 @@
# under the License.
# pylint: disable=C,R,W
"""Views used by the SqlAlchemy connector"""
import logging
from flask import flash, Markup, redirect
from flask_appbuilder import CompactCRUDMixin, expose
from flask_appbuilder.actions import action
@@ -33,11 +35,13 @@ from superset.views.base import (
)
from . import models
logger = logging.getLogger(__name__)
class TableColumnInlineView(CompactCRUDMixin, SupersetModelView): # noqa
datamodel = SQLAInterface(models.TableColumn)
list_title = _('List Columns')
list_title = _('Columns')
show_title = _('Show Column')
add_title = _('Add Column')
edit_title = _('Edit Column')
@@ -110,7 +114,7 @@ appbuilder.add_view_no_menu(TableColumnInlineView)
class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView): # noqa
datamodel = SQLAInterface(models.SqlMetric)
list_title = _('List Metrics')
list_title = _('Metrics')
show_title = _('Show Metric')
add_title = _('Add Metric')
edit_title = _('Edit Metric')
@@ -164,7 +168,7 @@ appbuilder.add_view_no_menu(SqlMetricInlineView)
class TableModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): # noqa
datamodel = SQLAInterface(models.SqlaTable)
list_title = _('List Tables')
list_title = _('Tables')
show_title = _('Show Table')
add_title = _('Import a table definition')
edit_title = _('Edit Table')
@@ -269,12 +273,13 @@ class TableModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): # noqa
# Fail before adding if the table can't be found
try:
table.get_sqla_table_object()
except Exception:
except Exception as e:
logger.exception(f'Got an error in pre_add for {table.name}')
raise Exception(_(
'Table [{}] could not be found, '
'please double check your '
'database connection, schema, and '
'table name').format(table.name))
'table name, error: {}').format(table.name, str(e)))
def post_add(self, table, flash_message=True):
table.fetch_metadata()

Binary file not shown.

Binary file not shown.

View File

@@ -14,7 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import gzip
import json
import os
import pandas as pd
import polyline
@@ -22,17 +24,16 @@ from sqlalchemy import String, Text
from superset import db
from superset.utils.core import get_or_create_main_db
from .helpers import TBL, get_example_data
from .helpers import DATA_FOLDER, TBL
def load_bart_lines():
tbl_name = 'bart_lines'
content = get_example_data('bart-lines.json.gz')
df = pd.read_json(content, encoding='latin-1')
df['path_json'] = df.path.map(json.dumps)
df['polyline'] = df.path.map(polyline.encode)
del df['path']
with gzip.open(os.path.join(DATA_FOLDER, 'bart-lines.json.gz')) as f:
df = pd.read_json(f, encoding='latin-1')
df['path_json'] = df.path.map(json.dumps)
df['polyline'] = df.path.map(polyline.encode)
del df['path']
df.to_sql(
tbl_name,
db.engine,

View File

@@ -0,0 +1,97 @@
DEPT_ID,2003,2004,2005,2006,2007,2008,2009,2010,2011,2012,2013,2014
FR-01,6866,6706,6976,7228,6949,7323,7157,7282,7265,7242,7296,7354
FR-02,6841,6761,6889,7041,6847,7012,6941,7050,6939,6755,6559,6468
FR-03,3391,3335,3363,3503,3277,3289,3308,3402,3196,3288,3198,3152
FR-04,1460,1522,1514,1536,1569,1569,1513,1547,1578,1561,1629,1538
FR-05,1408,1403,1395,1461,1448,1441,1513,1470,1399,1441,1406,1383
FR-06,11144,11514,11631,11754,11633,12275,11949,12257,11999,12087,12149,12170
FR-07,3367,3176,3414,3484,3484,3447,3307,3380,3360,3405,3179,3254
FR-08,3532,3422,3420,3343,3552,3522,3312,3254,3137,3258,3021,2966
FR-09,1350,1412,1389,1499,1570,1493,1452,1473,1404,1425,1413,1364
FR-10,3428,3553,3692,3685,3619,3721,3745,3722,3635,3587,3436,3377
FR-11,3421,3321,3502,3661,3723,3778,3797,3770,3789,3669,3618,3516
FR-12,2558,2614,2701,2829,2769,2748,2640,2694,2682,2615,2475,2555
FR-13,23908,24056,24411,25371,25126,25412,25547,26410,25889,26328,26762,26384
FR-14,8231,8257,8251,8531,8310,8183,8304,8111,8041,7833,7644,7466
FR-15,1344,1396,1391,1398,1357,1300,1377,1274,1237,1230,1290,1214
FR-16,3401,3514,3570,3653,3618,3666,3408,3564,3459,3490,3472,3378
FR-17,5935,5900,6069,6089,5903,6136,6209,6185,6065,5916,5778,5846
FR-18,3301,3271,3313,3231,3341,3303,3229,3341,3159,3120,3128,3097
FR-19,2133,2250,2319,2327,2245,2263,2231,2247,2196,2163,2055,2094
FR-21,6079,6052,5844,5986,6015,5960,5852,5963,5906,5905,5769,5779
FR-22,6413,6317,6287,6743,6473,6494,6559,6438,6221,6184,5927,5790
FR-23,1011,957,1054,1038,1013,1029,1044,919,967,998,897,879
FR-24,3607,3690,3662,3758,3760,3832,3672,3665,3645,3547,3486,3479
FR-25,6529,6798,6782,6993,6804,7097,6914,7105,6826,6778,6732,6659
FR-26,5525,5703,5579,5945,5833,5927,5846,5915,5978,5912,6026,5965
FR-27,7213,7220,7386,7402,7471,7717,7714,7715,7738,7676,7352,7242
FR-28,5370,5363,5585,5632,5440,5677,5573,5716,5540,5548,5312,5295
FR-29,9900,9963,9851,10184,9962,10040,9733,9823,9615,9597,9277,9088
FR-2A,1232,1228,1348,1337,1284,1370,1422,1408,1422,1398,1317,1371
FR-2B,1455,1444,1525,1474,1564,1569,1580,1591,1662,1612,1599,1616
FR-30,7446,7777,7901,8384,8190,8449,8354,8494,8467,8196,8427,8216
FR-31,13989,13900,14233,14957,14968,15415,15317,15770,16031,16347,16290,16641
FR-32,1635,1625,1666,1580,1669,1689,1718,1671,1587,1668,1648,1643
FR-33,15610,15819,15722,16539,16514,16636,17072,17271,17098,17097,17265,17303
FR-34,11380,11562,11636,12191,12252,12564,12531,12658,13000,12902,12899,13008
FR-35,12134,12072,12405,12687,12606,12837,12917,12876,13033,12892,12729,12555
FR-36,2312,2314,2394,2283,2341,2371,2178,2221,2137,2136,2006,2030
FR-37,6620,6594,6644,6813,6434,6811,6828,6886,6696,6796,6594,6718
FR-38,14885,15356,15447,15830,15646,15999,15916,16136,15739,15948,15724,15664
FR-39,2964,3017,2924,3021,3037,3045,2897,2865,2758,2741,2675,2637
FR-40,3477,3621,3574,3755,3953,3862,3914,3993,3853,3880,3864,3696
FR-41,3617,3678,3724,3815,3752,3847,3786,3777,3667,3704,3581,3517
FR-42,8804,8906,8975,9184,9222,9357,9174,9403,9357,9473,9086,9183
FR-43,2458,2416,2485,2426,2301,2398,2390,2348,2300,2244,2247,2157
FR-44,15795,15988,16301,16530,16664,16763,16766,17159,16747,16821,16822,16700
FR-45,8265,8424,8200,8635,8644,8524,8499,8757,8686,8689,8526,8355
FR-46,1537,1430,1477,1563,1511,1555,1435,1506,1423,1487,1345,1415
FR-47,3173,3245,3341,3426,3399,3378,3445,3359,3397,3332,3361,3347
FR-48,768,772,760,784,781,779,798,736,695,711,663,651
FR-49,10018,10085,10148,10548,10227,10270,10165,10312,10320,10061,10016,9781
FR-50,5490,5487,5538,5448,5356,5384,5231,5238,5193,5282,4998,4911
FR-51,6916,6979,7108,7118,6932,7065,7061,7182,7070,6761,7000,6887
FR-52,2100,2095,2029,2104,2062,2037,1944,1889,1916,1847,1923,1881
FR-53,3846,3932,3981,4118,3835,3912,3897,3962,3733,3750,3656,3456
FR-54,8398,8671,8542,8743,8421,8559,8487,8536,8499,8387,8197,8135
FR-55,2218,2287,2158,2294,2296,2220,2122,2221,2119,2107,2070,1928
FR-56,7817,8036,7802,8221,7968,8288,7942,8029,7894,7909,7645,7554
FR-57,11710,11970,12048,12114,11853,12012,11831,11856,11474,11579,11421,11385
FR-58,2123,2181,2115,2137,2151,2049,1986,1982,1999,1942,1850,1801
FR-59,36099,36257,35960,36858,36531,36572,36508,36703,36678,36513,36354,35923
FR-60,10696,10630,10753,11144,11097,11162,11013,10960,11032,10941,10814,10802
FR-61,3323,3243,3117,3276,3316,3185,3248,3192,3105,2933,2834,2810
FR-62,18888,19304,19407,19780,19668,19902,19661,19784,19720,19017,19054,18809
FR-63,6576,6632,6701,6902,6896,6865,6774,7131,6828,6933,6699,6908
FR-64,6436,6338,6395,6680,6288,6455,6652,6569,6459,6490,6269,6497
FR-65,2144,2186,2095,2284,2266,2095,2161,2149,2110,2201,2057,2111
FR-66,4456,4320,4563,4779,4638,4756,4837,4869,4843,4943,4914,4800
FR-67,13024,12828,13195,13388,13152,13231,13218,13346,13030,12895,13043,13262
FR-68,9045,8945,8912,9324,8941,8909,8938,9177,8927,8818,8713,8826
FR-69,23376,23796,24270,24808,24465,25120,25528,25973,25921,26294,25914,26712
FR-70,2675,2773,2827,2975,2888,2755,2785,2761,2643,2609,2510,2458
FR-71,5717,5709,5789,5876,5736,5860,5838,5865,5811,5752,5514,5552
FR-72,6871,6935,6770,7133,6808,6909,6957,6942,6810,6703,6645,6664
FR-73,4687,4736,4795,4903,5000,4971,4863,5074,4917,4786,4762,4798
FR-74,8839,8753,8967,9124,8939,9333,9271,9521,9476,9829,9893,9982
FR-75,31493,31817,31378,31748,30820,30623,31063,31447,30094,29291,28945,29134
FR-76,15862,15650,15691,16004,16066,16041,15947,16338,16146,16014,15574,15199
FR-77,17501,17729,18317,18986,18978,19240,19331,19712,19824,19678,19331,19708
FR-78,19937,19431,19766,20438,19899,19895,19868,20312,19886,19827,19886,19525
FR-79,3994,4100,4191,4057,4037,4331,4157,4060,4006,4029,3986,3718
FR-80,7134,7035,7024,7021,6939,7094,6838,7103,6989,6843,6743,6506
FR-81,3579,3611,3837,3933,3869,4056,4030,3925,4006,3939,3829,3831
FR-82,2398,2591,2590,2823,2858,2932,2935,2926,2978,2940,2827,2829
FR-83,10388,10622,10646,10889,10938,11131,10955,11159,11146,11240,10917,11123
FR-84,6547,6629,6608,6805,6694,7000,7014,6967,7008,7107,7171,7058
FR-85,6874,7062,7299,7589,7647,7629,7718,7601,7442,7436,7164,7070
FR-86,4594,4568,4725,4850,4753,4909,4953,5006,4885,4880,4708,4686
FR-87,3449,3659,3834,3754,3829,3891,3985,3848,3907,3825,3723,3724
FR-88,4291,4264,4310,4416,4274,4215,4252,4057,3883,3715,3796,3679
FR-89,3710,3844,3821,3929,3917,4045,3991,3842,3699,3729,3780,3621
FR-90,1896,1766,1837,1888,1880,1818,1822,1802,1794,1763,1675,1707
FR-91,17122,17614,17753,18281,17932,18134,18040,18509,18493,18506,18510,18903
FR-92,24607,24649,24588,25426,24937,25217,25192,25194,25083,24790,24614,24675
FR-93,25868,26313,26760,27916,27743,28062,28313,28513,28362,28675,28687,29471
FR-94,19637,19866,19947,20948,20331,20736,21022,21391,20991,20967,20748,21566
FR-95,17346,17863,18012,19015,18624,18761,18728,19506,19551,19495,19550,19737
1 DEPT_ID 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014
2 FR-01 6866 6706 6976 7228 6949 7323 7157 7282 7265 7242 7296 7354
3 FR-02 6841 6761 6889 7041 6847 7012 6941 7050 6939 6755 6559 6468
4 FR-03 3391 3335 3363 3503 3277 3289 3308 3402 3196 3288 3198 3152
5 FR-04 1460 1522 1514 1536 1569 1569 1513 1547 1578 1561 1629 1538
6 FR-05 1408 1403 1395 1461 1448 1441 1513 1470 1399 1441 1406 1383
7 FR-06 11144 11514 11631 11754 11633 12275 11949 12257 11999 12087 12149 12170
8 FR-07 3367 3176 3414 3484 3484 3447 3307 3380 3360 3405 3179 3254
9 FR-08 3532 3422 3420 3343 3552 3522 3312 3254 3137 3258 3021 2966
10 FR-09 1350 1412 1389 1499 1570 1493 1452 1473 1404 1425 1413 1364
11 FR-10 3428 3553 3692 3685 3619 3721 3745 3722 3635 3587 3436 3377
12 FR-11 3421 3321 3502 3661 3723 3778 3797 3770 3789 3669 3618 3516
13 FR-12 2558 2614 2701 2829 2769 2748 2640 2694 2682 2615 2475 2555
14 FR-13 23908 24056 24411 25371 25126 25412 25547 26410 25889 26328 26762 26384
15 FR-14 8231 8257 8251 8531 8310 8183 8304 8111 8041 7833 7644 7466
16 FR-15 1344 1396 1391 1398 1357 1300 1377 1274 1237 1230 1290 1214
17 FR-16 3401 3514 3570 3653 3618 3666 3408 3564 3459 3490 3472 3378
18 FR-17 5935 5900 6069 6089 5903 6136 6209 6185 6065 5916 5778 5846
19 FR-18 3301 3271 3313 3231 3341 3303 3229 3341 3159 3120 3128 3097
20 FR-19 2133 2250 2319 2327 2245 2263 2231 2247 2196 2163 2055 2094
21 FR-21 6079 6052 5844 5986 6015 5960 5852 5963 5906 5905 5769 5779
22 FR-22 6413 6317 6287 6743 6473 6494 6559 6438 6221 6184 5927 5790
23 FR-23 1011 957 1054 1038 1013 1029 1044 919 967 998 897 879
24 FR-24 3607 3690 3662 3758 3760 3832 3672 3665 3645 3547 3486 3479
25 FR-25 6529 6798 6782 6993 6804 7097 6914 7105 6826 6778 6732 6659
26 FR-26 5525 5703 5579 5945 5833 5927 5846 5915 5978 5912 6026 5965
27 FR-27 7213 7220 7386 7402 7471 7717 7714 7715 7738 7676 7352 7242
28 FR-28 5370 5363 5585 5632 5440 5677 5573 5716 5540 5548 5312 5295
29 FR-29 9900 9963 9851 10184 9962 10040 9733 9823 9615 9597 9277 9088
30 FR-2A 1232 1228 1348 1337 1284 1370 1422 1408 1422 1398 1317 1371
31 FR-2B 1455 1444 1525 1474 1564 1569 1580 1591 1662 1612 1599 1616
32 FR-30 7446 7777 7901 8384 8190 8449 8354 8494 8467 8196 8427 8216
33 FR-31 13989 13900 14233 14957 14968 15415 15317 15770 16031 16347 16290 16641
34 FR-32 1635 1625 1666 1580 1669 1689 1718 1671 1587 1668 1648 1643
35 FR-33 15610 15819 15722 16539 16514 16636 17072 17271 17098 17097 17265 17303
36 FR-34 11380 11562 11636 12191 12252 12564 12531 12658 13000 12902 12899 13008
37 FR-35 12134 12072 12405 12687 12606 12837 12917 12876 13033 12892 12729 12555
38 FR-36 2312 2314 2394 2283 2341 2371 2178 2221 2137 2136 2006 2030
39 FR-37 6620 6594 6644 6813 6434 6811 6828 6886 6696 6796 6594 6718
40 FR-38 14885 15356 15447 15830 15646 15999 15916 16136 15739 15948 15724 15664
41 FR-39 2964 3017 2924 3021 3037 3045 2897 2865 2758 2741 2675 2637
42 FR-40 3477 3621 3574 3755 3953 3862 3914 3993 3853 3880 3864 3696
43 FR-41 3617 3678 3724 3815 3752 3847 3786 3777 3667 3704 3581 3517
44 FR-42 8804 8906 8975 9184 9222 9357 9174 9403 9357 9473 9086 9183
45 FR-43 2458 2416 2485 2426 2301 2398 2390 2348 2300 2244 2247 2157
46 FR-44 15795 15988 16301 16530 16664 16763 16766 17159 16747 16821 16822 16700
47 FR-45 8265 8424 8200 8635 8644 8524 8499 8757 8686 8689 8526 8355
48 FR-46 1537 1430 1477 1563 1511 1555 1435 1506 1423 1487 1345 1415
49 FR-47 3173 3245 3341 3426 3399 3378 3445 3359 3397 3332 3361 3347
50 FR-48 768 772 760 784 781 779 798 736 695 711 663 651
51 FR-49 10018 10085 10148 10548 10227 10270 10165 10312 10320 10061 10016 9781
52 FR-50 5490 5487 5538 5448 5356 5384 5231 5238 5193 5282 4998 4911
53 FR-51 6916 6979 7108 7118 6932 7065 7061 7182 7070 6761 7000 6887
54 FR-52 2100 2095 2029 2104 2062 2037 1944 1889 1916 1847 1923 1881
55 FR-53 3846 3932 3981 4118 3835 3912 3897 3962 3733 3750 3656 3456
56 FR-54 8398 8671 8542 8743 8421 8559 8487 8536 8499 8387 8197 8135
57 FR-55 2218 2287 2158 2294 2296 2220 2122 2221 2119 2107 2070 1928
58 FR-56 7817 8036 7802 8221 7968 8288 7942 8029 7894 7909 7645 7554
59 FR-57 11710 11970 12048 12114 11853 12012 11831 11856 11474 11579 11421 11385
60 FR-58 2123 2181 2115 2137 2151 2049 1986 1982 1999 1942 1850 1801
61 FR-59 36099 36257 35960 36858 36531 36572 36508 36703 36678 36513 36354 35923
62 FR-60 10696 10630 10753 11144 11097 11162 11013 10960 11032 10941 10814 10802
63 FR-61 3323 3243 3117 3276 3316 3185 3248 3192 3105 2933 2834 2810
64 FR-62 18888 19304 19407 19780 19668 19902 19661 19784 19720 19017 19054 18809
65 FR-63 6576 6632 6701 6902 6896 6865 6774 7131 6828 6933 6699 6908
66 FR-64 6436 6338 6395 6680 6288 6455 6652 6569 6459 6490 6269 6497
67 FR-65 2144 2186 2095 2284 2266 2095 2161 2149 2110 2201 2057 2111
68 FR-66 4456 4320 4563 4779 4638 4756 4837 4869 4843 4943 4914 4800
69 FR-67 13024 12828 13195 13388 13152 13231 13218 13346 13030 12895 13043 13262
70 FR-68 9045 8945 8912 9324 8941 8909 8938 9177 8927 8818 8713 8826
71 FR-69 23376 23796 24270 24808 24465 25120 25528 25973 25921 26294 25914 26712
72 FR-70 2675 2773 2827 2975 2888 2755 2785 2761 2643 2609 2510 2458
73 FR-71 5717 5709 5789 5876 5736 5860 5838 5865 5811 5752 5514 5552
74 FR-72 6871 6935 6770 7133 6808 6909 6957 6942 6810 6703 6645 6664
75 FR-73 4687 4736 4795 4903 5000 4971 4863 5074 4917 4786 4762 4798
76 FR-74 8839 8753 8967 9124 8939 9333 9271 9521 9476 9829 9893 9982
77 FR-75 31493 31817 31378 31748 30820 30623 31063 31447 30094 29291 28945 29134
78 FR-76 15862 15650 15691 16004 16066 16041 15947 16338 16146 16014 15574 15199
79 FR-77 17501 17729 18317 18986 18978 19240 19331 19712 19824 19678 19331 19708
80 FR-78 19937 19431 19766 20438 19899 19895 19868 20312 19886 19827 19886 19525
81 FR-79 3994 4100 4191 4057 4037 4331 4157 4060 4006 4029 3986 3718
82 FR-80 7134 7035 7024 7021 6939 7094 6838 7103 6989 6843 6743 6506
83 FR-81 3579 3611 3837 3933 3869 4056 4030 3925 4006 3939 3829 3831
84 FR-82 2398 2591 2590 2823 2858 2932 2935 2926 2978 2940 2827 2829
85 FR-83 10388 10622 10646 10889 10938 11131 10955 11159 11146 11240 10917 11123
86 FR-84 6547 6629 6608 6805 6694 7000 7014 6967 7008 7107 7171 7058
87 FR-85 6874 7062 7299 7589 7647 7629 7718 7601 7442 7436 7164 7070
88 FR-86 4594 4568 4725 4850 4753 4909 4953 5006 4885 4880 4708 4686
89 FR-87 3449 3659 3834 3754 3829 3891 3985 3848 3907 3825 3723 3724
90 FR-88 4291 4264 4310 4416 4274 4215 4252 4057 3883 3715 3796 3679
91 FR-89 3710 3844 3821 3929 3917 4045 3991 3842 3699 3729 3780 3621
92 FR-90 1896 1766 1837 1888 1880 1818 1822 1802 1794 1763 1675 1707
93 FR-91 17122 17614 17753 18281 17932 18134 18040 18509 18493 18506 18510 18903
94 FR-92 24607 24649 24588 25426 24937 25217 25192 25194 25083 24790 24614 24675
95 FR-93 25868 26313 26760 27916 27743 28062 28313 28513 28362 28675 28687 29471
96 FR-94 19637 19866 19947 20948 20331 20736 21022 21391 20991 20967 20748 21566
97 FR-95 17346 17863 18012 19015 18624 18761 18728 19506 19551 19495 19550 19737

Binary file not shown.

View File

@@ -14,7 +14,9 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import gzip
import json
import os
import textwrap
import pandas as pd
@@ -26,7 +28,7 @@ from superset.utils.core import get_or_create_main_db
from .helpers import (
config,
Dash,
get_example_data,
DATA_FOLDER,
get_slice_json,
merge_slice,
Slice,
@@ -37,8 +39,8 @@ from .helpers import (
def load_birth_names():
"""Loading birth name dataset from a zip file in the repo"""
data = get_example_data('birth_names.json.gz')
pdf = pd.read_json(data)
with gzip.open(os.path.join(DATA_FOLDER, 'birth_names.json.gz')) as f:
pdf = pd.read_json(f)
pdf.ds = pd.to_datetime(pdf.ds, unit='ms')
pdf.to_sql(
'birth_names',

Binary file not shown.

View File

@@ -15,6 +15,7 @@
# specific language governing permissions and limitations
# under the License.
import datetime
import os
import pandas as pd
from sqlalchemy import BigInteger, Date, String
@@ -23,7 +24,7 @@ from superset import db
from superset.connectors.sqla.models import SqlMetric
from superset.utils import core as utils
from .helpers import (
get_example_data,
DATA_FOLDER,
get_slice_json,
merge_slice,
misc_dash_slices,
@@ -34,9 +35,8 @@ from .helpers import (
def load_country_map_data():
"""Loading data for map with country map"""
csv_bytes = get_example_data(
'birth_france_data_for_country_map.csv', is_gzip=False, make_bytes=True)
data = pd.read_csv(csv_bytes, encoding='utf-8')
csv_path = os.path.join(DATA_FOLDER, 'birth_france_data_for_country_map.csv')
data = pd.read_csv(csv_path, encoding='utf-8')
data['dttm'] = datetime.datetime.now().date()
data.to_sql( # pylint: disable=no-member
'birth_france_by_region',

Binary file not shown.

View File

@@ -16,6 +16,8 @@
# under the License.
"""Loads datasets, dashboards and slices in a new superset instance"""
# pylint: disable=C,R,W
import gzip
import os
import textwrap
import pandas as pd
@@ -24,16 +26,14 @@ from sqlalchemy import Float, String
from superset import db
from superset.connectors.sqla.models import SqlMetric
from superset.utils import core as utils
from .helpers import (
DATA_FOLDER, get_example_data, merge_slice, misc_dash_slices, Slice, TBL,
)
from .helpers import DATA_FOLDER, merge_slice, misc_dash_slices, Slice, TBL
def load_energy():
"""Loads an energy related dataset to use with sankey and graphs"""
tbl_name = 'energy_usage'
data = get_example_data('energy.json.gz')
pdf = pd.read_json(data)
with gzip.open(os.path.join(DATA_FOLDER, 'energy.json.gz')) as f:
pdf = pd.read_json(f)
pdf.to_sql(
tbl_name,
db.engine,

Binary file not shown.

View File

@@ -14,23 +14,26 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import gzip
import os
import pandas as pd
from sqlalchemy import DateTime
from superset import db
from superset.utils import core as utils
from .helpers import get_example_data, TBL
from .helpers import DATA_FOLDER, TBL
def load_flights():
"""Loading random time series data from a zip file in the repo"""
tbl_name = 'flights'
data = get_example_data('flight_data.csv.gz', make_bytes=True)
pdf = pd.read_csv(data, encoding='latin-1')
with gzip.open(os.path.join(DATA_FOLDER, 'flight_data.csv.gz')) as f:
pdf = pd.read_csv(f, encoding='latin-1')
# Loading airports info to join and get lat/long
airports_bytes = get_example_data('airports.csv.gz', make_bytes=True)
airports = pd.read_csv(airports_bytes, encoding='latin-1')
with gzip.open(os.path.join(DATA_FOLDER, 'airports.csv.gz')) as f:
airports = pd.read_csv(f, encoding='latin-1')
airports = airports.set_index('IATA_CODE')
pdf['ds'] = pdf.YEAR.map(str) + '-0' + pdf.MONTH.map(str) + '-0' + pdf.DAY.map(str)

View File

@@ -16,19 +16,13 @@
# under the License.
"""Loads datasets, dashboards and slices in a new superset instance"""
# pylint: disable=C,R,W
from io import BytesIO
import json
import os
import zlib
import requests
from superset import app, db
from superset.connectors.connector_registry import ConnectorRegistry
from superset.models import core as models
BASE_URL = 'https://github.com/apache-superset/examples-data/blob/master/'
# Shortcuts
DB = models.Database
Slice = models.Slice
@@ -66,12 +60,3 @@ def get_slice_json(defaults, **kwargs):
d = defaults.copy()
d.update(kwargs)
return json.dumps(d, indent=4, sort_keys=True)
def get_example_data(filepath, is_gzip=True, make_bytes=False):
content = requests.get(f'{BASE_URL}{filepath}?raw=true').content
if is_gzip:
content = zlib.decompress(content, zlib.MAX_WBITS|16)
if make_bytes:
content = BytesIO(content)
return content

View File

@@ -15,6 +15,8 @@
# specific language governing permissions and limitations
# under the License.
import datetime
import gzip
import os
import random
import geohash
@@ -24,7 +26,7 @@ from sqlalchemy import DateTime, Float, String
from superset import db
from superset.utils import core as utils
from .helpers import (
get_example_data,
DATA_FOLDER,
get_slice_json,
merge_slice,
misc_dash_slices,
@@ -35,8 +37,8 @@ from .helpers import (
def load_long_lat_data():
"""Loading lat/long data from a csv file in the repo"""
data = get_example_data('san_francisco.csv.gz', make_bytes=True)
pdf = pd.read_csv(data, encoding='utf-8')
with gzip.open(os.path.join(DATA_FOLDER, 'san_francisco.csv.gz')) as f:
pdf = pd.read_csv(f, encoding='utf-8')
start = datetime.datetime.now().replace(
hour=0, minute=0, second=0, microsecond=0)
pdf['datetime'] = [

Binary file not shown.

View File

@@ -14,6 +14,8 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import gzip
import os
import pandas as pd
from sqlalchemy import BigInteger, Date, DateTime, String
@@ -22,7 +24,7 @@ from superset import db
from superset.utils import core as utils
from .helpers import (
config,
get_example_data,
DATA_FOLDER,
get_slice_json,
merge_slice,
misc_dash_slices,
@@ -33,9 +35,8 @@ from .helpers import (
def load_multiformat_time_series():
"""Loading time series data from a zip file in the repo"""
data = get_example_data('multiformat_time_series.json.gz')
pdf = pd.read_json(data)
with gzip.open(os.path.join(DATA_FOLDER, 'multiformat_time_series.json.gz')) as f:
pdf = pd.read_json(f)
pdf.ds = pd.to_datetime(pdf.ds, unit='s')
pdf.ds2 = pd.to_datetime(pdf.ds2, unit='s')
pdf.to_sql(

View File

@@ -14,22 +14,24 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import gzip
import json
import os
import pandas as pd
from sqlalchemy import String, Text
from superset import db
from superset.utils import core as utils
from .helpers import TBL, get_example_data
from .helpers import DATA_FOLDER, TBL
def load_paris_iris_geojson():
tbl_name = 'paris_iris_mapping'
data = get_example_data('paris_iris.json.gz')
df = pd.read_json(data)
df['features'] = df.features.map(json.dumps)
with gzip.open(os.path.join(DATA_FOLDER, 'paris_iris.json.gz')) as f:
df = pd.read_json(f)
df['features'] = df.features.map(json.dumps)
df.to_sql(
tbl_name,

Binary file not shown.

Binary file not shown.

View File

@@ -14,6 +14,8 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import gzip
import os
import pandas as pd
from sqlalchemy import DateTime
@@ -22,7 +24,7 @@ from superset import db
from superset.utils import core as utils
from .helpers import (
config,
get_example_data,
DATA_FOLDER,
get_slice_json,
merge_slice,
Slice,
@@ -32,8 +34,8 @@ from .helpers import (
def load_random_time_series_data():
"""Loading random time series data from a zip file in the repo"""
data = get_example_data('random_time_series.json.gz')
pdf = pd.read_json(data)
with gzip.open(os.path.join(DATA_FOLDER, 'random_time_series.json.gz')) as f:
pdf = pd.read_json(f)
pdf.ds = pd.to_datetime(pdf.ds, unit='s')
pdf.to_sql(
'random_time_series',

Binary file not shown.

Binary file not shown.

View File

@@ -14,22 +14,24 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import gzip
import json
import os
import pandas as pd
from sqlalchemy import BigInteger, Text
from superset import db
from superset.utils import core as utils
from .helpers import TBL, get_example_data
from .helpers import DATA_FOLDER, TBL
def load_sf_population_polygons():
tbl_name = 'sf_population_polygons'
data = get_example_data('sf_population.json.gz')
df = pd.read_json(data)
df['contour'] = df.contour.map(json.dumps)
with gzip.open(os.path.join(DATA_FOLDER, 'sf_population.json.gz')) as f:
df = pd.read_json(f)
df['contour'] = df.contour.map(json.dumps)
df.to_sql(
tbl_name,

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