Compare commits

..

390 Commits

Author SHA1 Message Date
Alanna Scott
acc880c4df [v0.16.1] bump version for prod release (#2250) 2017-02-24 10:56:46 -08:00
Maxime Beauchemin
557b557503 [bugfix] avoid caching errors (#2244) 2017-02-24 10:32:51 -08:00
Bogdan
3018356588 Support more druid postaggregations. (#2235) 2017-02-23 17:14:00 -08:00
Bogdan
ede4dffcb7 Add trailing slash (#2236) 2017-02-23 16:27:59 -08:00
Bogdan
cad392eb76 Fetch schemas separately. (#2227) 2017-02-23 14:02:40 -08:00
Maxime Beauchemin
0296158100 [docs] more specific about python versions 2017-02-23 13:47:15 -08:00
Maxime Beauchemin
b2a4692a02 0.16.0rc3 2017-02-23 08:59:49 -08:00
Maxime Beauchemin
2fbadea9e3 Fixing exploring a table (#2233) 2017-02-23 08:58:27 -08:00
Bogdan
dc05be36a6 Check if the query is in state first. (#2226) 2017-02-22 16:26:17 -08:00
Maxime Beauchemin
dac0d1d0dc 0.16.0rc2 2017-02-22 15:32:26 -08:00
Maxime Beauchemin
459f7160ac Fixing filtering issues (#2223)
* Fixing filtering issues

* Fixing off by one error
2017-02-22 15:17:20 -08:00
vera-liu
aff524d843 **Allow user to put dbname in url (#2209)
**Parse dbid from url to integer
2017-02-22 14:48:14 -08:00
Bogdan
3a91667e92 Update cache for the command line command. (#2213) 2017-02-22 12:06:48 -08:00
Maxime Beauchemin
3e0d3584f7 v0.16.0rc1 2017-02-22 11:39:04 -08:00
Maxime Beauchemin
1e47d6fb41 Renaming field to control (#2210) 2017-02-22 08:36:47 -08:00
Maxime Beauchemin
d5ba88b407 Fixing the CACHING (#2203)
Caching wasn't working after deprecate_v1, this addresses it. Also surfacing
whether the data is served from cache in explore view and a way to force
run the query bypassing the cache.
2017-02-22 08:31:31 -08:00
Maxime Beauchemin
ce1e18b31b Merge pull request #2202 from mistercrunch/clean_cli
[cli] Cleaning up CLI stdout on startup
2017-02-21 14:32:24 -08:00
Maxime Beauchemin
ec84aa7577 Fixing typo 2017-02-21 08:44:55 -08:00
Maxime Beauchemin
8b4d72cf32 Reverting react-select to rc2 2017-02-21 08:43:39 -08:00
Maxime Beauchemin
85e6e65a47 Fixing the build 2017-02-21 08:35:16 -08:00
Maxime Beauchemin
7cad3655f5 Bumping react-select to 1.0.0-rc.3 2017-02-21 00:33:25 -08:00
Maxime Beauchemin
b9e7f292c3 Cleaning up CLI stdout on startup
on startup, FAB spits out a bunch of logging messages that aren't useful
in most cases. This shuts them down by default. They can be turned back
on with `config.SILENCE_FAB = True`

Also shushing a flask-cache warning around setting up a null (default) cache
2017-02-18 12:18:56 -08:00
vera-liu
fc85034c60 Better error handling for presto (#2161)
* Better error handling for presto

* Move to db_engine_spec
2017-02-17 10:29:35 -08:00
Maxime Beauchemin
f5e3d0cc02 [hotfix] incompatible diamond flask-sqlalchemy version 2017-02-16 23:16:35 -08:00
Maxime Beauchemin
fe377e8b94 [hotfix] dashboard won't load, error in fields.js 2017-02-17 07:13:08 +00:00
Maxime Beauchemin
5bb87138e9 [hotfix] Trends example slice is broken 2017-02-16 22:06:23 -08:00
Maxime Beauchemin
579e58206e Bumping up some of the python lib deps 2017-02-16 22:05:29 -08:00
Maxime Beauchemin
172b6ce892 v0.16.0 2017-02-16 17:48:02 -08:00
Maxime Beauchemin
0cc8eff1c3 [WiP] Deprecate Explore v1 (#2064)
* Simplifying the viz interface (#2005)

* Working on dashes

* Making this a collaborative branch

* Fixing some bugs

* Fixing bugs

* More improvements

* Add datasource back in bootstrap data

* Decent state

* Linting

* Moving forward

* Some more linting

* Fix the timer

* Triggering events through state

* Lingint

* Put filters in an array instead of flt strings (#2090)

* Put filters in an array instead of flt strings

* Remove query_filter(), put opChoices into Filter

* Update version_info.json

* Fix migrations

* More renderTrigger=true

* Fixing bugs

* Working on standalone

* getting standalone to work

* Fixed forcedHeight for standalone =view

* Linting

* Get save slice working in v2 (#2106)

* Filter bugfix

* Fixing empty series limit bug

* Fixed dashboard view

* Fixing short urls

* Only allow owners to overwrite slice (#2142)

* Raise exception when date range is wrong

* Only allow owner to overwrite a slice

* Fix tests for deprecate v1 (#2140)

* Fixed tests for control panels container and filters

* Fixed python tests for explorev2

* Fix linting errors

* Add in stop button during slice querying/rendering (#2121)

* Add in stop button during slice querying/rendering

* Abort ajax request on stop

* Adding missing legacy module

* Removing select2.sortable.js because of license

* Allow query to display while slice is loading (#2100)

* Allow query to display while slice is loading

* Put latestQueryFormData in store

* Reorganized query function, got rid of tu[le return values

* Merging migrations

* Wrapping up shortner migration

* Fixing tests

* Add folder creation to syncBackend

* Fixing edit URL in explore view

* Fix look of Stop button

* Adding syntax highlighting to query modal

* Fix cast_form_data and flase checkbox on dash

* Bugfix

* Going deeper

* Fix filtering

* Deleing invalid filters when changing datasource

* Minor adjustments

* Fixing calendar heatmap examples

* Moving edit datasource button to header's right side

* Fixing mapbox example

* Show stack trace when clicking alert

* Adding npm sync-backend command to build instruction

* Bumping up JS dependencies

* rm dep on select2

* Fix py3 urlparse

* rm superset-select2.js

* Improving migration scripts

* Bugfixes on staging

* Fixing Markup viz
2017-02-16 17:28:35 -08:00
Alanna Scott
3b023e5eaa add css to the data object to be saved (#2188) 2017-02-16 10:37:29 -08:00
Maxime Beauchemin
615d8f1624 Moving branding assets to folder 2017-02-15 14:11:47 -08:00
Maxime Beauchemin
b4409ace21 Adding branding assets in the repo 2017-02-15 14:09:14 -08:00
Alanna Scott
dbee6aca1f use pre-wrap for long lines (#2181) 2017-02-15 13:09:17 -08:00
Bogdan
acfe62eaf7 Add command to refresh datasources (#2180) 2017-02-15 10:13:53 -08:00
vera-liu
527a8af060 Return original state for query if query was stopped (#2164) 2017-02-14 10:28:25 -08:00
Benedict Jin
a5a931a670 Fix werkzeug instance was created twice in Debug Mode (#2135) (#2136)
* Fix werkzeug instance was created twice in Debug Mode (#2135)

* add reloader option for flask (#2136)

* using --no-reload option for flask

* divide a line of code into two lines for PEP8
2017-02-13 21:33:59 -08:00
vera-liu
2f05efaf12 Set default time range of query search to the past month (#2162) 2017-02-13 17:16:09 -08:00
vera-liu
83ef8a2e12 Add parsing for nested json objects in resultset (#2163) 2017-02-13 17:15:41 -08:00
Bogdan
c564881867 Implement caching and dynamic data fetching. (#1466)
* Rename rv => o in the decorator.

* Address comments.

* Permissions cleanup: remove none and duplicates. (#1967)

* Updates

* Rename var and dropdown text

* Cleanup

* Resolve comments.

* Add user to the perm check.
2017-02-13 16:14:55 -08:00
vera-liu
b16930f35d Keep order of axis data when storing df (#2092) 2017-02-10 12:54:03 -08:00
Alanna Scott
2d910e3f07 [vis] render line breaks in TableViz (#2118)
* convert line breaks to br tags in table vis

* use css!
2017-02-10 11:51:18 -08:00
Saleh Hindi
daa1420c8e adding tests for #1131 (#1902)
* Add login test

* Add add-slice test

* Fix linting errors

* Use get_resp()helper method instead of self.client.get()

* Fix failing test.

* Add checks for URLs in the tablemodelview

* Fix linting errors

* Address pull request comments

* Address pull request comments

* Remove accidentally added files

* Fix failing test
2017-02-10 01:17:49 -08:00
Benedict Jin
cea310e50b Using the time zone with specific name for querying Druid (#2143)
* Using the time zone with specific name for querying Druid

* Revert DRUID_TZ into tz.tzutc() (#2143)
2017-02-09 23:48:49 -08:00
Alanna Scott
fcdd5c6752 [slices axis] fix axis spacing on dashboard and explore slices (#2145)
* fix axis label size bug and accommodate dual axis chart

* don't adjust margins for bar time series

* handling this below now

* apply different margin padding if explore or dashboard

* fix linting
2017-02-09 21:24:52 -08:00
Alanna Scott
2ace73e9a1 [sql-lab] make datasource name in visualize flow more descriptive (#2103)
* make data source name more descriptive

* add user name to datasource name as well
2017-02-09 12:11:25 -08:00
Alanna Scott
80cfb08794 only call drawGraph once (#2132) 2017-02-09 09:39:08 -08:00
Benedict Jin
1edc2b91cf Fix ExtDeprecationWarning (#2137) (#2138)
* fix ExtDeprecationWarning (#2137)

* remove warnings.simplefilter from cli.py into superset for PEP (#2137)
2017-02-08 11:59:43 -08:00
Benedict Jin
1f58e18b6f Some code refactoring (#2139) 2017-02-08 11:52:58 -08:00
auxsvr
f2bf316058 Add NUMERIC num_type (#2127) 2017-02-07 11:16:52 -08:00
Benedict Jin
9cd38fa1ed little code refactor in models.py (#2124) 2017-02-06 22:19:30 -08:00
Bogdan
edb0111775 Increase query limit to 1M, add separate display limit. (#2111) 2017-02-06 10:35:54 -08:00
Benjamin Yolken
de4f9e8d1a Merge pull request #2113 from airbnb/byolken/s3_cache_implementation
Add implementation of S3Cache
2017-02-03 23:10:57 -08:00
Benjamin Yolken
461e41cd61 Use BytesIO instead of StringIO for python2/3 compatibility 2017-02-03 22:36:34 -08:00
Benjamin Yolken
716406198e Clean up imports of cPickle and StringIO 2017-02-03 21:50:04 -08:00
Benjamin Yolken
68592aeddf Fix StringIO import in results_backends module 2017-02-03 21:43:31 -08:00
Benjamin Yolken
b927ff6eef Fix indentation errors in results_backends module 2017-02-03 21:28:42 -08:00
Benjamin Yolken
ce50e6e4fe Fix python3 cPickle import errors 2017-02-03 21:27:21 -08:00
Benjamin Yolken
167ed33bba Fix name of test in results_backends_tests module 2017-02-03 16:29:28 -08:00
Benjamin Yolken
0ee1abf31a Misc. fixes in response to code review feedback 2017-02-03 16:21:33 -08:00
Benjamin Yolken
6a0a1af67e Fix misc. style issues 2017-02-03 15:59:18 -08:00
Benjamin Yolken
f85481d51b Fix long lines in superset/results_backends.py 2017-02-03 15:48:33 -08:00
Benjamin Yolken
00b6b0ac68 Misc. style tweaks to S3Cache changes and tests 2017-02-03 15:43:43 -08:00
Benjamin Yolken
1546b1ae71 Add tests for S3Cache 2017-02-03 15:40:18 -08:00
Benjamin Yolken
1e94498d9d Add initial implementation of S3Cache 2017-02-03 13:32:23 -08:00
Bogdan
0f7189b859 Do not fail is the filter cannot be parsed. (#2105) 2017-02-03 10:37:32 -08:00
Bogdan
a6e0f1b75a Add an option to configure celery workers size. (#2085) 2017-02-03 09:37:08 -08:00
Alanna Scott
543c22bb50 [dashboard] fix nvd3 tooltips (#2096)
* hide tooltips on scroll

* hide tooltips on render/re-render of the chart

* move function above vis function, fixes linter
2017-02-01 19:16:52 -08:00
Bogdan
07e067cf0b Revert "Bump version to 0.15.4.1" (#2095) 2017-02-01 17:24:05 -08:00
Bogdan
6c256a34a9 Bump version to 0.15.4.1 (#2094) 2017-02-01 17:17:21 -08:00
Bogdan
6b2eb04a73 Put back a default count * metric (#2091) 2017-02-01 16:53:26 -08:00
Bogdan
898d80ba38 Viz the compiled query rather than user input. (#2086) 2017-01-31 19:56:59 -08:00
Bogdan
ea8e4ad05b Display all columns if none are specified. (#2077)
* Display all columns if none are specified.

* Update models.py

* Do not use column for the time series.

* Update models.py

* Update config.py
2017-01-31 17:50:53 -08:00
vera-liu
27aeac6859 Remove fetch results button for async queries (#2084) 2017-01-31 17:25:06 -08:00
vera-liu
8da371e324 Make show query button work for v1 (#2080) 2017-01-31 14:47:50 -08:00
vera-liu
0c59fe933d Only call topn when having_filters don't exist (#2075) 2017-01-31 13:59:44 -08:00
Alanna Scott
e169c67760 [vis] fix axis labels display (#2066)
* re-render chart after adjusting for long axis labels

* measure all of the labels and take the max height

* add missing isTimeSeries var

* fix linting

* use jQuery to get text ticks

* rotate 45 rather than 90
2017-01-31 11:15:59 -08:00
vera-liu
3a5a927dc6 check if tempTable exists for ctas queries (#2073) 2017-01-30 17:08:52 -08:00
vera-liu
2d419e4253 Return alert instead of fetch button when async results has no data (#2072) 2017-01-30 11:03:37 -08:00
vera-liu
87869a29c9 Customize tooltip with axis format (#2068) 2017-01-30 10:43:11 -08:00
vera-liu
544211f5ec Revert "Display no data alert when async result has zero rows" (#2069) 2017-01-27 15:31:28 -08:00
Bogdan
f6ac95e2dd Convert objects to json (#2050) 2017-01-27 13:33:54 -08:00
vera-liu
63bef2f844 [bugfix] only pop slice_id when it exists in url (#2065) 2017-01-27 11:07:34 -08:00
vera-liu
4a8cd04de6 Display no data when async result has zero rows (#2055) 2017-01-27 11:06:54 -08:00
vera-liu
85806624db Use a key-value store model for sharing long queries (#1951)
* Add KeyValue model for storing id-value pairs
use it for storing shared queries

* Change string to text and added test

* Put getQueryLink in one place

* Changed migration down version

* Changes based on comments

* Update bcf3126872fc_add_keyvalue.py
2017-01-27 10:20:24 -08:00
Bogdan
1ac2273984 Reimplement has_access. (#2028) 2017-01-26 12:13:56 -08:00
vera-liu
a8c29c4ffe Change validator of timeshift to allow for strings (#2051) 2017-01-26 09:55:47 -08:00
Dylan J. Sather
31af01c4f2 Splitting dev-reqs.txt into requirements for development and docs (dev-reqs-for-docs.txt). Updating CONTRIBUTING.md accordingly (#2049) 2017-01-26 09:07:23 -08:00
Bogdan
b1bba96d04 Fix csv download. (#2036) 2017-01-25 18:06:29 -08:00
Bogdan
c5c730224e Check datasource level perms for downloading csv and fetching results (#2032)
* Check datasource level perms for downloading csv and fetching results

* Add index on the query table on the result key column
2017-01-25 16:02:26 -08:00
Emanuele Cesena
7441cf7d39 Fix inner query labels for Vertica (#2041)
In Vertica, inner queries require different aliases than the main query.

This is an example of query generated before this patch:
SELECT chain AS chain,
                weekstartday AS __timestamp,
                SUM(inventory) AS "Inventory"
FROM mytable
JOIN
  (SELECT chain AS chain__,
                   SUM(inventory) AS "Inventory"
   FROM mytable
   WHERE weekstartday >= '2016-01-24 00:00:00'
     AND weekstartday <= '2017-01-17 00:00:00'
   GROUP BY chain
   ORDER BY "Inventory" DESC LIMIT 50) AS anon_1 ON chain = chain__
WHERE weekstartday >= '2016-01-24 00:00:00'
  AND weekstartday <= '2017-01-17 00:00:00'
GROUP BY chain,
         weekstartday
ORDER BY "Inventory" DESC LIMIT 50000

Which in Vertica produces the error:
Error: ('42702', '[42702] ERROR 2671: Column reference "inventory" is ambiguous\n (2671) (SQLExecDirectW)')


And this is the same example after the patch:
SELECT chain AS chain,
                weekstartday AS __timestamp,
                SUM(inventory) AS "Inventory"
FROM mytable
JOIN
  (SELECT chain AS chain__,
                   SUM(inventory) AS mme_inner__
   FROM mytable
   WHERE weekstartday >= '2016-01-24 00:00:00'
     AND weekstartday <= '2017-01-17 00:00:00'
   GROUP BY chain
   ORDER BY mme_inner__ DESC LIMIT 50) AS anon_1 ON chain = chain__
WHERE weekstartday >= '2016-01-24 00:00:00'
  AND weekstartday <= '2017-01-17 00:00:00'
GROUP BY chain,
         weekstartday
ORDER BY "Inventory" DESC LIMIT 50000

Related PR:
19f5371787
2017-01-25 12:12:30 -08:00
Dylan J. Sather
45c72d25df New administrator tutorial (#2046)
* New Tutorial for Superset Admins

* Removing old images from tutorial, adding new
2017-01-25 11:44:02 -08:00
Dylan J. Sather
3fff631b32 Expanded on documentation section, running through an example of committing a change to the docs end-to-end and describing the process for adding static assets (#2047) 2017-01-25 11:41:39 -08:00
Riccardo Magliocchetti
bfa2891b23 models: add real to numeric types (#2044)
Fix #2010
2017-01-25 07:41:28 -08:00
vera-liu
5715f52fef [hotfix] delete DAR when datasource requested does not exist anymore (#2040) 2017-01-24 21:49:02 -08:00
Emanuele Cesena
1f2126f463 Fix Druid granularity timeZone (#2037)
timezone -> timeZone
2017-01-24 21:08:13 -08:00
vera-liu
27ed0b37bf Cleanup fulfilled requests after approve (#1953)
* Cleanup fulfilled requests after approve

* Modified tests

* Moved to separate test, add user to access functions

* Moved to separate test and added test cases

* Fixed issue with dryrun

* More changes based on comments
2017-01-24 18:11:51 -08:00
Bogdan
cdbd2f8507 Guess the filter value type (#1978)
* Guess the filter value type

* Require quotes

* Use column type
2017-01-24 14:35:39 -08:00
Maxime Beauchemin
e46ba2b4a4 Simplifying the viz interface (#2005) 2017-01-24 14:03:17 -08:00
vera-liu
1c338ba742 [WIP] [explorev2] Refactor filter into FieldSet (#1981)
* [explorev2] Refactor filter into FieldSet

* Fixed tests

* Added tests

* Modifications based on comments
2017-01-24 13:32:40 -08:00
Maxime Beauchemin
2b7673ad5d Fixing pypi_push.sh 2017-01-24 11:42:49 -08:00
Maxime Beauchemin
2f27353015 v0.15.4 2017-01-24 11:33:11 -08:00
Wyndham Blanton
1b8c3f420a avoid py3 error in setup.py (#2030)
print("GIT SHA: " + GIT_SHA)
TypeError: Can't convert 'NoneType' object to str implicitly
2017-01-24 11:31:05 -08:00
Emanuele Cesena
a3a070855c Use dist instead of src in mapbox (#2027)
I had issues compiling the src, I think it's because the syntax is different than the one used here?
With the dist it works fine.

I also saw there were other related issues in the past, so I'm suggesting to switch to the dist file.
2017-01-24 10:10:35 -08:00
Maxime Beauchemin
e84c6393b8 Correcting docs to run npm build instead of prod 2017-01-24 08:36:19 -08:00
Maxime Beauchemin
7413dd9f4b v0.15.3 2017-01-24 08:10:56 -08:00
Alanna Scott
9cbd667eb7 [explore-v2] Fix edit datasource link for druid datasources (#1982)
* only add imgSrc key if choices contain an imgSrc

* handle table and druid edit links for datasource drop down

* fix linting
2017-01-23 10:08:00 -08:00
alanmcruickshank
37fb56c61c Week beginning Monday time grain for MySQL (#2014) 2017-01-22 16:51:49 -08:00
Maxime Beauchemin
404a94cadb [hotfix] fixing the hotfix 2017-01-19 17:04:12 -08:00
Maxime Beauchemin
0807a8d016 [hotfix] load selectors in render 2017-01-19 15:27:01 -08:00
Silas H
4a9888157e Update INTHEWILD.md (#2000)
Adding Brilliant.org.
2017-01-19 09:44:51 -08:00
Jun Jiang
83fbdcceac Add Qunar to INTHEWILD (#2001) 2017-01-19 09:43:44 -08:00
robert-digit
b070ef5fdb added Digit to inthewild (#1997) 2017-01-18 08:22:40 -08:00
Maxime Beauchemin
7d380dcd14 Adding Clark.de and Yahoo to INTHEWILD 2017-01-17 09:16:15 -08:00
Maxime Beauchemin
a15dbd992d Adding Clark.de to INTHEWILD 2017-01-17 09:15:37 -08:00
Noppanit Charassinvichai
52c5d235af Add analysisTypes to refresh druid (#1983) 2017-01-14 16:00:34 -08:00
Bogdan
495f6460a4 Add email functionality (#1914)
* Add email functionality

* Add email templates.

* Test notifications

* Move email to utils
2017-01-13 19:30:17 -08:00
Maxime Beauchemin
a96024d0e7 [explorev2] Fields can validate input and handle errors (#1980)
As a result here, TextField does its own validation and now casts
the values it sends to Int or Float if set to do so.
2017-01-13 16:25:37 -08:00
Maxime Beauchemin
99b84d2909 Reverting CLI changes in #1713 (#1964) 2017-01-13 12:37:35 -08:00
Bogdan
24728b8b47 Permissions cleanup: remove none and duplicates. (#1967) 2017-01-13 09:55:45 -08:00
Jun Jiang
9750e49df8 Add the missing argument (#1969) 2017-01-13 08:38:08 -08:00
alanna scott
bf31783d0c v0.15.2 2017-01-12 23:08:59 -08:00
Jun Jiang
87eacf88c3 fix timestamp error in table view (#1960) 2017-01-12 17:05:34 -08:00
vera-liu
1dbfb99ead Leave metrics empty if not specified (#1965) 2017-01-12 15:03:46 -08:00
Maxime Beauchemin
ff4020ea73 [explorev2] using label in 'Visualization Type' Select instead of key (#1927)
* [explorev2] using label in 'Visualization Type' Select

* moved url related logic upstream in the caller
* moved option creation logic from render method to the constructor as
  it only needs to be executed once

* Refactoring out getOptions
2017-01-12 12:32:42 -08:00
Maxime Beauchemin
0ce7fc18a8 Adding a way to see the git SHA from the website (#1956)
* Adding a way to see the git SHA from the website

* Fixing py3 bug
2017-01-12 12:32:06 -08:00
Maxime Beauchemin
470a6e9d76 [explorev2] adding support for client side validators on controls (#1920)
* Adding support for client side validators on controls

* Applying validators to more fields

* Addressing comments
2017-01-12 09:21:17 -08:00
Alanna Scott
fc74fbeeaa [explore-v2] make control panel sections and fields more dense (#1954)
* make control panel sections and fields more dense

* remove Panel

* use <Panel> with className prop
2017-01-11 21:57:36 -08:00
Bogdan
9c6a5793b9 Fix none view_menues. (#1950) 2017-01-11 14:03:10 -08:00
vera-liu
49b6b38741 Pass query instead of slice to Action buttons to prevent lagging query (#1948)
* Pass query instead of slice to Action buttons to prevent lagging query

* Delete beforeOpen and put DisplayQueryButton in pure component
2017-01-11 12:29:06 -08:00
vera-liu
a385ee9e97 Use POST in sqllab_viz instead of url params to avoid error with long queries (#1933)
* Use POST in sqllab_viz instead of url params to avoid error with long queries

* Delete error handling

* Fix returning statement
2017-01-11 11:58:16 -08:00
vera-liu
f0917c62f2 Add a Async Select that fetches options from given endpoint (#1909)
* Add a Async Select that fetches options from given endpoint

* update it statement
2017-01-11 10:31:30 -08:00
vera-liu
94d20168da Change fields for dual_line to match with new SelectField structure (#1932) 2017-01-11 10:07:30 -08:00
Maxime Beauchemin
2d866e3ffa [hotfix] fix the logging fix that broke the build (#1940)
* [hotfix] fix the logging fix that broke the build

* Fixing tests
2017-01-11 07:53:24 -08:00
Alanna Scott
5d94d7067e [explore-v2] add edit link below datasource select (#1919)
* add edit link below datasource select

* add default prop
2017-01-10 21:13:25 -08:00
Jun Jiang
7323f4c2ab Make up the user link string (#1947)
prevent the user link from being escaped
2017-01-10 21:10:41 -08:00
Patrick Leo Tardif
eca6dfef6a switch order of period compare and rolling periods (#1946) 2017-01-10 21:09:02 -08:00
vera-liu
761462ef93 Revert "#views users for created dashboards on profile page" (#1943)
* Revert "#views users for created dashboards on profile page"

* Change the downversion after after-version

* Update models.py
2017-01-10 16:58:19 -08:00
mobcdi
98e83255e6 Added extra details around setting up admin user (#1937)
after running create-admin the user will be prompted to provide extra details and set the admin password
2017-01-10 09:06:38 -08:00
Saleh Hindi
cbf3562a6f Fix double scrollbar in pivot table (UI bug) (#1931) 2017-01-09 23:11:34 -08:00
Riccardo Magliocchetti
a2c41bbace viz: hotfix for saving in cache (#1922)
Fix cache set after c14c7ed update json serializer behaviour
but forget to update one call site.

While at it move payload serialization outside of catch all try
block since we want to see explosions if any.

Fix #1910
2017-01-09 16:27:01 -08:00
Maxime Beauchemin
2a12a3c702 [hotfix] logging is down 2017-01-09 15:40:07 -08:00
Bogdan
2ab6a411f4 Druid dashboard import/export. (#1930)
* Druid dashboard import

* Fix tests.

* Parse params with trailing commas

* Resolved issue with datasource is not in DB yet when slice perms are set

* Finish rebase

* Fix heads alembic problem.
2017-01-09 10:02:03 -08:00
Maxime Beauchemin
14ed10bdb0 Fixing docs generation 2017-01-08 07:48:02 -08:00
Maxime Beauchemin
49e6fd5bfb Revert "Druid dashboard import/export. " (#1923) 2017-01-07 14:02:13 -08:00
Bogdan
af872fa4d4 Druid dashboard import/export. (#1811)
* Druid dashboard import

* Fix tests.

* Parse params with trailing commas

* Resolved issue with datasource is not in DB yet when slice perms are set

* Finish rebase
2017-01-06 16:48:21 -08:00
vera-liu
cec4cf014c #views users for created dashboards on profile page (#1667)
* Add #views and #distinct users to created dashboard on profile page

* Added index on logs to speed up query

* Added #views and #users for slice table

* Add a views column to dashboards and slices, prepopulate
them with Log data

* Remove index on Log model

* Remove unused index

* Update 1b2c3f7c96f9_.py

fix multiple heads

* Exclude postgres in prepopulating views column
2017-01-06 16:31:20 -08:00
vera-liu
783ad703d0 [hotfix] delete ipdb breakpoint (#1917) 2017-01-06 13:48:01 -08:00
Maxime Beauchemin
222671675c [exploreV2] mapStateToProps for fields (#1882)
* Controls support for mapStateToProps

* Binding methods in the constructor

* Adressing comments

* Fixing tests
2017-01-06 12:38:44 -08:00
Maxime Beauchemin
9a62d94630 [sqllab] bugfix visualizing a query with a semi-colon (#1869)
* [sqllab] bugfix visualizing a query with a semi-colon

* Fixing tests
2017-01-06 12:24:07 -08:00
vera-liu
c3edc6e24b [WIP] Add dual-axis line chart to viz (#1782)
* Add dual-axis line chart to viz
 - Add a new viz using two y axis for multi-chart in nvd3
 - Option to set metric and axis format for two y axis

* Resolve conflicts

* Fixed x axis and resized thumbnail

* Added example to __init__.py

* Change get_df to match with format
2017-01-06 10:30:27 -08:00
Maxime Beauchemin
119b0c55e9 [explore] fix height in embed mode (#1898)
* [explore] fix height in embed mode

* Linting
2017-01-06 09:41:53 -08:00
Maxime Beauchemin
c14c7edc5e [explore] show the broken query when failing (#1871)
* Return query when failing

* Linting

* sjson -> simplejson
2017-01-05 10:00:39 -08:00
Riccardo Magliocchetti
e3b296c558 utils: teach our json serializer to handle more types (#1907)
Namely datetime.time and numpy.bool_

Refs: #1900
Refs: #1903
2017-01-05 09:56:07 -08:00
Maxime Beauchemin
c2d29fb54b Change ordering of fields when adding a table (#1899) 2017-01-04 17:36:37 -08:00
Maxime Beauchemin
7aab8b0ae3 Simplifying the Fields (Controls) interface (#1868)
* Simplifying the Field interface / logic

* Moving ControlLabelWithTooltip where it belongs

* Progress

* Rename FieldClass->FieldType
2017-01-04 15:46:52 -08:00
andreamelloncelli
861a3bd4ae docs: 8088 is the default port, no need to specify it (#1861)
* docs: 8088 is the default port, no need to specify it

* docs: hint to use a non default port
2017-01-04 14:43:56 -08:00
vera-liu
9bc7ad9cd5 Do not use persistState for explorev2 (#1894)
* Do not use persistState for explorev2

* Change enhancerWithPersistState to enhancer and function name to initEnhancer
2017-01-03 20:23:43 -08:00
Maxime Beauchemin
a1e3fc1c23 [explorev2] giving more room for long textboxes (#1881) 2017-01-03 18:06:49 -08:00
Alanna Scott
242869db3a [sql lab] only show single run query button (#1858)
* only show single run query button, allow async if possible

* only pass the needed props, rather than entire objects to the component

* add simple test

* fix linting
2017-01-03 17:20:30 -08:00
Maxime Beauchemin
8924bb79e7 [explorev2] moving the "Time" section up to 2nd section (#1885)
This keeps the same section ordering as in v1.
2017-01-03 14:18:55 -08:00
David Moodie
a0d103dac3 Fix small typo (#1888) 2016-12-29 17:51:25 -08:00
Maxime Beauchemin
d52b299df8 Updating CHANGELOG 2016-12-28 14:02:26 -08:00
Maxime Beauchemin
092432f04f v0.15.1 2016-12-28 13:29:17 -08:00
willgroves
ea8e6634d6 read anon user role from config, remove reference to public role (#1878)
* read anon user role from configuration, add anon user role as 'Public' to testing configuration

* apply suggestions
2016-12-27 14:30:01 -08:00
Dongkyu Hwangbo
3e6f90cf72 Upgrading pydruid version and adopt 'merge' flag during refresh_druid operation (#1879)
* Initial

* rewrite some line to make it short and setting merge variable temporarily

* rewrite commit author

* add emitted attribute

* Fix typo

* fix test error

* fix typo

* test added
2016-12-27 14:27:55 -08:00
Maxime Beauchemin
16731056ed [sqllab] async queries - better error handling (#1853) 2016-12-19 22:33:55 -08:00
Maxime Beauchemin
0712894353 Improving database logging by adding duration, referrer and post data (#1830)
* Improving logging with duration and referrer

* Handling case where referrer is None

* Providing log_this with its own session

* Attempting to fix tests

* Fixing tests
2016-12-19 22:32:50 -08:00
Riccardo Magliocchetti
36fad803ed sqllab: don't hold database deletion because of query reference (#1863)
Let people delete the database even if there are sqllab queries
on that database. Instead delete them too.

Fix #1848
2016-12-18 17:12:23 -08:00
vera-liu
6732f01cb7 Enable freeform-select with fetched column values for filter values (#1697)
* Enable freeform-select with fetched column values for filter values
 - db migration to add filter_select_enabled
 - add freeform-multi option for Selectfield
 - modify formatFilter() function on query to accomodate filter-select

* Fix js tests

* Fix codeclimate issue

* Changes based on comments

* Add test for filter endpoint

* Extract out renderFilterFormField function from render

* Fix landscape issues
2016-12-16 14:23:48 -08:00
szmate1618
bb04e6fcfa Use APP_ICON in template (#1855)
* use APP_ICON in template

* use APP_NAME in navbar template
2016-12-16 08:56:26 -08:00
Maxime Beauchemin
007ee88d33 [explorev2] improving the scrolling/scrollbars placement (#1840) 2016-12-16 08:06:40 -08:00
vera-liu
7a5bb94754 Stop ChartContainer from rendering twice on chartStatus change (#1828)
* Stop ChartContainer from rendering twice on chartStatus change

* Make spinner overlay and dim chart while laoding

* Added timeout to make render more stable on resize

* Put viz in state and call resize at browser size change

* add render after height change since resize depends on render called on the same object

* Change name of renderVis to renderVisOnChange

* Only call resize at height change, persist viz in state

* Call resize wihout render at window size change
2016-12-15 14:06:54 -08:00
vera-liu
e06a0cd89b Add force_ctas_schema to query model when enabled (#1825)
* Add force_ctas_schema to query model when enabled

* Add schema to temp_table_name

* Remove extra arg in create_table_as
2016-12-15 13:19:54 -08:00
Maxime Beauchemin
b6cba13293 [explorev2] enabling redux dev tools (#1842) 2016-12-15 08:54:02 -08:00
Maxime Beauchemin
d929bbfe30 [explorev2] making QueryAndSaveBtns disabled while running queries (#1841)
As I altered QueryAndSaveBtns to add the `disabled` prop
I also moved it to using react-boostrap
2016-12-15 08:53:33 -08:00
Maxime Beauchemin
bf67d64708 [explorev2] making Datasource an Viz controls not clearable (#1845)
* [explorev2] making Datasource an Viz controls not clearable

* Making choices default to empty list
2016-12-15 08:53:15 -08:00
Bogdan
92aa1a6124 Permissions refactoring, optimizations and unit testing. (#1798)
* Refactor and speed up superset init

* Add unit tests.

* Test fixes.

* More test updates.

* Fix read only perms

* Address comments.
2016-12-15 08:38:34 -05:00
Maxime Beauchemin
733ab8014b [explorev2] using a loader to load the explorev2 specific css (#1843) 2016-12-14 18:31:50 -08:00
Alexander Mancevice
6aaa49f0bf Change default gunicorn address (#1838)
Use `0.0.0.0` as default instead of `127.0.0.1`
2016-12-14 16:34:35 -08:00
vera-liu
638f27c2df [sqllab] Fix sql expression bug with count distinct metrics (#1805)
* Return error message when metrics are not valid

* Fix bug with count distinct expression

* Fixed codeclimate issue
2016-12-14 16:09:22 -08:00
Maxime Beauchemin
84a3b55912 [explorev2] remove unused file SqlClause.jsx (#1839) 2016-12-14 15:17:45 -08:00
Maxime Beauchemin
552d46479b [explorev2] no bootstrap data, just metadata in exploreV2 (#1827)
json_data really just returns the *metadata* for the slice, where
get_json returns both the metadata and the data
2016-12-14 14:42:14 -08:00
vera-liu
fa9c066ffe Add email-to option in action buttons for dashboard and slice (#1705)
* Add email-to option in explore action buttons

* email to option for dashboard
2016-12-14 11:52:53 -08:00
vera-liu
e1e20b8757 Sort searched queries by recency (#1735)
* Sort searched queries by recency

* Fixed sqllab tests

* Add one more tr to QueryTable spec for pagination

* Change desc to asc as we reverse queries in component
2016-12-14 10:14:54 -08:00
Rossouw Minnaar
2fb94a89e2 Add ADDITIONAL_MIDDLEWARE option to config (#1832)
Add documentation to explain ADDITIONAL_MIDDLEWARE
2016-12-14 09:39:59 -08:00
rlei
7a9604a3c9 Workaround for slices "Not Found" issue in IE <= 11 (#1821)
This should fix issue #1339.

IE 11 and lower has a long standing issue: out-of-document element's
pathname has no leading '/'. See

https://connect.microsoft.com/IE/feedbackdetail/view/1002846/pathname-incorrect-for-out-of-document-elements

And Superset's Slice.jsonEndpoint() method relies on pathname() to build
JSON API URL for slices:

```javascript
      jsonEndpoint() {
        const parser = document.createElement('a');
        parser.href = data.json_endpoint;
        let endpoint = parser.pathname + this.querystring();
        endpoint += '&json=true';
        endpoint += '&force=' + this.force;
        return endpoint;
      },
```

`parser` above is exactly an out-of-document element. Therefore when
running in IE <= 11, Superset would build wrong JSON endpoint URLs,
hence the 404 errors for loading data for slices.

This commit adds a simple workaround when leading '/' is missing in the
value returned by pathname().
2016-12-14 08:41:03 -08:00
Maxime Beauchemin
e099088012 [hotfix] fixing the build 2016-12-13 16:05:05 -08:00
Alanna Scott
34e107e7d3 [explore-v2] add config option for explore v2 beta users, and send through v2 path (#1671)
* add config option for explore v2 beta users, and send through v2 path

* fix long lines

* use role rather than user ids in config

* add test

* simplify role check

* remove extra  line

* use different test user for v2 tests
2016-12-12 16:42:38 -08:00
Maxime Beauchemin
2254a4d0b4 v0.15.0 2016-12-12 11:30:11 -08:00
vera-liu
9f7486f402 remove extra call to get_viz in explorev2 (#1812)
* Not working errors

* Remove update_explore endpoint, only update explore endpoints in reducer on query

* Change scroll to auto and make container reponse to height:

* Remove update_explore endpoint

* Remove can_update_explore perm
2016-12-12 10:58:07 -08:00
vera-liu
699602d1c5 Add tooltips to RunAsync and CTAS button (#1792)
* Add tooltips to RunAsync and CTAS button

* Use button from components

* Phrasing
2016-12-12 10:13:01 -08:00
Daniel Darabos
2993ff1d75 Add NVD3's bullet chart (#1775)
* Add NVD3's bullet chart.

* Add empty lines before nested function definitions.

* Add thumbnail for bullet chart.

* Add bullet chart to gallery.rst.

* Add "requiresTime: false", fix indentation.

* Avoid scaling bullet chart vertically.

* Use a default if no range is specified.

* Fix coloring of bullet chart.
2016-12-12 08:58:13 -08:00
Maxime Beauchemin
afb3c24d5a Showing more fields in DatabaseView 2016-12-10 08:06:58 -08:00
vera-liu
8ef730b5fe Added timer to explore v2 and share it with sqllab (#1802)
* Added timer to explore v2 and share it with sqllab

* Fixed js tests

* Add timer to initial load

* Make timer smaller

* nits
2016-12-09 13:39:53 -08:00
vera-liu
866cfe5279 Add schema name to output column in query history (#1790)
* Add schema name to output column in query history

* Replace line break with dot between schema and table name
2016-12-09 11:03:31 -08:00
Maxime Beauchemin
68c2eab6b9 [hotfix] handling 0% change in big number with trendline (#1801) 2016-12-08 12:59:35 -08:00
Maxime Beauchemin
aeda5bd260 [sqllab] config item for SQLLAB_DEFAULT_DBID (#1793) 2016-12-07 21:01:02 -08:00
vera-liu
a95cd71456 Add viz thumbnails to viz_type select (#1794)
* Add viz thumbnails to viz_type select

* Replace alt with value
2016-12-07 15:59:17 -08:00
Alanna Scott
34d0dd9d6e adjust header nav links so they are all aligned on the base line (#1786) 2016-12-07 11:24:54 -08:00
Alanna Scott
401d9afd54 [ui] update logo, favicon, and new primary color (#1781)
* update favicon image

* change primary color

* update logo in header

* update logo in readme
2016-12-06 20:59:44 -08:00
vera-liu
74edb936a5 [WIP] Add http to copied url and move function to componentWillReceiveProps (#1780)
* Add http to copied url and move function to componentWillReceiveProps

* Added getText() to CopyToClipbaord to enable ajax calls for getting copy text

* Set ajax call to synchronous (document.execCommand only works in synchronous mode
2016-12-06 17:49:41 -08:00
Maxime Beauchemin
c1558578d7 [explorev2] Breaking down large files, fixing JS warnings (#1773)
* Breaking down large files, fixing JS warnings

* fix unit tests
2016-12-06 14:39:30 -08:00
Bogdan
3597fdb7f8 Filter table list based on the user permissions. (#1769)
* Filter table list based on the user permissions.

* Fix tests
2016-12-06 02:18:16 -05:00
vera-liu
43f2a379a1 Make cell-click filter in table viz optional (#1762)
* Make cell-click filter in table viz optional
 - Added table_filter checkbox in table viz
 - If set to false, clicking on a cell in table will not apply filter to
   dashboard

* Fix codeclimate issue

* Change default to false
2016-12-05 17:17:31 -08:00
Bogdan
69702e3a19 Create users if not found. (#1753)
* Create users if not found.

* Do not fail is user is a duplicate.

* Make endpoint faster.
2016-12-05 20:03:40 -05:00
vera-liu
eb0655cf85 [sqllab] Fixed js error when results are not available (#1715)
* Fixed js error when results are not available

* Flush data and query in results when running new query, keeping columns

* add exception for columns

* Move setState from componentWillMount to componentWillReceiveProps

* Nit
2016-12-05 13:36:18 -08:00
vera-liu
d8864bc92b Enable overwrite sql in QueryHistory (#1731) 2016-12-05 11:34:42 -08:00
vera-liu
89fc9d7c80 Make entire menuitem clickable for copy query (#1747) 2016-12-05 10:02:23 -08:00
vera-liu
76aa9f7e10 [explorev2] fix textfield and druid bug (#1732)
* Fixed bug with textfield being empty

* Only return time grains for sqla table
2016-12-05 09:51:59 -08:00
Riccardo Magliocchetti
abd0974897 Fix superset cli for python3 (#1760)
* Fix superset cli for python3

dict.iteritems() has been removed since dict.items() returns an
iterable in python3. Shouldn't be a big deal for python2 to load
all the data into a list.

Fix #1756

* bin/superset: avoid some work when reading config

We don't need to unpack and then pack again a dictionary.
2016-12-04 07:40:56 -08:00
Maxime Beauchemin
c4e943a24f [sqllab] making 'click to retrieve results' a button (#1737) 2016-12-03 20:09:09 -08:00
Maxime Beauchemin
a3106bcb3d [bugfix] bignumber comparison wrong with neg values (#1743)
* [bugfix] bignumber comparison wrong with neg values

* Handling zero div
2016-12-03 20:05:43 -08:00
David Dolphin
b045075a96 Sankey Tooltip fix (#1748) (#1750)
.layer{X,Y} gives viewport offset, moved to node offset using .offset{X,Y}
2016-12-02 22:14:39 -08:00
Bogdan
09d597f3ad Prevent duplicated view_menu perms (#1751) 2016-12-02 17:48:46 -05:00
Bogdan
9d4c3d83d0 Update role based on usernames not emails. (#1749) 2016-12-02 16:30:21 -05:00
Maxime Beauchemin
95580a004f [explorev2] cosmetic, smaller size for input text (#1746) 2016-12-02 10:44:16 -08:00
Maxime Beauchemin
723f90755e Fixing the sourcemap in dev mode (#1744) 2016-12-02 10:43:58 -08:00
Maxime Beauchemin
324205f77a [sqllab] bugfix where a query has the same alias twice as output (#1734) 2016-12-01 19:53:23 -08:00
Benjamin Yolken
0a40d8ce8f Rremove unused symlinks (#1736) 2016-12-01 19:52:46 -08:00
Bogdan
168a25239e State that npm should be between 3.9 and 4 2016-12-01 16:48:56 -08:00
Maxime Beauchemin
7eef46e941 Adding links pointing to the new user profile page (#1704)
* Adding links pointing to the new user profile page

* Raising coverage

* Fixing tests
2016-12-01 15:21:18 -08:00
Benjamin Yolken
50da4f8c07 Support running superset via pex (#1713)
* Support running superset via pex

* [superset] Update default port in superset/bin/superset

* Fix codeclimate line length issues

* Fix another line length issue, in config.py

* Add trivial utils test to increase test coverage

* Clean up runserver handling
2016-12-01 15:18:55 -08:00
vera-liu
2d0ebeae1b [explorev2] Make chart container more responsive (#1724)
* Make chart container more responsive
 **Leave chart empty when theres error
 **Make all boolean fields auto-query chart when changed

* Replace forEach with some

* Added fields to autoQueryFields
2016-12-01 11:59:44 -08:00
Bogdan
1a16491971 Display full table name (schema + name) if possible. (#1728) 2016-12-01 14:57:39 -05:00
vera-liu
7f4f250970 Redirects to login page if user not logged in at welcome page (#1723) 2016-11-30 17:41:02 -08:00
Bogdan
25acb78071 Pass schema to the select star query. (#1714) 2016-11-30 19:33:43 -05:00
Bogdan
e822d5a1b7 Make edit / add / delete perms available to all users. (#1722)
* Make edit / add / delete perms available to all users.

* Add tests and restrict from editing the datasources.
2016-11-30 17:05:09 -05:00
vera-liu
32fc0ff6d0 [Bugfix] autocomplete in sqleditor doesnot use newly loaded table columns (#1712) 2016-11-30 09:09:35 -08:00
Maxime Beauchemin
94dde075b3 v0.14.1 2016-11-29 15:57:05 -08:00
Bogdan
65e92327ab Druid hotfix. (#1710) 2016-11-29 18:11:59 -05:00
Maxime Beauchemin
0be02e67a5 Updating CHANGELOG 0.14.0 2016-11-29 15:10:21 -08:00
Maxime Beauchemin
7327c97e4c v0.14.0 2016-11-29 15:10:21 -08:00
vera-liu
03b21dcf0a [explorev2] Bug fixes in Save Modal (#1707)
* Bug fixes in Save Modal
Issues solved:
 - Save button doesn't pass in gotodash
 - slice_name was passed in from store as original slice_name instead of
   new one in 'saveas' action
 - datasource_type wasn't passed in to defaultViz and defaultForm
   function

* Change css filename to exploreV2

* Moved out utils
2016-11-29 15:03:15 -08:00
Bogdan
dc98c6739f Implement table name extraction. (#1598)
* Implement table name extraction tests.

* Address comments.

* Fix tests and reimplement the token processing.

* Exclude aliases.

* Clean up print statements and code.

* Reverse select test.

* Fix failing test.

* Test JOINs

* refactore as a class

* Check for permissions in SQL Lab.

* Implement permissions check for the datasources in sql_lab

* Address comments.
2016-11-29 15:43:36 -05:00
Bogdan
fcb870728d Add per schema permissions. (#1698)
* Add per schema permissions.

* Address comments.

* Add schema_access perms to the alpha and admin

* Create permissions on addition databases and datasources.

* Remove hybrid_property. Linter complains.
2016-11-29 14:16:55 -05:00
vera-liu
7919428a1e Vliu explorev2 bugs (#1701)
* Fixed table_name does not exist in druid

* Make Chart container scrollable for large chart

* Fixed bug of action buttons not clickable in heatmap

* Solve codeclimate issue

* Limit overflow to x
2016-11-29 10:31:11 -08:00
Alanna Scott
3496a80f5a make stack trace more readable (#1672)
* make stack trace more readable

* remove ascii_art import

* remove ascii_art.py
2016-11-28 21:05:37 -08:00
Alanna Scott
56b917a5c2 [explore-v2] fix errors on table view (#1675)
* render table name if in table view

* only render fave star and edit button if slice, not table

* fix error when table view

* use [table-name] - untitled format

* remove extra fave star
2016-11-28 21:04:43 -08:00
Alanna Scott
18c43aaea2 make chart title larger, fix explore actions btn spacing (#1680) 2016-11-28 20:12:44 -08:00
Maxime Beauchemin
c43fc38f69 [druid] fix having clause (#1694) 2016-11-28 16:43:45 -08:00
Bogdan
c07f0ab9c7 Config programmatic roles in the config.py (#1664)
* Config programmatic roles in the config.py,

* Add sql_lab.
2016-11-28 19:29:30 -05:00
Maxime Beauchemin
1c429b27bc Fixing issue #1689 (#1696) 2016-11-28 16:25:09 -08:00
Maxime Beauchemin
b7019ad4f3 [sqllab] bugfix SouthPane doesn't update as expected (#1699)
* [sqllab] bugfix SouthPane doesn't update as expected

* Linting
2016-11-28 16:24:02 -08:00
the-dcruz
84e8f741ae Add 'Save As' feature for dashboards (#1669)
* Add 'Save As' feature for dashboards

* Address code review comments
2016-11-28 08:34:14 -08:00
Nicolas Noé
e3a9b393c2 Missing merge_perm function. Fixes 1691. (#1692) 2016-11-28 08:27:41 -08:00
Riccardo Magliocchetti
16aba517e4 Use smaller size for node max_old_space_size (#1679)
This makes the difference from being killed by OOM on a 8GB machine
or working.
2016-11-25 14:10:40 -08:00
Riccardo Magliocchetti
205928e6df docs: fix python-redis link markup (#1683) 2016-11-25 14:09:14 -08:00
vera-liu
39ce4aa049 Added filter in ControlPanelsContainer for explore V2 (#1647)
* Added filter in ControlPanelsContainer

* Move function for getting url params object to utils

* Fixed python test

* Move Filter to separate component

* Added specs and made changes based on comments

* Moved specs to right folder
2016-11-23 09:51:19 -08:00
Maxime Beauchemin
cef4a8296a [sqllab] adding a sql preprocessor for Presto (#1670)
* [sqllab] adding a sql preprocessor for Presto

* fixing tests
2016-11-22 21:24:38 -08:00
vera-liu
b370ef0229 Rerender chart without clicking query button for fields (#1658)
* For some fields we would like to re-render chart once field is
  * changed, saving user the time to click query button
  * Such fields are stored in an array in store, could add more fields in
  * the future
2016-11-22 17:08:52 -08:00
vera-liu
6b80f5bb35 Get sections to render when switching datasource (#1660)
* Get sections to render when switching datasource
 - Move sectionsToRender in store and use is for defaultFormData
 - Change some SelectField to FreeFormSelect according to forms.py

* Solved the css not found problem in staging

* Fixed js tests
2016-11-22 14:55:32 -08:00
vera-liu
bdae570a69 Temperary fix of a slice bug (#1648) 2016-11-22 14:54:49 -08:00
vera-liu
face5245a9 Make explore container resize with browser window (#1608) 2016-11-22 14:36:41 -08:00
vera-liu
db1ed2a765 Calculate height dynamically using jquery for scrollable sqllab (#1611)
* Calculate height dynamically using jquery for scrollable sqllab components

* Move editorHeight to App.jsx

* Calculate height dynamically for query search
2016-11-22 13:21:07 -08:00
vera-liu
10982dec3c Make QueryTable scrollable in Query Search page (#1656) 2016-11-22 11:19:23 -08:00
vera-liu
6825e75681 Fixed bug with querylink passing sql object instead of string (#1659) 2016-11-22 11:09:32 -08:00
vera-liu
bd6a439e0b [QuerySearch] Add loading status to QuerySearch page (#1657)
* Add loading state to QuerySearch
 * Move ajax call to ComponentDidMount
 * Show loading image during ajax call
2016-11-21 13:18:18 -08:00
Bogdan
c90dd4902f Programatically sync the role with user list. (#1619) 2016-11-21 13:06:43 -05:00
Robert Kingston
868e5c45fe Redirect URL requests with "caravel" to "superset" (#1651)
* Redirect URL requests with "caravel" to "superset"

* Adding extra line break
2016-11-20 10:36:51 -08:00
Maxime Beauchemin
7e1852ee88 User profile pages (favorites, created content, recent activity, security & access) (#1615)
* Super

* User profile page

* Fixing python style

* Python unit tests

* Touchups and js tests

* Addressing comments
2016-11-19 21:23:44 -08:00
Maxime Beauchemin
5ae98bc7c9 Improving jinja2 security by using SandboxedEnvironment (#1632)
http://jinja.pocoo.org/docs/dev/sandbox/#sandbox
2016-11-18 17:14:30 -08:00
Bogdan
1624e7de7d Add all_tables endpoint to allow airpal / superset perm sync. (#1614) 2016-11-18 20:14:26 -05:00
Bogdan
7a98f84890 Admin / Alpha permission cleanup and fixes. (#1645) 2016-11-18 19:53:19 -05:00
Alanna Scott
9b181280d4 include jQuery and bootstrap (#1642) 2016-11-18 16:22:46 -08:00
vera-liu
38e94b9e43 Save modal component for explore v2 (#1612)
* Added specs for SaveModal

* Move datasource_id and datasource_name to form_data

* Add comments

* Deleted redundant fetchDashboard

* Replcae has_key for python3

* More react and less jquery

* Added alert for save slice

* Small changes based on comments

* Use react bootstrap
2016-11-18 14:56:02 -08:00
Bogdan
dc25bc6f4d Fix alpha permission checks. (#1641) 2016-11-18 17:22:24 -05:00
vera-liu
f64a205603 Use Alert for visualization error (#1639) 2016-11-18 13:38:14 -08:00
vera-liu
a8480f5492 Added Alert for ControlPanel and ChartContainer (#1626)
* Added Alert for ControlPanel and ChartContainer

Done:
 - Add alert for Control Panel when fetch_datasource_metadata failes
 - Add alert for Chart Container when update_explore query fails

* Changed color to warning-yellow

* Solve linter issue

* Fixed indent and delete error_redirect
2016-11-18 11:17:06 -08:00
vera-liu
0acf26b37c Fixed a bug with switching viz_type in exploreV2 (#1631)
- Issue: when switching from a viz_type outside nvd3 to a viz_type in
 nvd3, the Chart Container doesn't draw new graph
 - Fix: The reason was somehow the function inside nv.addGraph() wasn't
   called, extract the function outside and explicitly calling it solve
   the problem
2016-11-18 10:23:00 -08:00
Alanna Scott
2c068a1a15 increase space between fieldsset rows (#1629) 2016-11-18 10:08:50 -08:00
Maxime Beauchemin
b961c95121 dim visualization during refresh (#1636) 2016-11-18 10:06:07 -08:00
Dody Suria Wijaya
82693211f0 Update faq.rst (#1637)
Related to #1557
2016-11-18 10:04:44 -08:00
the-dcruz
e5467462cb Make nvd3 refresh smoother. (#1618) 2016-11-18 09:43:36 -08:00
Alanna Scott
ab5a4102cd [dashboard] give user feedback when there are unsaved changes (#1633)
* show alert and use dialog window if there are unsaved changes.

* add container class to alert
2016-11-18 09:37:01 -08:00
vera-liu
d5ef937b31 Fixed bugs with viz in exploreV2 (#1609)
* Fixed bugs with viz in exploreV2

Done:
 - fix typo in pie viz
 - add metrics/groupby to dist_bar viz
 - fix typo in heatmap viz
 - add big_number_total to viz
 - fixed problem with fetching columns for datasource
 - add sqlaTimeSeries to viz
 - change row_limit and limit from number to strings so that we
 don't need to parse integers in bootstrap data

* Fix python tests

* Added order_bars checkbox for dist_bar viz
2016-11-17 12:31:36 -08:00
Maxime Beauchemin
bce02e3f51 [security] improving the security scheme (#1587)
* [security] improving the security scheme

* Addressing comments

* improving docs

* Creating security module to organize things

* Moving CLI to its own module

* perms

* Materializung perms

* progrss

* Addressing comments, linting
2016-11-17 11:58:33 -08:00
Alanna Scott
aad9744d85 add new screenshots (#1589) 2016-11-17 11:45:40 -08:00
Alanna Scott
506b781f3a [explore-v2] add fave star and edit button to chart header (#1623)
* add fave star to chart title

* add TooltipWrapper, and css for icons

* fix linting
2016-11-17 10:53:44 -08:00
Maxime Beauchemin
267fd5b9bc [table viz] adding support for pagination (#1616)
* [table viz] adding support for pagination

* linting
2016-11-17 10:05:36 -08:00
Maxime Beauchemin
c362f2869e More Dashboard UX unit tests (#1603) 2016-11-17 10:05:08 -08:00
vera-liu
4f7f437527 Vliu put datasource in store (#1610)
* Move datasource from global store object to form_data

* Moved datasource_id  and datasource_name to form_data

* Refetch defaultFormData when switching datasource

* Fixed js tests
2016-11-17 09:26:25 -08:00
Maxime Beauchemin
ab5da5ba28 [table viz] allow sorting on any column (#1601)
* [table viz] allow sorting on any column

* explorev2
2016-11-17 07:15:18 -08:00
vera-liu
7531bb8942 Fixed dashboard controls for standalone bug (#1617) 2016-11-16 17:54:50 -08:00
vera-liu
811ee8ccdc Deleted unused components in exploreV2 (#1613)
* Deleted unused components in exploreV2

* Deleted constants.js
2016-11-16 14:33:00 -08:00
vera-liu
51cb485ce3 Add standalone to reactified dashboard page (#1596) 2016-11-16 13:22:14 -08:00
vera-liu
83d08b8b8f Get query button working in explorev2 (#1581)
* Get query buttonw working in explorev2

 - Create new endpoint for updating explore viz
 - Send over new form_data when query button is pressed

* Added endpoint test

* Changes based on comments

* Added docstring for endpoint, and query spec

* Remove white space around docstring
2016-11-16 13:21:53 -08:00
Maxime Beauchemin
ed3d44d591 Changelog entries for 0.13.2 2016-11-15 16:54:45 -08:00
Maxime Beauchemin
895fe23203 v0.13.2 2016-11-15 16:23:39 -08:00
André Cruz
af04a560c8 Moved check to the correct place. (#1606) 2016-11-15 10:38:03 -08:00
Maxime Beauchemin
9124a17e86 Removing ascii_art.p from code coverage analysis 2016-11-15 08:50:08 -08:00
Maxime Beauchemin
99b0d4c111 Fix MySql time grain issue (#1590)
* Fix MySql time grain issue

* linting

* linting
2016-11-14 21:35:10 -08:00
Maxime Beauchemin
84b98c234f Adding Greenplum to supported dbs 2016-11-14 15:46:24 -08:00
Maxime Beauchemin
bcc1428ebf Updating CODECLIMATE_REPO_TOKEN to new location 2016-11-14 12:45:58 -08:00
vera-liu
2133056c04 Added different Select Fields (#1583)
* Added different Select Fields
 - Switched FormGroup to react-select
 - Added multi and freeform to select, now it can take customized user
   input and insert it as options

* Fixed tests

* Small nit based on comments
2016-11-13 14:11:52 -08:00
Robert Kingston
4155a9d7f9 Removing broken link to old docker image (#1591)
Need to re-add if/when Kochalex updates to using Superset
2016-11-12 19:32:39 -08:00
vera-liu
ed4825523c Fixed a bug with new dashboard (#1585) 2016-11-11 17:28:07 -08:00
Maxime Beauchemin
fdbb2bbdab fixing the build 2016-11-11 16:24:57 -08:00
Mazdak B
c064d6d847 Correct part_fields variable name (#1586) 2016-11-11 16:20:04 -08:00
Maxime Beauchemin
d33874bd3d [hotfix] postgres issue when slice_id is missing 2016-11-11 09:57:34 -08:00
Maxime Beauchemin
96d32dd11f Improve Druid metadata fetching resilience (#1584) 2016-11-11 09:46:48 -08:00
Maxime Beauchemin
d6bc354ff3 [hotfix] fix support for presto DATE and TIMESTAMP type 2016-11-11 00:39:20 +00:00
Maxime Beauchemin
7325a4fb4b [hotfix] table view not group by without orderby fails 2016-11-10 23:54:51 +00:00
Maxime Beauchemin
90f00c5b29 Minor documentation touchups 2016-11-10 11:27:56 -08:00
Maxime Beauchemin
8539c423ea v0.13.1 2016-11-10 10:01:31 -08:00
Maxime Beauchemin
e9bfbfce84 Removing boat pic from README 2016-11-10 09:41:09 -08:00
Maxime Beauchemin
6e4f0664cb [hotfix] lint 2016-11-10 09:09:40 -08:00
Maxime Beauchemin
3c920c9d94 [hotfix] datatables import issues 2016-11-10 09:07:53 -08:00
Maxime Beauchemin
15b67b2c6c [WiP] rename project from Caravel to Superset (#1576)
* Change in files

* Renamin files and folders

* cleaning up a single piece of lint

* Removing boat picture from docs

* add superset word mark

* Update rename note in docs

* Fixing images

* Pinning datatables

* Fixing issues with mapbox-gl

* Forgot to rename one file

* Linting

* v0.13.0

* adding pyyaml to dev-reqs
2016-11-09 23:08:22 -08:00
Maxime Beauchemin
973537fd9a [hotfix] resizing widgets 2016-11-09 14:52:39 -08:00
vera-liu
d70a74479d Make Sqllab a one-page app -- body not scrollable (#1551)
* Make Sqllab a one-page app -- body not scrollable

* Add scroll-container and scroll-content in main.css
2016-11-09 14:21:43 -08:00
Maxime Beauchemin
946e4b750a Reactifying the dashboard (#1572) 2016-11-09 14:21:23 -08:00
vera-liu
9789e3fb9b Bind data preview tabs to sql editor (#1573) 2016-11-09 14:21:06 -08:00
Maxime Beauchemin
6a15679d87 [hotfix] encode csv to utf-8 2016-11-09 11:46:15 -08:00
vera-liu
ad1cd5577c Pass values from global store to fields in exploreV2 (#1561)
* Link fields to store by firing setFormData action

* Moved onChange to ControlPanelsContainer, retrieve defaultFormData from fields in store

* Pass data from store to checkbox/select/text Field

* Fixed tests

* Changed reducer back to old Object.assign() style
2016-11-09 09:01:17 -08:00
Dirk Kelly
55668ca621 Link to database-urls in databaseadd view (#1480) 2016-11-09 08:49:01 -08:00
Alanna Scott
0c221a28d1 add slice_name and table_name for title (#1567) 2016-11-09 08:48:26 -08:00
Maxime Beauchemin
a475551b23 [sqllab] bind alt+enter shortcut in AceEditor to run a query (#1554) 2016-11-09 08:42:13 -08:00
Riccardo Magliocchetti
bad7676414 Bump cryptography dependency to 1.5.3 (#1569)
As the 1.5 release branch builds with OpenSSL 1.1.x
2016-11-09 08:41:57 -08:00
Alanna Scott
51c0470f0b [explore v2] populate dynamic select field options (#1543)
* pass field options in viz json

* move field options to fetch_datasource_metadata

* on control panels container mount, fetch datasource meta data and set dynamic field choices

* render options for select fields

* use component class rather than sic

* fix linting

* fix whitespace

* delete unused var

* only render fields once datasource meta has returned

* fix typo

* add datasources and fix column formatting

* fix tests

* never used function

* fix tests

* add test for fetch_datasource_metadata

* remove unneeded props
2016-11-08 15:55:49 -08:00
vera-liu
4530047c76 Added action buttons to Chart Container of explore V2 (#1562) 2016-11-08 15:22:20 -08:00
Alanna Scott
1bf83c3bf7 [explore-v2] render columns based on length of fieldSets array (#1559)
* render columns based on length of fieldSets array

* fix tests
2016-11-08 13:52:24 -08:00
vera-liu
bb6ab11001 Vliu link form data explore v2 (#1540)
* Put default form_data and viz in store

* Link fields to store by firing setFormData action

* Fixed tests for Container and actions

* Moved onChange to ControlPanelsContainer, retrieve defaultFormData from fields in store

* Deleted switch statement in reducer

* Removed resetFormData and refactored setFormData in reducers

* Added text for fields

* Changed test statements
2016-11-08 10:28:51 -08:00
Maxime Beauchemin
e4bd1884d3 [druid] adding support for dimensionspecs (#1545)
More about it here:
http://druid.io/docs/latest/querying/dimensionspecs.html

fixes https://github.com/airbnb/caravel/issues/1086
2016-11-07 17:05:41 -08:00
vera-liu
4014a48f7d Added cache prop to ResultSet (#1552)
* Added cache prop to ResultSet for distiguishing query result and datapreview results

* small nit

* Only cache results.data locally
2016-11-07 15:28:46 -08:00
pinkythalli97
97ded32415 Update linting instructions. (#1478)
flake8 changes tests lints only the test dir.
2016-11-06 08:26:14 -08:00
vera-liu
593ac081f0 Added scroll bar and option to collapse for Sql Editor tool bar (#1532)
* Added scroll bar and option to collapse for Sql Editor tool bar
Done:
 - Added scroll-bar to Sql Editor tool bar
 - Added hide/expand tool bar option to dropdown menu of tab

* Add more margin to give space to scroll-bar

* Add scroll to right panel independently
2016-11-04 15:58:11 -07:00
vera-liu
69f0a4e1cb Put data preview in south pane (#1486)
* Put data preview in south pane

Before: data preview of a selected table appears as a modal, but for
some cases users may want to view data and edit sql at the same time

After:
 - data preview of a selected table pops up a new tab in South Pane
 - data are saved to local state and flushed in global store in
   ResultSet component

* Moved dataPreviewId to table object

* Put back preview icon for fetching preview data

* Revert "Put back preview icon for fetching preview data"

This reverts commit b6f5dcfe64.

* Added option to retrieve preview results after refresh
2016-11-04 14:21:51 -07:00
Glen Schrader
757e7de60c add oracle time_grains (#1544) 2016-11-04 09:52:06 -07:00
Maxime Beauchemin
1d7d5469a9 [hotfix] remove failing Druid test 2016-11-04 09:47:02 -07:00
vera-liu
98afc3e590 Added setFilter(), containerID and getFilter() to (#1360)
mock slice object in exploreV2

 - filter_box in exploreV2 was broken because containerID and
   getFilter() were not defined in the mock of slice object
2016-11-03 21:14:08 -07:00
Maxime Beauchemin
ea189790f1 [hotfix] druid dist_bar viz issues with non-str x values 2016-11-04 00:59:41 +00:00
Bogdan
62987077fa Read the user origin specification. (#1541) 2016-11-03 17:33:06 -07:00
Maxime Beauchemin
3b9f7cb3f1 [hotfix] groupby may be a set 2016-11-04 00:08:18 +00:00
vera-liu
5882c7e344 Added jquery methods to ChartContainer to get world_map viz working in exploreV2 (#1443) 2016-11-03 15:50:36 -07:00
vera-liu
77b6e2cd2e Get pivot table working in explore v2 (#1432)
* Use jquery calls in find() and html() of slice mock to
Get pivot_table viz working

* Borrowed code from caravel.js into error callback
2016-11-03 13:53:45 -07:00
Alanna Scott
88b1f956c7 [explore-v2] handle field overrides (#1535)
* pass all props to *Field components

* s/fieldSetOverrides/fieldOverrides

* handle field overrides
2016-11-03 13:38:17 -07:00
Alanna Scott
d9b49ca2bc [exploreV2] remove /exploreV2 endpoint, add v2 bootstrap data to /explore endpoint (#1536)
* remove exploreV2 endpoint, add v2 bootstrap data to explore endpoint

* delete empty line

* move bootstrap data inside elif condition
2016-11-03 13:37:29 -07:00
Alanna Scott
4156ad5a30 [explore-v2] control panel fixes (#1529)
* make fieldset conditions more clear

* make label required

* use render* pattern

* use slugify util for turning labels into ids

* use field rather than html

* don't need panel-title class here
2016-11-02 21:51:56 -07:00
Bogdan
ae46561648 Support week_ending_saturday for Druid. (#1491)
* Support week_ending_saturday for Druid.

* Use period granularity

* Use ISO 8601 for period definitions.

* Fix tests

* More flexibility for the freeform choices.
2016-11-02 18:45:10 -07:00
Maxime Beauchemin
1700a807e9 [sqllab] templating refactor (#1504)
* Add support for jinja templates in WHERE/HAVING clauses

* Generalizing

* bugfix
2016-11-02 13:22:07 -07:00
shashank singh
0bab15b213 Update INTHEWILD.md (#1526)
Added Faasos
2016-11-02 13:21:16 -07:00
Alanna Scott
38d3075554 [explore V2] render all control panels and fields dynamically for each vis type (#1493)
* export functions directly rather than object at the bottom

* move viztypes to controlPanelMappings, add fieldset rows and section data

* for each viz type, render a controlPanelsContainer, controlPanelSections, FieldSetRows, and FieldsSets

* add comments, move mappings to store

* organize store and add default sections

* render all the needed sections

* add tooltip to sections

* remove console log

* use only panel panel-default, not panel-body, no need the padding

* render fields for all fields in field set

* add the rest of the control panel sections and field overrides

* fix naming

* add fieldTypes array

* don't use default section

* pass only needed state via mapStateToProps

* fix code climate errors

* linting

* move field components to their own files

* render field sets as lists

* fix field components

* use SFC

* update modal trigger test to be more accurate

* add FieldSetRow test

* add test for controlpanelsContainer

* fix test

* make code climate happy

* add freeform select field
2016-11-02 12:57:44 -07:00
Maxime Beauchemin
1b124bfb87 [druid] optimize Druid queries where possible (#1517)
* [druid] optimize Druid queries where possible

Trying to use timeseries, topn where possible, falling back on 2-phases
groupby only where needed

* Fixing py3 bug
2016-11-02 11:25:33 -07:00
plumbeo
cdf4dd0302 Add yearly and quarterly granularities to mysql engine backend (#1518) 2016-11-02 08:00:34 -07:00
vera-liu
a13bed2db6 Moved sqllab tests from core_tests to sqllab_tests (#1502)
* Moved sqllab tests from core_tests to sqllab_tests

* Minor changes based on comments
2016-11-01 20:48:31 -07:00
vera-liu
26318f94fe Moved queriesArray from render() to local state (#1505)
* Moved queriesArray from render() to local state, so that QueriesArray
is only reloaded only during switching tabs or queries object is updated.

* Changed object comparison function to take length into consideration
2016-11-01 19:40:09 -07:00
Bogdan
769fb0820f Strip sql and remove ; for csv download. (#1508) 2016-11-01 15:03:42 -07:00
vera-liu
52380534f3 Moved ajax call for fetching table metadata from SqlEditorLeftBar to actions (#1494) 2016-11-01 09:00:24 -07:00
Maxime Beauchemin
2fd2526046 Add support for jinja templates in WHERE/HAVING clauses (#1442) 2016-11-01 08:16:06 -07:00
Maxime Beauchemin
61509bbd44 [sqllab] surfacing more table metadata (indices, pk, fks) (#1485)
* Expose more table/column metadata

* [sqllab] expose more table metadata

* more tests
2016-10-31 23:52:37 -07:00
Maxime Beauchemin
76499afd8d [pep8] allowing 90 chars per line 2016-10-31 21:22:44 -07:00
Maxime Beauchemin
4023f328f7 [sqllab] run only the part of the query that is selected (#1479) 2016-10-31 21:07:46 -07:00
Bogdan
4f49cb555b Celery uses separate db engine with NullPool. (#1492)
* Celery uses separate db engine with NullPool.

* Address comment
2016-10-31 16:02:12 -07:00
Bogdan
4dc959a3e4 Revert "NullPool for the celery worker." (#1488) 2016-10-31 10:02:07 -07:00
Ron Baker
518fbf562c Minor Fixes (#1484)
* Fix typo accomodate

* Fix typo calender

* Fix typo dimentions
2016-10-30 16:55:56 -07:00
Maxime Beauchemin
49828d3d9d add step to pypi build/push 2016-10-29 21:11:16 -07:00
willgroves
248e6a7b05 fix name for postgresql (#1482) 2016-10-29 20:34:10 -07:00
Bogdan
5561e6b770 Fix celery module import in comments. (#1474) 2016-10-29 14:08:23 -07:00
Maxime Beauchemin
ab083b86f3 [sqllab] slide animations when adding/removing/toggling TableElement (#1472)
* [sqllab] slide animations when adding/removing/toggling TableElement

* Adressing comments
2016-10-29 07:48:17 -07:00
Maxime Beauchemin
4bf525222a [sqllab] add autocomplete to AceEditor for table and column names (#1475)
* [sqllab] add autocomplete to AceEditor for table and column names

* addressing comment about getCompletions as a component method
2016-10-28 21:35:49 -07:00
vera-liu
45efcb381c Added time filter to query search page (#1329)
* Added time filter to query search page

* Added start date

* Updated python endpoint test

* changed spec

* Added specs and tests

* Modified python/js tests and some function/file names
based on code review comments

* Resolved conflicts in DashboardSelect_spec and QuerySearch_spec

* Break python tests for separate functions, Move sql queries to setUp()

* Get around eslint error for spec

* Small changes based on comments
2016-10-28 14:12:53 -07:00
Bogdan
07a7736c71 NullPool for the celery worker. (#1465) 2016-10-28 14:01:24 -07:00
vera-liu
d2826ab7af Added checkbox in dist_bar viz to enable sorting of bars based on x axis labels (#1379)
* Added order bars option for dist_bar viz

* Make order_bar checkbox to cover half the colum and translate the description
2016-10-28 10:33:57 -07:00
Maxime Beauchemin
6ab769f382 CHANGELOG for 0.12.0 2016-10-28 09:48:28 -07:00
Maxime Beauchemin
3e1cd2bdca v0.12.0 2016-10-28 09:40:52 -07:00
Riccardo Magliocchetti
22784b7f06 run_specific_test: take the test as parameter (#1469)
Instead of hardcoding it, e.g.:
./run_specific_test.sh tests.core_tests:CoreTests.test_templated_sql_json
2016-10-28 09:24:51 -07:00
Maxime Beauchemin
c4922615eb [sqllab] add column sort feature to TableElement (#1467) 2016-10-27 18:42:04 -07:00
vera-liu
7307ddad3c Highlight affected slices for filter change in dashboard view (#1439)
* Highlight affected slices for filter change in dashboard view
Done:
 - When user adds/deletes a filter in dashboard, affected slices will
   have their header highlighted for 2 seconds

* Modified highlight to a more subtle box shadow

* Added slice-cell class for highlight transition

* Changed class name to slice-cell-highlight
2016-10-26 21:22:10 -07:00
Maxime Beauchemin
c7ba143d03 Fix typo in sqllab docs 2016-10-26 20:44:03 -07:00
Maxime Beauchemin
b24206387b [sqllab] optimizing React (#1438)
* [sqllab] optimizing React

* Addressing comments and making npm run dev faster
2016-10-26 17:41:46 -07:00
vera-liu
64d196442f Added dashboard standalone page (#1429)
* Added dashboard standalone page

* Deleted additional template, parameterized dashboard.html for standalone

* Only wrap add-slice-container for standalone instead of including whole modal

* Use standalone_mode argument passed from both explore view and dashboard view
2016-10-26 15:24:05 -07:00
Maxime Beauchemin
5944643da6 [sqllab] add support for Jinja templating (#1426)
* [sqllab] add support for Jinja templating

* Adressing comments

* Presto macros

* Progress

* Addressing coments
2016-10-26 11:09:27 -07:00
Riccardo Magliocchetti
8c5e495272 Add github issue template (#1436) 2016-10-26 11:03:05 -07:00
vera-liu
bb23685b9d Added average metric AVG() to default metrics (#1413)
* Added average metric AVG() to default metrics

* Added avg as a column for both SqlaTable and Druid
2016-10-26 09:19:28 -07:00
Maxime Beauchemin
940659bc14 [sqllab] some frontend tests (#1400)
* [sqllab] some frontend tests

* linting

* Addressing comments

* Addressing unaddressed comments

* Touchups
2016-10-25 16:44:32 -07:00
Bogdan
7c5933732b Filter immune slices array stores strings. (#1402) 2016-10-25 11:08:08 -07:00
vera-liu
89df2fcf76 Adjusted top margin of heatmap plot to get it working in V2 (#1361)
* Adjusted top margin of heatmap plot to get it working in V2

Problem:
The heatmap in V2 was shifted towards the top margin of slice
container, this was because in v2 slice name header was part of the
container body, while in v1 the header was separately defined in
explore.html template.

Solution:
To get heatmap properly shown in V2, we need to
take into account the height of the slice_name header. Adding to
margin_top will shift the plot in V1 too, but it won't make a big
difference to the look.

Ideally when we renovate slice container in future PR we would defined a
height for slice_name header and take it into account for all
visualization files.

* Added panel header height to margin_top for explore v2

* Use getBoundingClientRect to get header height

* Use slice-header for id of panel-title
2016-10-25 10:44:29 -07:00
Maxime Beauchemin
174a199c30 [hotfix] Query search is unreachable 2016-10-24 17:54:43 -07:00
vera-liu
6f1e7c3016 Added url shortner for sharing query link (#1314)
* Added url shortner for sharing query link

* Move shortener outside CopyToClipboard and move ajax call to common.js

* transfer dbId to int to avoid failed prop
2016-10-24 17:49:23 -07:00
vera-liu
9f81e23f8f Fixed css class not being used by slice container (#1359)
* Fixed css class not being used by slice container

* Delete require css line since it's not needed when className is specified
2016-10-24 16:26:30 -07:00
vera-liu
19fab6eea7 Get table viz work in explore v2: Added d3 format to mock slice (#1353)
* Added d3format() function to mock slice in explorev2

Problem:
table viz was not working in explorev2 due to d3format() not defined in
mock slice.

* Change column_formats to camel case
2016-10-24 16:01:06 -07:00
Maxime Beauchemin
63161b11c3 [sqllab] proper, quoted, select * on the server side (#1404)
* [sqllab] proper, quoted, select * on the server side

* fixing tests
2016-10-21 16:55:37 -07:00
Bogdan
4f886d65ec Fix None view_menues in permissions. (#1409) 2016-10-21 16:09:51 -07:00
Maxime Beauchemin
62e0e195e8 [docfix] d3.format docs have moved 2016-10-21 16:08:55 -07:00
Maxime Beauchemin
e9d4749f44 [hotfix] sqllab presto 2016-10-21 13:35:21 -07:00
Maxime Beauchemin
93f8e7d8e9 Fix the js build running out of heap space (#1408) 2016-10-21 13:05:14 -07:00
Maxime Beauchemin
3dea6e0da5 [sqllab] adding more descriptive labels to left panel (#1407) 2016-10-21 12:51:50 -07:00
Maxime Beauchemin
6fb3b305ad [sqllab] add support for results backends (#1377)
* [sqllab] add support for results backends

Long running SQL queries (beyond the scope of a web request) can now use
a k/v store to hold their result sets.

* Addressing comments, fixed js tests

* Fixing mysql has gone away

* Adressing more comments

* Touchups
2016-10-20 23:40:24 -07:00
Maxime Beauchemin
7dfe891cc1 [hotfix] timeseries_limit_metric: Not a valid choice 2016-10-20 17:42:16 -07:00
Bogdan
5c3966a32d Override the role with perms for given datasources. (#1399)
* Override the role with perms for give datasources.

* Address comments.
2016-10-20 15:30:09 -07:00
575 changed files with 25966 additions and 14378 deletions

View File

@@ -6,7 +6,7 @@ engines:
eslint:
enabled: true
config:
config: caravel/assets/.eslintrc
config: superset/assets/.eslintrc
pep8:
enabled: true
fixme:
@@ -19,16 +19,17 @@ engines:
ratings:
paths:
- "**.py"
- "caravel/assets/**.js"
- "caravel/assets/**.jsx"
- "superset/assets/**.js"
- "superset/assets/**.jsx"
exclude_paths:
- ".*"
- "**.pyc"
- "**.gz"
- "env/"
- "tests/"
- "caravel/assets/images/"
- "caravel/assets/vendor/"
- "caravel/assets/node_modules/"
- "caravel/assets/javascripts/dist/"
- "caravel/migrations"
- "superset/assets/images/"
- "superset/assets/vendor/"
- "superset/assets/node_modules/"
- "superset/assets/javascripts/dist/"
- "superset/migrations"
- "docs/"

12
.gitignore vendored
View File

@@ -1,21 +1,25 @@
*.pyc
yarn-error.log
_modules
superset/assets/coverage/*
changelog.sh
.DS_Store
.coverage
_build
_static
_images
caravel/bin/caravelc
_modules
superset/bin/supersetc
env_py3
.eggs
build
*.db
tmp
caravel_config.py
superset_config.py
local_config.py
env
dist
caravel.egg-info/
superset.egg-info/
app.db
*.bak
.idea
@@ -26,3 +30,5 @@ app.db
*.js.map
node_modules
npm-debug.log
yarn.lock
superset/assets/version_info.json

View File

@@ -16,8 +16,7 @@ pep8:
full: true
ignore-paths:
- docs
- caravel/migrations/env.py
- caravel/ascii_art.py
- superset/migrations/env.py
ignore-patterns:
- ^example/doc_.*\.py$
- (^|/)docs(/|$)

2
.pycodestyle Normal file
View File

@@ -0,0 +1,2 @@
[pycodestyle]
max-line-length = 90

View File

@@ -24,15 +24,14 @@ env:
before_install:
- npm install -g npm@'>=3.9.5'
before_script:
- mysql -e 'drop database if exists caravel; create database caravel DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci' -u root
- mysql -e 'drop database if exists superset; create database superset DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci' -u root
- mysql -u root -e "CREATE USER 'mysqluser'@'localhost' IDENTIFIED BY 'mysqluserpassword';"
- mysql -u root -e "GRANT ALL ON caravel.* TO 'mysqluser'@'localhost';"
- psql -c 'create database caravel;' -U postgres
- mysql -u root -e "GRANT ALL ON superset.* TO 'mysqluser'@'localhost';"
- psql -c 'create database superset;' -U postgres
- psql -c "CREATE USER postgresuser WITH PASSWORD 'pguserpassword';" -U postgres
- export PATH=${PATH}:/tmp/hive/bin
install:
- pip install --upgrade pip
- pip install tox tox-travis
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION
- npm install
script: tox -e $TOX_ENV

View File

@@ -1,5 +1,319 @@
## Change Log
### 0.15.1 (2016/12/28 21:29 +00:00)
- [092432f](https://github.com/airbnb/superset/commit/092432f04f0033e60493f009728a7bfd6a744b22) v0.15.1 (@mistercrunch)
- [ea8e663](https://github.com/airbnb/superset/commit/ea8e6634d6c304cde3a42c65d37ec694f76b8cec) read anon user role from config, remove reference to public role (#1878) (@willgroves)
- [3e6f90c](https://github.com/airbnb/superset/commit/3e6f90cf722f205e1f13ef7228be6d0d767c1d1d) Upgrading pydruid version and adopt 'merge' flag during refresh_druid operation (#1879) (@dkhwangbo)
- [1673105](https://github.com/airbnb/superset/commit/16731056edf25cd4422fa674de827215df9027b1) [sqllab] async queries - better error handling (#1853) (@mistercrunch)
- [0712894](https://github.com/airbnb/superset/commit/0712894353825ea2faa19d8000ca031c44debf39) Improving database logging by adding duration, referrer and post data (#1830) (@mistercrunch)
- [36fad80](https://github.com/airbnb/superset/commit/36fad803edf6666188746f270e498e03c2df363c) sqllab: don't hold database deletion because of query reference (#1863) (@xrmx)
- [6732f01](https://github.com/airbnb/superset/commit/6732f01cb7bb08e1b180bb74f263bf50317d7462) Enable freeform-select with fetched column values for filter values (#1697) (@vera-liu)
- [bb04e6f](https://github.com/airbnb/superset/commit/bb04e6fcfa1042b4532b50d8898555813be7fa29) Use APP_ICON in template (#1855) (@szmate1618)
- [007ee88](https://github.com/airbnb/superset/commit/007ee88d33f92e6d052122b35ccad84d176029a7) [explorev2] improving the scrolling/scrollbars placement (#1840) (@mistercrunch)
### airbnb_prod.0.15.0.1 (2016/12/15 22:06 +00:00)
- [7a5bb94](https://github.com/airbnb/superset/commit/7a5bb947542fdc20e2ec70e18b1cf418b8d1dba8) Stop ChartContainer from rendering twice on chartStatus change (#1828) (@vera-liu)
- [e06a0cd](https://github.com/airbnb/superset/commit/e06a0cd89bc84f3a7e75ee6f03df8c9c3a2badeb) Add force_ctas_schema to query model when enabled (#1825) (@vera-liu)
- [b6cba13](https://github.com/airbnb/superset/commit/b6cba13293101f3022c16e120e96383b59cae03e) [explorev2] enabling redux dev tools (#1842) (@mistercrunch)
- [d929bbf](https://github.com/airbnb/superset/commit/d929bbfe3010f795cdf9ef28b48e9e249cf7ef86) [explorev2] making QueryAndSaveBtns disabled while running queries (#1841) (@mistercrunch)
- [bf67d64](https://github.com/airbnb/superset/commit/bf67d64708834800669e8be30ffa695d7a6db022) [explorev2] making Datasource an Viz controls not clearable (#1845) (@mistercrunch)
- [92aa1a6](https://github.com/airbnb/superset/commit/92aa1a612476fdef5f19eb68f961324a333b346c) Permissions refactoring, optimizations and unit testing. (#1798) (@bkyryliuk)
- [733ab80](https://github.com/airbnb/superset/commit/733ab8014bd83ae4e0e4961b9c454ad238556bd8) [explorev2] using a loader to load the explorev2 specific css (#1843) (@mistercrunch)
- [6aaa49f](https://github.com/airbnb/superset/commit/6aaa49f0bf182f449a970a1e15d86111987dde32) Change default gunicorn address (#1838) (@amancevice)
- [638f27c](https://github.com/airbnb/superset/commit/638f27c2df6540a50d5cba92847bc7000b33cb1b) [sqllab] Fix sql expression bug with count distinct metrics (#1805) (@vera-liu)
- [84a3b55](https://github.com/airbnb/superset/commit/84a3b559128bb7717869f5fa7339bfe1ac4003cc) [explorev2] remove unused file SqlClause.jsx (#1839) (@mistercrunch)
- [552d464](https://github.com/airbnb/superset/commit/552d46479bbf819e4fa7fb811ec05cff104a840c) [explorev2] no bootstrap data, just metadata in exploreV2 (#1827) (@mistercrunch)
- [fa9c066](https://github.com/airbnb/superset/commit/fa9c066ffe6b71a36f97e2e0d9fdb94b94183ca6) Add email-to option in action buttons for dashboard and slice (#1705) (@vera-liu)
- [e1e20b8](https://github.com/airbnb/superset/commit/e1e20b875748f677312d8c5ed3daf87113143b63) Sort searched queries by recency (#1735) (@vera-liu)
- [2fb94a8](https://github.com/airbnb/superset/commit/2fb94a89e2538710c5293404acaf6d8f0ef847ea) Add ADDITIONAL_MIDDLEWARE option to config (#1832) (@jr-minnaar)
- [7a9604a](https://github.com/airbnb/superset/commit/7a9604a3c918ce5fad1010e9e95fbf961a95420d) Workaround for slices "Not Found" issue in IE 11 (#1821) (@rlei)
- [e099088](https://github.com/airbnb/superset/commit/e0990880121643b63f66cc18fe917ea9fbe98554) [hotfix] fixing the build (@mistercrunch)
- [34e107e](https://github.com/airbnb/superset/commit/34e107e7d35b7d48385a13268b2ed713a2808114) [explore-v2] add config option for explore v2 beta users, and send through v2 path (#1671) (@ascott)
### 0.15.0 (2016/12/12 19:30 +00:00)
- [2254a4d](https://github.com/airbnb/superset/commit/2254a4d0b4480da7563c7c3afdeadecf66b08cf5) v0.15.0 (@mistercrunch)
- [9f7486f](https://github.com/airbnb/superset/commit/9f7486f4029fcbc18dfdeaeb4ff3c3c07242ad2b) remove extra call to get_viz in explorev2 (#1812) (@vera-liu)
- [699602d](https://github.com/airbnb/superset/commit/699602d1c5bdd7d15f6adb5f22e458c268bc1306) Add tooltips to RunAsync and CTAS button (#1792) (@vera-liu)
- [2993ff1](https://github.com/airbnb/superset/commit/2993ff1d75ff2391ffab388544f198cbb78364a9) Add NVD3's bullet chart (#1775) (@darabos)
- [afb3c24](https://github.com/airbnb/superset/commit/afb3c24d5a4951e7cc5a5714a79eb201e0b1aa24) Showing more fields in DatabaseView (@mistercrunch)
- [8ef730b](https://github.com/airbnb/superset/commit/8ef730b5feb8e27116552ca863e8dac42b1aa6ec) Added timer to explore v2 and share it with sqllab (#1802) (@vera-liu)
- [866cfe5](https://github.com/airbnb/superset/commit/866cfe52794d5c6df9f85f088243fa85f66eaec7) Add schema name to output column in query history (#1790) (@vera-liu)
- [68c2eab](https://github.com/airbnb/superset/commit/68c2eab6b93a13966b19aae20fb6c82fc34e3bcf) [hotfix] handling 0% change in big number with trendline (#1801) (@mistercrunch)
- [aeda5bd](https://github.com/airbnb/superset/commit/aeda5bd2606946a5cb6fd7dd5ecd4b73413c79a9) [sqllab] config item for SQLLAB_DEFAULT_DBID (#1793) (@mistercrunch)
- [a95cd71](https://github.com/airbnb/superset/commit/a95cd71456a0b24c08d4d020238fd9d89b2c9cd7) Add viz thumbnails to viz_type select (#1794) (@vera-liu)
- [34d0dd9](https://github.com/airbnb/superset/commit/34d0dd9d6e074e41968c39247d66e19fd551c57b) adjust header nav links so they are all aligned on the base line (#1786) (@ascott)
- [401d9af](https://github.com/airbnb/superset/commit/401d9afd54ce46f5c63a821094350098f07823a1) [ui] update logo, favicon, and new primary color (#1781) (@ascott)
- [74edb93](https://github.com/airbnb/superset/commit/74edb936a599ed54528389814ddf83e74f8452fd) [WIP] Add http to copied url and move function to componentWillReceiveProps (#1780) (@vera-liu)
- [c155857](https://github.com/airbnb/superset/commit/c1558578d7c555fb7bb9ee30eb98bad4d28fbd07) [explorev2] Breaking down large files, fixing JS warnings (#1773) (@mistercrunch)
### airbnb_prod.0.13.0.3 (2016/12/06 07:18 +00:00)
- [3597fdb](https://github.com/airbnb/superset/commit/3597fdb7f869929e0b09ff0144ff430b81e67853) Filter table list based on the user permissions. (#1769) (@bkyryliuk)
### airbnb_prod.0.13.0.2 (2016/12/06 01:17 +00:00)
- [43f2a37](https://github.com/airbnb/superset/commit/43f2a379a1b88b260d0a4bdeac97b3cb4478afcf) Make cell-click filter in table viz optional (#1762) (@vera-liu)
- [69702e3](https://github.com/airbnb/superset/commit/69702e3a1956ef5587e5aea2bffb3720b9b4cd35) Create users if not found. (#1753) (@bkyryliuk)
- [eb0655c](https://github.com/airbnb/superset/commit/eb0655cf85daad4329cf8630fe99e2a50d0e7e5a) [sqllab] Fixed js error when results are not available (#1715) (@vera-liu)
- [d8864bc](https://github.com/airbnb/superset/commit/d8864bc92b7566fa4ebb9d4863d75bd5eaa1e9c4) Enable overwrite sql in QueryHistory (#1731) (@vera-liu)
- [89fc9d7](https://github.com/airbnb/superset/commit/89fc9d7c80564e2f02949ed33113a63fc1e898e4) Make entire menuitem clickable for copy query (#1747) (@vera-liu)
- [76aa9f7](https://github.com/airbnb/superset/commit/76aa9f7e1047de5e41e8bd5bfee610fcd569b8ad) [explorev2] fix textfield and druid bug (#1732) (@vera-liu)
- [abd0974](https://github.com/airbnb/superset/commit/abd097489738eacc37fcd5dc9605d483c84071f6) Fix superset cli for python3 (#1760) (@xrmx)
- [c4e943a](https://github.com/airbnb/superset/commit/c4e943a24fe89824e59a105d6ce6e5d9255717c1) [sqllab] making 'click to retrieve results' a button (#1737) (@mistercrunch)
- [a3106bc](https://github.com/airbnb/superset/commit/a3106bcb3d0bc6fb0e5df2c356d453563a07c8a2) [bugfix] bignumber comparison wrong with neg values (#1743) (@mistercrunch)
- [b045075](https://github.com/airbnb/superset/commit/b045075a96232f1e2f2e14bdaa171eba67b0fa29) Sankey Tooltip fix (#1748) (#1750) (@ddol)
- [09d597f](https://github.com/airbnb/superset/commit/09d597f3adf4b238dc2a43b802fdda22b451dfe5) Prevent duplicated view_menu perms (#1751) (@bkyryliuk)
- [9d4c3d8](https://github.com/airbnb/superset/commit/9d4c3d83d0907925f1f37cbe48c5decd2c54639f) Update role based on usernames not emails. (#1749) (@bkyryliuk)
- [95580a0](https://github.com/airbnb/superset/commit/95580a004fe3322ff1c623ac4eec72150e1355b5) [explorev2] cosmetic, smaller size for input text (#1746) (@mistercrunch)
- [723f907](https://github.com/airbnb/superset/commit/723f90755e89e46287e212b18844c9f7abc6cf60) Fixing the sourcemap in dev mode (#1744) (@mistercrunch)
- [324205f](https://github.com/airbnb/superset/commit/324205f77ac7a77b6546da482979842d58ce9fbb) [sqllab] bugfix where a query has the same alias twice as output (#1734) (@mistercrunch)
- [0a40d8c](https://github.com/airbnb/superset/commit/0a40d8ce8f08ba6edc18370032aa85571495b570) Rremove unused symlinks (#1736) (@yolken)
- [168a252](https://github.com/airbnb/superset/commit/168a25239e712fe9eccf676f26df75fd91bd126f) State that npm should be between 3.9 and 4 (@bkyryliuk)
- [7eef46e](https://github.com/airbnb/superset/commit/7eef46e9413b01ec15be515567a9619c695d5501) Adding links pointing to the new user profile page (#1704) (@mistercrunch)
- [50da4f8](https://github.com/airbnb/superset/commit/50da4f8c0708f91ff24c0abd7189a137a1415bf6) Support running superset via pex (#1713) (@yolken)
### airbnb_prod.0.13.0.1 (2016/12/01 19:59 +00:00)
- [2d0ebea](https://github.com/airbnb/superset/commit/2d0ebeae1bfd5e385000c9aa952891cf474821c6) [explorev2] Make chart container more responsive (#1724) (@vera-liu)
- [1a16491](https://github.com/airbnb/superset/commit/1a164919715d6eaf1a999b97daaa53acb94a827d) Display full table name (schema + name) if possible. (#1728) (@bkyryliuk)
- [7f4f250](https://github.com/airbnb/superset/commit/7f4f25097046dac9b436ef87f041debe2713827a) Redirects to login page if user not logged in at welcome page (#1723) (@vera-liu)
- [25acb78](https://github.com/airbnb/superset/commit/25acb78071acc2eec8b44cb7019f269c1b2a6deb) Pass schema to the select star query. (#1714) (@bkyryliuk)
- [e822d5a](https://github.com/airbnb/superset/commit/e822d5a1b7eb8f0cabcfcc85f5201df8199db796) Make edit / add / delete perms available to all users. (#1722) (@bkyryliuk)
- [32fc0ff](https://github.com/airbnb/superset/commit/32fc0ff6d0b437766d16db128a7b1d40a09080bb) [Bugfix] autocomplete in sqleditor doesnot use newly loaded table columns (#1712) (@vera-liu)
### 0.14.1 (2016/11/29 23:57 +00:00)
- [94dde07](https://github.com/airbnb/superset/commit/94dde075b3eab41797725e1e02c7f87b6b45471a) v0.14.1 (@mistercrunch)
- [65e9232](https://github.com/airbnb/superset/commit/65e92327abdd0d521a9dcb65319165b163da356c) Druid hotfix. (#1710) (@bkyryliuk)
- [0be02e6](https://github.com/airbnb/superset/commit/0be02e67a554d323efe4ed119a59bba53c559477) Updating CHANGELOG 0.14.0 (@mistercrunch)
- [7327c97](https://github.com/airbnb/superset/commit/7327c97e4c5efcf7c5b080a1e534d7b44129bb8b) v0.14.0 (@mistercrunch)
### 0.14.0 (2016/11/29 23:03 +00:00)
- [03b21dc](https://github.com/airbnb/superset/commit/03b21dcf0a3fc18e1290f7770004d3b74df8cef3) [explorev2] Bug fixes in Save Modal (#1707) (@vera-liu)
- [dc98c67](https://github.com/airbnb/superset/commit/dc98c6739fcccc8edc60ef7e761cb1491005f644) Implement table name extraction. (#1598) (@bkyryliuk)
- [fcb8707](https://github.com/airbnb/superset/commit/fcb870728db69bbee092d20c3f78cb7785fe2e61) Add per schema permissions. (#1698) (@bkyryliuk)
- [7919428](https://github.com/airbnb/superset/commit/7919428a1e02457a50ae00439e827f996403f71c) Vliu explorev2 bugs (#1701) (@vera-liu)
- [3496a80](https://github.com/airbnb/superset/commit/3496a80f5a85a0b66e59ec259ed13ca9ba3d5ba0) make stack trace more readable (#1672) (@ascott)
- [56b917a](https://github.com/airbnb/superset/commit/56b917a5c206d3083d9d9d3d0606b976c64b6044) [explore-v2] fix errors on table view (#1675) (@ascott)
- [18c43aa](https://github.com/airbnb/superset/commit/18c43aaea2f889e50211b22f0a68269f314bcafa) make chart title larger, fix explore actions btn spacing (#1680) (@ascott)
- [c43fc38](https://github.com/airbnb/superset/commit/c43fc38f69d6284729cd47368e796117adcc1d1b) [druid] fix having clause (#1694) (@mistercrunch)
- [c07f0ab](https://github.com/airbnb/superset/commit/c07f0ab9c72430f5892f701d6cba35718ef322ad) Config programmatic roles in the config.py (#1664) (@bkyryliuk)
- [1c429b2](https://github.com/airbnb/superset/commit/1c429b27bc425aa8ba0f8cc6b43887cfb91dcd15) Fixing issue #1689 (#1696) (@mistercrunch)
- [b7019ad](https://github.com/airbnb/superset/commit/b7019ad4f343ecbd5d33ce4a5800a72a9f4301b6) [sqllab] bugfix SouthPane doesn't update as expected (#1699) (@mistercrunch)
- [84e8f74](https://github.com/airbnb/superset/commit/84e8f741ae969888c4f2501ada132f58bdcfb249) Add 'Save As' feature for dashboards (#1669) (@the-dcruz)
- [e3a9b39](https://github.com/airbnb/superset/commit/e3a9b393c26ab173fe3ffe3dd14191705cab7119) Missing merge_perm function. Fixes 1691. (#1692) (@niconoe)
- [16aba51](https://github.com/airbnb/superset/commit/16aba517e4640300c9a71f6186776671540bc488) Use smaller size for node max_old_space_size (#1679) (@xrmx)
- [205928e](https://github.com/airbnb/superset/commit/205928e6df892060cdd3ffe0af6a1217a848f301) docs: fix python-redis link markup (#1683) (@xrmx)
- [39ce4aa](https://github.com/airbnb/superset/commit/39ce4aa049fffef3b9f6e368d64130ae85cb86d8) Added filter in ControlPanelsContainer for explore V2 (#1647) (@vera-liu)
- [cef4a82](https://github.com/airbnb/superset/commit/cef4a8296a6a9d46503dd63e268be3a35e9e8e91) [sqllab] adding a sql preprocessor for Presto (#1670) (@mistercrunch)
- [b370ef0](https://github.com/airbnb/superset/commit/b370ef0229377c6b85f78d9ba080d00ff6dba58e) Rerender chart without clicking query button for fields (#1658) (@vera-liu)
- [6b80f5b](https://github.com/airbnb/superset/commit/6b80f5bb35e497c79fe458b25ba87266e3c0f3bf) Get sections to render when switching datasource (#1660) (@vera-liu)
- [bdae570](https://github.com/airbnb/superset/commit/bdae570a69cd948987b05fed2e7653a221ef0d80) Temperary fix of a slice bug (#1648) (@vera-liu)
- [face524](https://github.com/airbnb/superset/commit/face5245a99d13089b9fa4cfa7521ee2ca6b209c) Make explore container resize with browser window (#1608) (@vera-liu)
- [db1ed2a](https://github.com/airbnb/superset/commit/db1ed2a765d317e55377f2550f169b78f981b4a0) Calculate height dynamically using jquery for scrollable sqllab (#1611) (@vera-liu)
- [10982de](https://github.com/airbnb/superset/commit/10982dec3c69f1bed709b38616417eada995d2f4) Make QueryTable scrollable in Query Search page (#1656) (@vera-liu)
- [6825e75](https://github.com/airbnb/superset/commit/6825e75681b1249d066d9fa0bf0dca9f1824bb24) Fixed bug with querylink passing sql object instead of string (#1659) (@vera-liu)
- [bd6a439](https://github.com/airbnb/superset/commit/bd6a439e0b2a3a76f8aece91f11a7eee2ebf6d29) [QuerySearch] Add loading status to QuerySearch page (#1657) (@vera-liu)
- [c90dd49](https://github.com/airbnb/superset/commit/c90dd4902f18bb11c46bc38b8f70bfc14cfc2171) Programatically sync the role with user list. (#1619) (@bkyryliuk)
- [868e5c4](https://github.com/airbnb/superset/commit/868e5c45fed8e090750dffe88660f3943f373c19) Redirect URL requests with "caravel" to "superset" (#1651) (@kingo55)
- [7e1852e](https://github.com/airbnb/superset/commit/7e1852ee883628d38b2e3bb71e2b2b03fad41ba3) User profile pages (favorites, created content, recent activity, security & access) (#1615) (@mistercrunch)
- [5ae98bc](https://github.com/airbnb/superset/commit/5ae98bc7c9b432683d03d30a30631a6efd7a78a3) Improving jinja2 security by using SandboxedEnvironment (#1632) (@mistercrunch)
- [1624e7d](https://github.com/airbnb/superset/commit/1624e7de7dd50f1c4f5fdd9153adac4ba5b983d2) Add all_tables endpoint to allow airpal / superset perm sync. (#1614) (@bkyryliuk)
- [7a98f84](https://github.com/airbnb/superset/commit/7a98f848909ca2099e29d3f485fd299037142e65) Admin / Alpha permission cleanup and fixes. (#1645) (@bkyryliuk)
- [9b18128](https://github.com/airbnb/superset/commit/9b181280d44171cb0c724a07f50488eb08f98e72) include jQuery and bootstrap (#1642) (@ascott)
- [38e94b9](https://github.com/airbnb/superset/commit/38e94b9e43f82c682f311fe1563c8f502ae4157a) Save modal component for explore v2 (#1612) (@vera-liu)
- [dc25bc6](https://github.com/airbnb/superset/commit/dc25bc6f4d5eeb74665dd353bafda5d97ef5faa1) Fix alpha permission checks. (#1641) (@bkyryliuk)
- [f64a205](https://github.com/airbnb/superset/commit/f64a2056038e96883e31419df5fcd4fa396dffb6) Use Alert for visualization error (#1639) (@vera-liu)
- [a8480f5](https://github.com/airbnb/superset/commit/a8480f54922775992a28edd7878b1cfa7690264e) Added Alert for ControlPanel and ChartContainer (#1626) (@vera-liu)
- [0acf26b](https://github.com/airbnb/superset/commit/0acf26b37c7a59cb976cf7a929caf7cc5a1a968e) Fixed a bug with switching viz_type in exploreV2 (#1631) (@vera-liu)
- [2c068a1](https://github.com/airbnb/superset/commit/2c068a1a1583fa61db2f1797b0fcb2618cd6dbe3) increase space between fieldsset rows (#1629) (@ascott)
- [b961c95](https://github.com/airbnb/superset/commit/b961c95121e5e4d4342a2926746dbf8a62bd77ea) dim visualization during refresh (#1636) (@mistercrunch)
- [8269321](https://github.com/airbnb/superset/commit/82693211f0545affbdc306561a1abb4478c2de9a) Update faq.rst (#1637) (@dodysw)
- [e546746](https://github.com/airbnb/superset/commit/e5467462cb73630a9b487891845ab1f01245f2a8) Make nvd3 refresh smoother. (#1618) (@the-dcruz)
- [ab5a410](https://github.com/airbnb/superset/commit/ab5a4102cd8921ca2df234bfa6133973ba83a425) [dashboard] give user feedback when there are unsaved changes (#1633) (@ascott)
- [d5ef937](https://github.com/airbnb/superset/commit/d5ef937b315f4afc679349369b4e7ac7455748f0) Fixed bugs with viz in exploreV2 (#1609) (@vera-liu)
- [bce02e3](https://github.com/airbnb/superset/commit/bce02e3f518237c03273e3ed4d9d1a13d9f8f6a9) [security] improving the security scheme (#1587) (@mistercrunch)
- [aad9744](https://github.com/airbnb/superset/commit/aad9744d85b50721d55d5770aad70ba1ee397ede) add new screenshots (#1589) (@ascott)
- [506b781](https://github.com/airbnb/superset/commit/506b781f3a6048b433c12d25c1dbce614b5bd31b) [explore-v2] add fave star and edit button to chart header (#1623) (@ascott)
- [267fd5b](https://github.com/airbnb/superset/commit/267fd5b9bc4f21a55c4664ae8c3ee717cc1be82c) [table viz] adding support for pagination (#1616) (@mistercrunch)
- [c362f28](https://github.com/airbnb/superset/commit/c362f2869e012a4eeb9b76ff654ee3e82a190979) More Dashboard UX unit tests (#1603) (@mistercrunch)
- [4f7f437](https://github.com/airbnb/superset/commit/4f7f43752798f57daa8cd8b8ed8a9cbc9c948000) Vliu put datasource in store (#1610) (@vera-liu)
- [ab5da5b](https://github.com/airbnb/superset/commit/ab5da5ba2811ac6c2350c7d0534dd209906318af) [table viz] allow sorting on any column (#1601) (@mistercrunch)
- [7531bb8](https://github.com/airbnb/superset/commit/7531bb89429547fb541c36fe365791cd742d82a1) Fixed dashboard controls for standalone bug (#1617) (@vera-liu)
- [811ee8c](https://github.com/airbnb/superset/commit/811ee8ccdc76a2630a4c8014df26558391b981fe) Deleted unused components in exploreV2 (#1613) (@vera-liu)
- [51cb485](https://github.com/airbnb/superset/commit/51cb485ce3e8cb80c72ec8c732281a78441396fd) Add standalone to reactified dashboard page (#1596) (@vera-liu)
- [83d08b8](https://github.com/airbnb/superset/commit/83d08b8b8f7c73cbf4de25cadeab93dd3fdfc2fc) Get query button working in explorev2 (#1581) (@vera-liu)
- [ed3d44d](https://github.com/airbnb/superset/commit/ed3d44d5919fc2ba739cf8d82e75e2680630646d) Changelog entries for 0.13.2 (@mistercrunch)
### 0.13.2 (2016/11/16 00:23 +00:00)
- [895fe23](https://github.com/airbnb/superset/commit/895fe23203a85a4590f84625507849ce63d69f30) v0.13.2 (@mistercrunch)
- [af04a56](https://github.com/airbnb/superset/commit/af04a560c887ecbcee40b53c358ee9c2ad2f44ad) Moved check to the correct place. (#1606) (@edevil)
- [9124a17](https://github.com/airbnb/superset/commit/9124a17e864b8b2eb109af33fe1b8aad809069da) Removing ascii_art.p from code coverage analysis (@mistercrunch)
- [99b0d4c](https://github.com/airbnb/superset/commit/99b0d4c111b66f6da0eb9991b54b375e2fbeecc4) Fix MySql time grain issue (#1590) (@mistercrunch)
- [84b98c2](https://github.com/airbnb/superset/commit/84b98c234f852550ecf536e4a6e7ce2d7ebc5df6) Adding Greenplum to supported dbs (@mistercrunch)
- [bcc1428](https://github.com/airbnb/superset/commit/bcc1428ebf1cf7e83c93e351858bee3cfbb2e9c2) Updating CODECLIMATE_REPO_TOKEN to new location (@mistercrunch)
- [2133056](https://github.com/airbnb/superset/commit/2133056c04d20807ea0c503d0fed235ee20e94bb) Added different Select Fields (#1583) (@vera-liu)
- [4155a9d](https://github.com/airbnb/superset/commit/4155a9d7f996d09ebdfc8df0db3dcbe9ccf9b529) Removing broken link to old docker image (#1591) (@kingo55)
- [ed48255](https://github.com/airbnb/superset/commit/ed4825523ca54309272f044826f383c2606456a1) Fixed a bug with new dashboard (#1585) (@vera-liu)
- [fdbb2bb](https://github.com/airbnb/superset/commit/fdbb2bbdab5e5dbb2f3496b67eb227dc3dc5f2a7) fixing the build (@mistercrunch)
- [c064d6d](https://github.com/airbnb/superset/commit/c064d6d8475b07a63a3b5ca7b4dbd248a437f6ed) Correct part_fields variable name (#1586) (@geraneum)
- [d33874b](https://github.com/airbnb/superset/commit/d33874bd3d9ffffca7f4726a29c3eb9de2a68d42) [hotfix] postgres issue when slice_id is missing (@mistercrunch)
- [96d32dd](https://github.com/airbnb/superset/commit/96d32dd11f29afa3590b79d8683aaddd05f48a02) Improve Druid metadata fetching resilience (#1584) (@mistercrunch)
- [d6bc354](https://github.com/airbnb/superset/commit/d6bc354ff3e2f24aeb459dbc1413371f2b072306) [hotfix] fix support for presto DATE and TIMESTAMP type (@mistercrunch)
- [7325a4f](https://github.com/airbnb/superset/commit/7325a4fb4ba08f554454534fe9efe3d0eea5a6ce) [hotfix] table view not group by without orderby fails (@mistercrunch)
- [90f00c5](https://github.com/airbnb/superset/commit/90f00c5b292ff83802d35bac49a26e6b257de409) Minor documentation touchups (@mistercrunch)
### 0.13.1 (2016/11/10 18:01 +00:00)
- [8539c42](https://github.com/airbnb/superset/commit/8539c423ea61d84e8e0a81317275713103f99a8a) v0.13.1 (@mistercrunch)
- [e9bfbfc](https://github.com/airbnb/superset/commit/e9bfbfce84b5ab851c839c70adf5298b2538e9dc) Removing boat pic from README (@mistercrunch)
- [6e4f066](https://github.com/airbnb/superset/commit/6e4f0664cb49d5e7144dadba7ccda548cf58e905) [hotfix] lint (@mistercrunch)
- [3c920c9](https://github.com/airbnb/superset/commit/3c920c9d943540cc8ed0d6e3dfd2ae0eba3acb70) [hotfix] datatables import issues (@mistercrunch)
- [15b67b2](https://github.com/airbnb/superset/commit/15b67b2c6c3c2982f6620fce5d30bd05951458f7) [WiP] rename project from Caravel to Superset (#1576) (@mistercrunch)
### 0.13.0 (2016/11/10 05:37 +00:00)
- [973537f](https://github.com/airbnb/superset/commit/973537fd9a60766a6ee99bd2e7080aa7db21f540) [hotfix] resizing widgets (@mistercrunch)
- [d70a744](https://github.com/airbnb/superset/commit/d70a74479df87908de7a7b4df7c24c6b267bf9e3) Make Sqllab a one-page app -- body not scrollable (#1551) (@vera-liu)
- [946e4b7](https://github.com/airbnb/superset/commit/946e4b750afeebbfa16e6ce7e9fc61575136b237) Reactifying the dashboard (#1572) (@mistercrunch)
- [9789e3f](https://github.com/airbnb/superset/commit/9789e3fb9b658e1f38080915132ae43c541e68e9) Bind data preview tabs to sql editor (#1573) (@vera-liu)
- [6a15679](https://github.com/airbnb/superset/commit/6a15679d876c5c76d177b624a5d69da80ac75a3f) [hotfix] encode csv to utf-8 (@mistercrunch)
- [ad1cd55](https://github.com/airbnb/superset/commit/ad1cd5577c231e4100f5a214fe7a4d372de96a04) Pass values from global store to fields in exploreV2 (#1561) (@vera-liu)
- [55668ca](https://github.com/airbnb/superset/commit/55668ca6217f52924288f3849a0aa54c28d40ce1) Link to database-urls in databaseadd view (#1480) (@dirkkelly)
- [0c221a2](https://github.com/airbnb/superset/commit/0c221a28d10abc8cf4e929e50fd788a56136a9cf) add slice_name and table_name for title (#1567) (@ascott)
- [a475551](https://github.com/airbnb/superset/commit/a475551b23d5830ab2945f615328f31d48df36ca) [sqllab] bind alt+enter shortcut in AceEditor to run a query (#1554) (@mistercrunch)
- [bad7676](https://github.com/airbnb/superset/commit/bad7676414662b28a4b72eb680fbf42a5c6281a5) Bump cryptography dependency to 1.5.3 (#1569) (@xrmx)
### airbnb_prod.0.12.0.1 (2016/11/08 23:55 +00:00)
- [51c0470](https://github.com/airbnb/superset/commit/51c0470f0be438312e90f2efb1f2e37291a30ce4) [explore v2] populate dynamic select field options (#1543) (@ascott)
- [4530047](https://github.com/airbnb/superset/commit/4530047c769ba6d5953ef1547b8507c62f657942) Added action buttons to Chart Container of explore V2 (#1562) (@vera-liu)
- [1bf83c3](https://github.com/airbnb/superset/commit/1bf83c3bf78de422df1b21c3049d106b1cb29385) [explore-v2] render columns based on length of fieldSets array (#1559) (@ascott)
- [bb6ab11](https://github.com/airbnb/superset/commit/bb6ab110013f2e29933fb9a70d6891d4486eb49d) Vliu link form data explore v2 (#1540) (@vera-liu)
- [e4bd188](https://github.com/airbnb/superset/commit/e4bd1884d34a1949b986410ebc385b75945afff8) [druid] adding support for dimensionspecs (#1545) (@mistercrunch)
- [4014a48](https://github.com/airbnb/superset/commit/4014a48f7df9fe166c77132f3d8d83cf615ac176) Added cache prop to ResultSet (#1552) (@vera-liu)
- [97ded32](https://github.com/airbnb/superset/commit/97ded32415e9e32ba2c6a7e4556a0ed96034244a) Update linting instructions. (#1478) (@pinkythalli97)
- [593ac08](https://github.com/airbnb/superset/commit/593ac081f06af5e95dc784597473d42aea52cf28) Added scroll bar and option to collapse for Sql Editor tool bar (#1532) (@vera-liu)
- [69f0a4e](https://github.com/airbnb/superset/commit/69f0a4e1cb2c7775617a15a5ba709836a568210c) Put data preview in south pane (#1486) (@vera-liu)
- [757e7de](https://github.com/airbnb/superset/commit/757e7de60cf7aa04f15160942ceaadb46daa15b5) add oracle time_grains (#1544) (@gschrader)
- [1d7d546](https://github.com/airbnb/superset/commit/1d7d5469a925690d4b4fd1e7a3cdf37779acffb6) [hotfix] remove failing Druid test (@mistercrunch)
- [98afc3e](https://github.com/airbnb/superset/commit/98afc3e590ef25d08fecf159bf4411ac292e95f0) Added setFilter(), containerID and getFilter() to (#1360) (@vera-liu)
- [ea18979](https://github.com/airbnb/superset/commit/ea189790f160dc6419ac97d8f6740afa62ba6e78) [hotfix] druid dist_bar viz issues with non-str x values (@mistercrunch)
- [6298707](https://github.com/airbnb/superset/commit/62987077fa31ebbedafbaa745b36527b7df0c9f7) Read the user origin specification. (#1541) (@bkyryliuk)
- [3b9f7cb](https://github.com/airbnb/superset/commit/3b9f7cb3f1d0dd1387d01204259b5ce7f2469793) [hotfix] groupby may be a set (@mistercrunch)
- [5882c7e](https://github.com/airbnb/superset/commit/5882c7e3447a06875e63b4424994a0a5e5fc58a5) Added jquery methods to ChartContainer to get world_map viz working in exploreV2 (#1443) (@vera-liu)
- [77b6e2c](https://github.com/airbnb/superset/commit/77b6e2cd2e796d063dbc6d42934143bf45bba8b0) Get pivot table working in explore v2 (#1432) (@vera-liu)
- [88b1f95](https://github.com/airbnb/superset/commit/88b1f956c7cd8bd457d04648447fb23700b859ae) [explore-v2] handle field overrides (#1535) (@ascott)
- [d9b49ca](https://github.com/airbnb/superset/commit/d9b49ca2bc1eab3221fc329d0ab5a9b421746110) [exploreV2] remove /exploreV2 endpoint, add v2 bootstrap data to /explore endpoint (#1536) (@ascott)
- [4156ad5](https://github.com/airbnb/superset/commit/4156ad5a3054f5194ea94b6fc146e53e4ccb0b57) [explore-v2] control panel fixes (#1529) (@ascott)
- [ae46561](https://github.com/airbnb/superset/commit/ae465616482c8e76634728bcd6990fcfb3b752ad) Support week_ending_saturday for Druid. (#1491) (@bkyryliuk)
- [1700a80](https://github.com/airbnb/superset/commit/1700a807e9a8ebc4cfb2749293308be733e42473) [sqllab] templating refactor (#1504) (@mistercrunch)
- [0bab15b](https://github.com/airbnb/superset/commit/0bab15b2132ae769ce81519f2d27858ad6d0b187) Update INTHEWILD.md (#1526) (@shashanksingh)
- [38d3075](https://github.com/airbnb/superset/commit/38d307555487d84b60dd6169006bbb6aa9fc87a5) [explore V2] render all control panels and fields dynamically for each vis type (#1493) (@ascott)
- [1b124bf](https://github.com/airbnb/superset/commit/1b124bfb87862a25a7b904e05929c23f0c61d0e2) [druid] optimize Druid queries where possible (#1517) (@mistercrunch)
- [cdf4dd0](https://github.com/airbnb/superset/commit/cdf4dd03024cff40f17845ab9b57ae6a6070e197) Add yearly and quarterly granularities to mysql engine backend (#1518) (@plumbeo)
- [a13bed2](https://github.com/airbnb/superset/commit/a13bed2db685a9fa913676bd71b570c81a190c6e) Moved sqllab tests from core_tests to sqllab_tests (#1502) (@vera-liu)
- [26318f9](https://github.com/airbnb/superset/commit/26318f94fef888a4b54a022bbbd44f269cfbf9b3) Moved queriesArray from render() to local state (#1505) (@vera-liu)
- [769fb08](https://github.com/airbnb/superset/commit/769fb0820fbefd1e58ced40fa79d2d7c923091e4) Strip sql and remove ; for csv download. (#1508) (@bkyryliuk)
- [5238053](https://github.com/airbnb/superset/commit/52380534f360cbb0e3d7ab46889066cd2df47440) Moved ajax call for fetching table metadata from SqlEditorLeftBar to actions (#1494) (@vera-liu)
- [2fd2526](https://github.com/airbnb/superset/commit/2fd252604689693c957a1b6875a18872a59d5ec8) Add support for jinja templates in WHERE/HAVING clauses (#1442) (@mistercrunch)
- [61509bb](https://github.com/airbnb/superset/commit/61509bbd446bbcc21f4f79229c88d82441d1fb98) [sqllab] surfacing more table metadata (indices, pk, fks) (#1485) (@mistercrunch)
- [76499af](https://github.com/airbnb/superset/commit/76499afd8d28289cac0cf0f2d7316e6e64bab089) [pep8] allowing 90 chars per line (@mistercrunch)
- [4023f32](https://github.com/airbnb/superset/commit/4023f328f7893fbe4b0e0af1612d598e1c931f72) [sqllab] run only the part of the query that is selected (#1479) (@mistercrunch)
- [4f49cb5](https://github.com/airbnb/superset/commit/4f49cb555be68c5e1daed817d1137fbd1c3f4e3f) Celery uses separate db engine with NullPool. (#1492) (@bkyryliuk)
- [4dc959a](https://github.com/airbnb/superset/commit/4dc959a3e4464ed38d4b5e580ae1fc009178e185) Revert "NullPool for the celery worker." (#1488) (@bkyryliuk)
- [518fbf5](https://github.com/airbnb/superset/commit/518fbf562cca638cf14d97dfae92756091630eb2) Minor Fixes (#1484) (@ronbak)
- [49828d3](https://github.com/airbnb/superset/commit/49828d3d9d51c02b13ce916a85cfe912bee43c54) add step to pypi build/push (@mistercrunch)
- [248e6a7](https://github.com/airbnb/superset/commit/248e6a7b05fd4ef30c4f2c006d0df935da84e052) fix name for postgresql (#1482) (@willgroves)
- [5561e6b](https://github.com/airbnb/superset/commit/5561e6b77086ffafb58c363a22e97a5800590e47) Fix celery module import in comments. (#1474) (@bkyryliuk)
- [ab083b8](https://github.com/airbnb/superset/commit/ab083b86f35c6f01dfa16bf84c8c212bf21743cf) [sqllab] slide animations when adding/removing/toggling TableElement (#1472) (@mistercrunch)
- [4bf5252](https://github.com/airbnb/superset/commit/4bf525222a609320acc28232f25b7651c54cfda0) [sqllab] add autocomplete to AceEditor for table and column names (#1475) (@mistercrunch)
- [45efcb3](https://github.com/airbnb/superset/commit/45efcb381c0d0b53b9de72a3437ec980b201bab0) Added time filter to query search page (#1329) (@vera-liu)
- [07a7736](https://github.com/airbnb/superset/commit/07a7736c71050c20dc04661ab3f1d21f58cb3b39) NullPool for the celery worker. (#1465) (@bkyryliuk)
- [d2826ab](https://github.com/airbnb/superset/commit/d2826ab7af4da1b36816150b71002ed966c553cd) Added checkbox in dist_bar viz to enable sorting of bars based on x axis labels (#1379) (@vera-liu)
- [6ab769f](https://github.com/airbnb/superset/commit/6ab769f38227788fbffb1eadb0e43c656f7c1da0) CHANGELOG for 0.12.0 (@mistercrunch)
### 0.12.0 (2016/10/28 16:40 +00:00)
- [3e1cd2b](https://github.com/airbnb/caravel/commit/3e1cd2bdcabce219dc01c6ce7b80850ecd50f9ba) v0.12.0 (@mistercrunch)
- [22784b7](https://github.com/airbnb/caravel/commit/22784b7f069d59e3fa7df03cfea84df9e147af13) run_specific_test: take the test as parameter (#1469) (@xrmx)
- [c492261](https://github.com/airbnb/caravel/commit/c4922615eb707228cdc1badd49bf2d293c74a699) [sqllab] add column sort feature to TableElement (#1467) (@mistercrunch)
- [7307dda](https://github.com/airbnb/caravel/commit/7307ddad3c9b60ec2178286cbc27a302e158de83) Highlight affected slices for filter change in dashboard view (#1439) (@vera-liu)
- [c7ba143](https://github.com/airbnb/caravel/commit/c7ba143d039aff61302017329860cd3db432b0f6) Fix typo in sqllab docs (@mistercrunch)
- [b242063](https://github.com/airbnb/caravel/commit/b24206387b09946df40a834db2d3b1deae21f865) [sqllab] optimizing React (#1438) (@mistercrunch)
- [64d1964](https://github.com/airbnb/caravel/commit/64d196442fcd17f098ba6d71da8bad1f84bdbb5a) Added dashboard standalone page (#1429) (@vera-liu)
- [5944643](https://github.com/airbnb/caravel/commit/5944643da67acb1ec38ed8d52f9ae6f58d7549ac) [sqllab] add support for Jinja templating (#1426) (@mistercrunch)
- [8c5e495](https://github.com/airbnb/caravel/commit/8c5e4952727d7ff6e96e61c1a38de2e600b02208) Add github issue template (#1436) (@xrmx)
- [bb23685](https://github.com/airbnb/caravel/commit/bb23685b9db623be89714153361ee3b03b46a39d) Added average metric AVG() to default metrics (#1413) (@vera-liu)
- [940659b](https://github.com/airbnb/caravel/commit/940659bc14ed276a68478b66055356cf3b48ba29) [sqllab] some frontend tests (#1400) (@mistercrunch)
- [7c59337](https://github.com/airbnb/caravel/commit/7c5933732bcb199e486ac8c4bf7ecec71013117d) Filter immune slices array stores strings. (#1402) (@bkyryliuk)
- [89df2fc](https://github.com/airbnb/caravel/commit/89df2fcf76bdaca34dfef19a9d5bb752a9859c6d) Adjusted top margin of heatmap plot to get it working in V2 (#1361) (@vera-liu)
- [174a199](https://github.com/airbnb/caravel/commit/174a199c30ad6c24505092975b814688e8a37292) [hotfix] Query search is unreachable (@mistercrunch)
- [6f1e7c3](https://github.com/airbnb/caravel/commit/6f1e7c3016b53cf8d3c0e28dba3538861fe56086) Added url shortner for sharing query link (#1314) (@vera-liu)
- [9f81e23](https://github.com/airbnb/caravel/commit/9f81e23f8f0698c94a9f4c34b49cbf3dda1e2e87) Fixed css class not being used by slice container (#1359) (@vera-liu)
- [19fab6e](https://github.com/airbnb/caravel/commit/19fab6eea71bbd97974a0ef8dd74451011f3862f) Get table viz work in explore v2: Added d3 format to mock slice (#1353) (@vera-liu)
- [63161b1](https://github.com/airbnb/caravel/commit/63161b11c347d5a6d62f7ae7dc91fa3c30b5dc93) [sqllab] proper, quoted, select * on the server side (#1404) (@mistercrunch)
- [4f886d6](https://github.com/airbnb/caravel/commit/4f886d65ecc208149cf9b7663492a81df868dcc2) Fix None view_menues in permissions. (#1409) (@bkyryliuk)
- [62e0e19](https://github.com/airbnb/caravel/commit/62e0e195e8eaa53afa41a5fec89cc4486a7114a3) [docfix] d3.format docs have moved (@mistercrunch)
- [e9d4749](https://github.com/airbnb/caravel/commit/e9d4749f4470aee294f1351baa9422173a2a62c8) [hotfix] sqllab presto (@mistercrunch)
- [93f8e7d](https://github.com/airbnb/caravel/commit/93f8e7d8e9b25a1aaf07e2b0c3772c327e523f0e) Fix the js build running out of heap space (#1408) (@mistercrunch)
- [3dea6e0](https://github.com/airbnb/caravel/commit/3dea6e0da538ed1c0a7761ccbf97bd8f12ac7f2f) [sqllab] adding more descriptive labels to left panel (#1407) (@mistercrunch)
- [6fb3b30](https://github.com/airbnb/caravel/commit/6fb3b305ad23c27d0555ded2ab80820000fdec50) [sqllab] add support for results backends (#1377) (@mistercrunch)
- [7dfe891](https://github.com/airbnb/caravel/commit/7dfe891cc1d294bd55982d63c9c1eb8f9e9e4c25) [hotfix] timeseries_limit_metric: Not a valid choice (@mistercrunch)
- [5c3966a](https://github.com/airbnb/caravel/commit/5c3966a32d454a2fc7fcabd75c66594662361586) Override the role with perms for given datasources. (#1399) (@bkyryliuk)
- [c198535](https://github.com/airbnb/caravel/commit/c198535292869ae1ced6d66d08de536188be7e05) Change slice ids in the position json during dashboard import. (#1380) (@bkyryliuk)
- [ece69fb](https://github.com/airbnb/caravel/commit/ece69fbb75086cb8855789881a32c6daca4be483) Fix migration for make creator owners (#1262) (@ShengyaoQian)
- [458651f](https://github.com/airbnb/caravel/commit/458651fa3e0098f35412772b622e7c0b63d34299) Add parens for custom where and having (#1337) (@yejianye)
- [b2f7081](https://github.com/airbnb/caravel/commit/b2f7081c6f3dde80846493330f1ec1e5fa3a414c) bumping versions of JS packages to latest (#1352) (@mistercrunch)
- [c255e89](https://github.com/airbnb/caravel/commit/c255e89219c209b5a5371134fbb9a9b90036ded4) [sqllab] show partition metadata for Presto (#1342) (@mistercrunch)
- [2edce5b](https://github.com/airbnb/caravel/commit/2edce5bf8afa7b74fdafd8ab8a2e6394d46e6391) Enable "Run Query in New Tab" in SQL Lab (#1343) (@nickbarnwell)
- [8f29944](https://github.com/airbnb/caravel/commit/8f299448ea954f75f576399f6bc113dd8ac1c824) [bugfix] text as subquery fails with 'Series Limit' (#1347) (@mistercrunch)
- [ecb951b](https://github.com/airbnb/caravel/commit/ecb951bb7474b9c829d0eef12792f6c146757dba) Specify the metric to order by for Series Limit (#1351) (@mistercrunch)
- [0dff6a9](https://github.com/airbnb/caravel/commit/0dff6a9030d8f2ff7a03654258f3b02f01d9d57e) Add quarter time grain to postgresql (#1362) (@xrmx)
- [2095095](https://github.com/airbnb/caravel/commit/2095095895d9cf5bc8131fb51fbb4868fae124dc) Fixed big number issue (#1355) (@vera-liu)
- [4fc8a17](https://github.com/airbnb/caravel/commit/4fc8a17f2ae18dcc2be778e52b3d3ed7be29e95a) [hotfix] use instead of prod for Travis build, take 3 (@mistercrunch)
- [3cb737f](https://github.com/airbnb/caravel/commit/3cb737f8c890b018bd6b3275047a02f79abb7444) [hotfix] use instead of prod for Travis build, take2 (@mistercrunch)
- [7449aa8](https://github.com/airbnb/caravel/commit/7449aa813b8b695e6dc3b06eb25004bc2a55614f) [hotfix] use instead of prod for Travis build (@mistercrunch)
- [7a3bcc2](https://github.com/airbnb/caravel/commit/7a3bcc227cccdec756404ab6140a6ba0d3882419) [bugfix] NaN issue in Big Number viz (#1346) (@mistercrunch)
- [b669a14](https://github.com/airbnb/caravel/commit/b669a140816b950237b7d66b73ac6af392f28d8a) [explore-v2] make chart container work with existing visualization files (#1333) (@ascott)
- [9db4cc8](https://github.com/airbnb/caravel/commit/9db4cc8c6d0422f28357dc4283089ee3dde9a08e) add node/npm versions to contributing.md (#1344) (@ascott)
- [65c744f](https://github.com/airbnb/caravel/commit/65c744f2424d111e950b385630bd8a13b3fa003e) Fix utc time calculation if provided datetime has tz info (#1287) (@labeneator)
- [82bcadf](https://github.com/airbnb/caravel/commit/82bcadf7f829183fbf6feca81dcc6c69f08c358c) Moving 'CSS TEmplates' to the Manage menu category (#1336) (@mistercrunch)
- [f0f8478](https://github.com/airbnb/caravel/commit/f0f8478922cbe8e4ac754fddcfd6183032bbb893) Revert "Override the role with perms for give datasources." (#1345) (@bkyryliuk)
- [40e7057](https://github.com/airbnb/caravel/commit/40e7057bcec55421275d0121d365a2e4f2d38a29) Override the role with perms for give datasources. (#1335) (@bkyryliuk)
- [11a8e35](https://github.com/airbnb/caravel/commit/11a8e3591d281a8cde6fb0ab57ed54ec97035e23) Some dashboard import/export fixes. (#1340) (@bkyryliuk)
- [5bea398](https://github.com/airbnb/caravel/commit/5bea3986b26359eb98c62246c34729e2d0cc5a1b) [hotfix] handling json errors in explore view (@mistercrunch)
- [89cb726](https://github.com/airbnb/caravel/commit/89cb726284b2a4f2707f26d3004ef9ae59b33970) [hotfix] explore errors are not raise properly 2 (@mistercrunch)
- [4e9392d](https://github.com/airbnb/caravel/commit/4e9392d21bf7f43e8189eb4478cf9d737c3616bd) [hotfix] explore errors are not raise properly (@mistercrunch)
- [b785d27](https://github.com/airbnb/caravel/commit/b785d27241e9f22dcb6a880414500ce8f9e67eb3) Taking out object spread operator (#1311) (@vera-liu)
- [451860a](https://github.com/airbnb/caravel/commit/451860afcab971a0a16b521a7c884d1c4d910986) remove #app styling (#1312) (@ascott)
- [4cf4e38](https://github.com/airbnb/caravel/commit/4cf4e3805c4d0a217fa6232ba1d6ee3a68c86ed2) Bugfix when there's only date filter in FilterBox (no groupby) (#1326) (@yejianye)
- [ef2670c](https://github.com/airbnb/caravel/commit/ef2670ca32e8ba0a04f9dc7899db55812e2f9b46) Using inheritance scheme to organize db specific code (#1294) (@mistercrunch)
- [8626c80](https://github.com/airbnb/caravel/commit/8626c80d3a491b842f8262c5807154903c107747) Stop duplicating datasources (#1321) (@bkyryliuk)
- [5cb3cc2](https://github.com/airbnb/caravel/commit/5cb3cc2ed8c8c2f0295da7ecdf8ec33a526acee1) polyfill es2015 in older browsers and for phantomjs (#1323) (@ascott)
- [73cd2ea](https://github.com/airbnb/caravel/commit/73cd2ea3b17574f8fef1112aa5e5b39f843882f6) Import / export of the dashboards. (#1197) (@bkyryliuk)
- [cd2ab42](https://github.com/airbnb/caravel/commit/cd2ab42abcd2ea5f93dd285321c448c97abf4580) do not overwrite the stored password with the masked password (#1209) (@dennisobrien)
- [bf1f5ea](https://github.com/airbnb/caravel/commit/bf1f5ea3de23a62eca4f823474119e4c15270738) [sqllab] use encodeURIComponent for copy query URL (#1317) (@mistercrunch)
- [79460ab](https://github.com/airbnb/caravel/commit/79460abdd230b2d53a9f2da15ac5160ba6db400b) [SQLLab] Fix the usage of Redux DevTools Enhancer (#1278) (@zalmoxisus)
- [1e6e144](https://github.com/airbnb/caravel/commit/1e6e144d24da38aef0d306889dc3b8f962ca4b32) Fixed viewing dashboards as anonymous (#1320) (@Rapsutin)
- [fe66557](https://github.com/airbnb/caravel/commit/fe66557bbb4848feed762e859abcd5884d72d369) [explore-v2] hook up ExploreViewContainer to state and add specs (#1300) (@ascott)
- [f8e2ce6](https://github.com/airbnb/caravel/commit/f8e2ce6ff367047405be60d7cbcfd68e4f4521d8) Change status color in tab to match with success (#1247) (@vera-liu)
- [1967743](https://github.com/airbnb/caravel/commit/19677438c23a9beab907b5b51d99f54ef62c8f92) Add cascade delete to the 1 to composite relationships. (#1295) (@bkyryliuk)
- [9012b11](https://github.com/airbnb/caravel/commit/9012b11101805c9810745f049363223f110bc93e) add ImmutableMultiDict back to views.py (#1298) (@ascott)
- [f70d301](https://github.com/airbnb/caravel/commit/f70d301f0d37fe6c4f94cd7d6026a72a56087d34) Refactor the explore view (#1252) (@mistercrunch)
- [b7d1f78](https://github.com/airbnb/caravel/commit/b7d1f78f5e38bdcc99c7b6a15645092f5d76ca4a) Put formData in store (#1281) (@vera-liu)
- [3384e75](https://github.com/airbnb/caravel/commit/3384e7598ee9e82f8b83ae71e3e5b587c6546443) Fixing explore actions & slice controller interactions (#1292) (@mistercrunch)
- [382b8e8](https://github.com/airbnb/caravel/commit/382b8e85da36ed1a8f235775b883e54f5db90ab7) [explore v2] add scrollbar to control panel container (#1284) (@ascott)
- [0a3121c](https://github.com/airbnb/caravel/commit/0a3121c2438f74ab1bd3730eac7a11bad0b317f5) [doc] installation, load examples before init (@mistercrunch)
- [ecfe1a2](https://github.com/airbnb/caravel/commit/ecfe1a241786bc1fae1d5d3c45b45e21703e33a7) Updated eslinter for object rest spread (#1289) (@vera-liu)
- [609ae22](https://github.com/airbnb/caravel/commit/609ae22bdabfb9a9cb99d1a4bfba62367f03a8d5) less number of default workers. (#1206) (@StefanoOrdine)
- [94578cb](https://github.com/airbnb/caravel/commit/94578cb6a7e91cb4ca741a8f437636d264b56322) reduce chunk size for countries table (#1279) (@vivo75)
- [8a5f050](https://github.com/airbnb/caravel/commit/8a5f050f6cbf2a4eca7f2799a244f94218bb11c1) [explore v2] fix explorev2 chart errors (#1277) (@ascott)
- [5c5b393](https://github.com/airbnb/caravel/commit/5c5b393f2fd7be5a4496c0ea0a0808f720fdc904) Change userId, dbId to username and dbname (#1274) (@vera-liu)
- [f837733](https://github.com/airbnb/caravel/commit/f837733d858643152304469c899c096e31d2bf24) [explorev2] chart and controls (#1251) (@ascott)
- [66b498d](https://github.com/airbnb/caravel/commit/66b498de25b6bb3145eb441f64ef7fdaffc5a747) Added controls for Table Viz (#1253) (@vera-liu)
- [659bf6d](https://github.com/airbnb/caravel/commit/659bf6d7e80d70def5b6c46a9d8a3a262e37a13e) Moved time column and grains to models.py (#1255) (@vera-liu)
- [a8a1690](https://github.com/airbnb/caravel/commit/a8a16900e7497083f14de3b6dc50063f845401b7) docs: add libsasl as system requirement on linux (#1257) (@xrmx)
- [e50b59e](https://github.com/airbnb/caravel/commit/e50b59e5535bc653e3321a2630287540ef99df86) docs: document that gunicorn does not work on windows (#1258) (@xrmx)
- [231804e](https://github.com/airbnb/caravel/commit/231804e2b47771ed6b31cb48e36c2f417973cad1) CHANGELOG: Add proper credit to tan31989 for #744 (#1259) (@xrmx)
- [421a86a](https://github.com/airbnb/caravel/commit/421a86ade5ac5ec4d147bdfb913ce32a7004b455) Some polish on query search (#1222) (@vera-liu)
- [140a055](https://github.com/airbnb/caravel/commit/140a055e4ef85080bbe3e7defdfeb2dd7b828b58) [docs] add line in installation instructions (@mistercrunch)
- [5bf86d9](https://github.com/airbnb/caravel/commit/5bf86d91ec9911e6155335ff1f97dc48c2c16a21) [docs] suggest to upgrade pip and setuptools (@mistercrunch)
- [715cdd9](https://github.com/airbnb/caravel/commit/715cdd98fb213e06c687ee8587ded9437039fb2d) Changelog for 0.11.0 (@mistercrunch)
### 0.11.0 (2016/10/05 04:27 +00:00)
- [7a01d9d](https://github.com/airbnb/caravel/commit/7a01d9dbcbbd7678960c33c8cca1b13680290ac7) v0.11.0 (@mistercrunch)
- [58dfa43](https://github.com/airbnb/caravel/commit/58dfa436ee53aac185b457e721bfe2303b8d006a) Do not shadow _ function. (#1254) (@bkyryliuk)

View File

@@ -30,8 +30,8 @@ Look through the GitHub issues for features. Anything tagged with
### Documentation
Caravel could always use better documentation,
whether as part of the official Caravel docs,
Superset could always use better documentation,
whether as part of the official Superset docs,
in docstrings, `docs/*.rst` or even on the web as blog posts or
articles.
@@ -47,18 +47,105 @@ If you are proposing a feature:
- Remember that this is a volunteer-driven project, and that
contributions are welcome :)
## Latest Documentation
## Documentation
Latest documentation and tutorial are available [here](http://airbnb.io/caravel)
The latest documentation and tutorial are available [here](http://airbnb.io/superset).
Contributing to the official documentation is relatively easy, once you've setup
your environment and done an edit end-to-end. The docs can be found in the
`docs/` subdirectory of the repository, and are written in the
[reStructuredText format](https://en.wikipedia.org/wiki/ReStructuredText) (.rst).
If you've written Markdown before, you'll find the reStructuredText format familiar.
Superset uses [Sphinx](http://www.sphinx-doc.org/en/1.5.1/) to convert the rst files
in `docs/` to the final HTML output users see.
Before you start changing the docs, you'll want to
[fork the Superset project on Github](https://help.github.com/articles/fork-a-repo/).
Once that new repository has been created, clone it on your local machine:
git clone git@github.com:your_username/superset.git
At this point, you may also want to create a
[Python virtual environment](http://docs.python-guide.org/en/latest/dev/virtualenvs/)
to manage the Python packages you're about to install:
virtualenv superset-dev
source superset-dev/bin/activate
Finally, to make changes to the rst files and build the docs using Sphinx,
you'll need to install a handful of dependencies from the repo you cloned:
cd superset
pip install -r dev-reqs-for-docs.txt
To get the feel for how to edit and build the docs, let's edit a file, build
the docs and see our changes in action. First, you'll want to
[create a new branch](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging)
to work on your changes:
git checkout -b changes-to-docs
Now, go ahead and edit one of the files under `docs/`, say `docs/tutorial.rst`
- change it however you want. Check out the
[ReStructuredText Primer](http://docutils.sourceforge.net/docs/user/rst/quickstart.html)
for a reference on the formatting of the rst files.
Once you've made your changes, run this command from the root of the Superset
repo to convert the docs into HTML:
python setup.py build_sphinx
You'll see a lot of output as Sphinx handles the conversion. After it's done, the
HTML Sphinx generated should be in `docs/_build/html`. Go ahead and navigate there
and start a simple web server so we can check out the docs in a browser:
cd docs/_build/html
python -m SimpleHTTPServer
This will start a small Python web server listening on port 8000. Point your
browser to [http://localhost:8000/](http://localhost:8000/), find the file
you edited earlier, and check out your changes!
If you've made a change you'd like to contribute to the actual docs, just commit
your code, push your new branch to Github:
git add docs/tutorial.rst
git commit -m 'Awesome new change to tutorial'
git push origin changes-to-docs
Then, [open a pull request](https://help.github.com/articles/about-pull-requests/).
If you're adding new images to the documentation, you'll notice that the images
referenced in the rst, e.g.
.. image:: _static/img/tutorial/tutorial_01_sources_database.png
aren't actually included in that directory. _Instead_, you'll want to add and commit
images (and any other static assets) to the _superset/assets/images_ directory.
When the docs are being pushed to [airbnb.io](http://airbnb.io/superset/), images
will be moved from there to the _\_static/img_ directory, just like they're referenced
in the docs.
For example, the image referenced above actually lives in
superset/assets/images/tutorial
Since the image is moved during the documentation build process, the docs reference the
image in
_static/img/tutorial
instead.
## Setting up a Python development environment
Check the [OS dependencies](http://airbnb.io/caravel/installation.html#os-dependencies) before follows these steps.
Check the [OS dependencies](http://airbnb.io/superset/installation.html#os-dependencies) before follows these steps.
# fork the repo on GitHub and then clone it
# alternatively you may want to clone the main repo but that won't work
# so well if you are planning on sending PRs
# git clone git@github.com:airbnb/caravel.git
# git clone git@github.com:airbnb/superset.git
# [optional] setup a virtual env and activate it
virtualenv env
@@ -68,30 +155,30 @@ Check the [OS dependencies](http://airbnb.io/caravel/installation.html#os-depend
python setup.py develop
# Create an admin user
fabmanager create-admin --app caravel
fabmanager create-admin --app superset
# Initialize the database
caravel db upgrade
superset db upgrade
# Create default roles and permissions
caravel init
superset init
# Load some data to play with
caravel load_examples
superset load_examples
# start a dev web server
caravel runserver -d
superset runserver -d
## Setting up the node / npm javascript environment
`caravel/assets` contains all npm-managed, front end assets.
`superset/assets` contains all npm-managed, front end assets.
Flask-Appbuilder itself comes bundled with jQuery and bootstrap.
While these may be phased out over time, these packages are currently not
managed with npm.
### Node/npm versions
Make sure you are using recent versions of node and npm. No problems have been found with node>=5.10 and npm>=3.9.
Make sure you are using recent versions of node and npm. No problems have been found with node>=5.10 and 4.0. > npm>=3.9.
### Using npm to generate bundled files
@@ -112,18 +199,21 @@ export PATH="$HOME/.npm-packages/bin:$PATH"
#### npm packages
To install third party libraries defined in `package.json`, run the
following within the `caravel/assets/` directory which will install them in a
following within the `superset/assets/` directory which will install them in a
new `node_modules/` folder within `assets/`.
```
npm install
```
To parse and generate bundled files for caravel, run either of the
To parse and generate bundled files for superset, run either of the
following commands. The `dev` flag will keep the npm script running and
re-run it upon any changes within the assets directory.
```
# Copies a conf file from the frontend to the backend
npm run sync-backend
# Compiles the production / optimized js & css
npm run prod
@@ -135,7 +225,7 @@ For every development session you will have to start a flask dev server
as well as an npm watcher
```
caravel runserver -d -p 8081
superset runserver -d -p 8081
npm run dev
```
@@ -147,7 +237,7 @@ Python tests can be run with:
We use [Mocha](https://mochajs.org/), [Chai](http://chaijs.com/) and [Enzyme](http://airbnb.io/enzyme/) to test Javascript. Tests can be run with:
cd /caravel/caravel/assets/javascripts
cd /superset/superset/assets/javascripts
npm i
npm run test
@@ -157,13 +247,13 @@ Lint the project with:
# for python changes
flake8 changes tests
flake8 changes caravel
flake8 changes superset
# for javascript
npm run lint
## Linting with codeclimate
Codeclimate is a service we use to measure code quality and test coverage. To get codeclimate's report on your branch, ideally before sending your PR, you can setup codeclimate against your Caravel fork. After you push to your fork, you should be able to get the report at http://codeclimate.com . Alternatively, if you prefer to work locally, you can install the codeclimate cli tool.
Codeclimate is a service we use to measure code quality and test coverage. To get codeclimate's report on your branch, ideally before sending your PR, you can setup codeclimate against your Superset fork. After you push to your fork, you should be able to get the report at http://codeclimate.com . Alternatively, if you prefer to work locally, you can install the codeclimate cli tool.
*Install the codeclimate cli tool*
```
@@ -193,12 +283,12 @@ Generate the documentation with:
cd docs && ./build.sh
## CSS Themes
As part of the npm build process, CSS for Caravel is compiled from `Less`, a dynamic stylesheet language.
As part of the npm build process, CSS for Superset is compiled from `Less`, a dynamic stylesheet language.
It's possible to customize or add your own theme to Caravel, either by overriding CSS rules or preferably
It's possible to customize or add your own theme to Superset, either by overriding CSS rules or preferably
by modifying the Less variables or files in `assets/stylesheets/less/`.
The `variables.less` and `bootswatch.less` files that ship with Caravel are derived from
The `variables.less` and `bootswatch.less` files that ship with Superset are derived from
[Bootswatch](https://bootswatch.com) and thus extend Bootstrap. Modify variables in these files directly, or
swap them out entirely with the equivalent files from other Bootswatch (themes)[https://github.com/thomaspark/bootswatch.git]
@@ -221,14 +311,14 @@ meets these guidelines:
## Translations
We use [Babel](http://babel.pocoo.org/en/latest/) to translate Caravel. The
We use [Babel](http://babel.pocoo.org/en/latest/) to translate Superset. The
key is to instrument the strings that need translation using
`from flask_babel import lazy_gettext as _`. Once this is imported in
a module, all you have to do is to `_("Wrap your strings")` using the
underscore `_` "function".
To enable changing language in your environment, you can simply add the
`LANGUAGES` parameter to your `caravel_config.py`. Having more than one
`LANGUAGES` parameter to your `superset_config.py`. Having more than one
options here will add a language selection dropdown on the right side of the
navigation bar.
@@ -241,23 +331,23 @@ navigation bar.
As per the [Flask AppBuilder documentation] about translation, to create a
new language dictionary, run the following command:
pybabel init -i ./babel/messages.pot -d caravel/translations -l es
pybabel init -i ./babel/messages.pot -d superset/translations -l es
Then it's a matter of running the statement below to gather all stings that
need translation
fabmanager babel-extract --target caravel/translations/
fabmanager babel-extract --target superset/translations/
You can then translate the strings gathered in files located under
`caravel/translation`, where there's one per language. For the translations
`superset/translation`, where there's one per language. For the translations
to take effect, they need to be compiled using this command:
fabmanager babel-compile --target caravel/translations/
fabmanager babel-compile --target superset/translations/
## Adding new datasources
1. Create Models and Views for the datasource, add them under caravel folder, like a new my_models.py
1. Create Models and Views for the datasource, add them under superset folder, like a new my_models.py
with models for cluster, datasources, columns and metrics and my_views.py with clustermodelview
and datasourcemodelview.
@@ -267,6 +357,6 @@ to take effect, they need to be compiled using this command:
For example:
`ADDITIONAL_MODULE_DS_MAP = {'caravel.my_models': ['MyDatasource', 'MyOtherDatasource']}`
`ADDITIONAL_MODULE_DS_MAP = {'superset.my_models': ['MyDatasource', 'MyOtherDatasource']}`
This means it'll register MyDatasource and MyOtherDatasource in caravel.my_models module in the source registry.
This means it'll register MyDatasource and MyOtherDatasource in superset.my_models module in the source registry.

View File

@@ -1,4 +1,4 @@
Please use [pull requests](https://github.com/airbnb/caravel/pull/new/master)
Please use [pull requests](https://github.com/airbnb/superset/pull/new/master)
to add your organization and/or project to this document!
Organizations
@@ -8,6 +8,12 @@ Organizations
- [Maieutical Labs] (https://cloudschooling.it)
- [Shopkick] (https://www.shopkick.com)
- [Amino] (https://amino.com)
- [Faasos] (http://faasos.com/)
- [Clark.de] (http://clark.de/)
- [Yahoo!] (www.yahoo.com)
- [Digit Game Studios] (https://www.digitgaming.com/)
- [Brilliant.org] (https://brilliant.org/)
- [Qunar] (https://www.qunar.com/)
Projects
----------

19
ISSUE_TEMPLATE.md Normal file
View File

@@ -0,0 +1,19 @@
Make sure these boxes are checked before submitting your issue - thank you!
- [ ] I have checked the superset logs for python stacktraces and included it here as text if any
- [ ] I have reproduced the issue with at least the latest released version of superset
- [ ] I have checked the issue tracker for the same issue and I haven't found one similar
### Superset version
### Expected results
### Actual results
### Steps to reproduce

View File

@@ -1,9 +1,9 @@
recursive-include caravel/templates *
recursive-include caravel/static *
recursive-exclude caravel/static/assets/node_modules *
recursive-include caravel/static/assets/node_modules/font-awesome *
recursive-exclude caravel/static/docs *
recursive-exclude caravel/static/spec *
recursive-include superset/templates *
recursive-include superset/static *
recursive-exclude superset/static/assets/node_modules *
recursive-include superset/static/assets/node_modules/font-awesome *
recursive-exclude superset/static/docs *
recursive-exclude superset/static/spec *
recursive-exclude tests *
recursive-include caravel/data *
recursive-include caravel/migrations *
recursive-include superset/data *
recursive-include superset/migrations *

105
README.md
View File

@@ -1,47 +1,57 @@
Caravel
Superset
=========
<img src="http://i.imgur.com/H0Kyvyi.jpg" style="border-radius: 20px; box-shadow:5px 5px 5px gray;" alt="Caravel" width="500"/>
[![Build Status](https://travis-ci.org/airbnb/caravel.svg?branch=master)](https://travis-ci.org/airbnb/caravel)
[![PyPI version](https://badge.fury.io/py/caravel.svg)](https://badge.fury.io/py/caravel)
[![Coverage Status](https://coveralls.io/repos/airbnb/caravel/badge.svg?branch=master&service=github)](https://coveralls.io/github/airbnb/caravel?branch=master)
[![JS Test Coverage](https://codeclimate.com/github/airbnb/caravel/badges/coverage.svg)](https://codeclimate.com/github/airbnb/caravel/coverage)
[![Code Health](https://landscape.io/github/airbnb/caravel/master/landscape.svg?style=flat)](https://landscape.io/github/airbnb/caravel/master)
[![Code Climate](https://codeclimate.com/github/airbnb/caravel/badges/gpa.svg)](https://codeclimate.com/github/airbnb/caravel)
[![PyPI](https://img.shields.io/pypi/pyversions/caravel.svg?maxAge=2592000)](https://pypi.python.org/pypi/caravel)
[![Requirements Status](https://requires.io/github/airbnb/caravel/requirements.svg?branch=master)](https://requires.io/github/airbnb/caravel/requirements/?branch=master)
[![Join the chat at https://gitter.im/airbnb/caravel](https://badges.gitter.im/airbnb/caravel.svg)](https://gitter.im/airbnb/caravel?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation](https://img.shields.io/badge/docs-airbnb.io-blue.svg)](http://airbnb.io/caravel/)
[![dependencies Status](https://david-dm.org/airbnb/caravel/status.svg?path=caravel/assets)](https://david-dm.org/airbnb/caravel?path=caravel/assets)
[![Build Status](https://travis-ci.org/airbnb/superset.svg?branch=master)](https://travis-ci.org/airbnb/superset)
[![PyPI version](https://badge.fury.io/py/superset.svg)](https://badge.fury.io/py/superset)
[![Coverage Status](https://coveralls.io/repos/airbnb/superset/badge.svg?branch=master&service=github)](https://coveralls.io/github/airbnb/superset?branch=master)
[![JS Test Coverage](https://codeclimate.com/github/airbnb/superset/badges/coverage.svg)](https://codeclimate.com/github/airbnb/superset/coverage)
[![Code Health](https://landscape.io/github/airbnb/superset/master/landscape.svg?style=flat)](https://landscape.io/github/airbnb/superset/master)
[![Code Climate](https://codeclimate.com/github/airbnb/superset/badges/gpa.svg)](https://codeclimate.com/github/airbnb/superset)
[![PyPI](https://img.shields.io/pypi/pyversions/superset.svg?maxAge=2592000)](https://pypi.python.org/pypi/superset)
[![Requirements Status](https://requires.io/github/airbnb/superset/requirements.svg?branch=master)](https://requires.io/github/airbnb/superset/requirements/?branch=master)
[![Join the chat at https://gitter.im/airbnb/superset](https://badges.gitter.im/airbnb/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-airbnb.io-blue.svg)](http://airbnb.io/superset/)
[![dependencies Status](https://david-dm.org/airbnb/superset/status.svg?path=superset/assets)](https://david-dm.org/airbnb/superset?path=superset/assets)
Caravel is a data exploration platform designed to be visual, intuitive
<img
src="https://cloud.githubusercontent.com/assets/130878/20946612/49a8a25c-bbc0-11e6-8314-10bef902af51.png"
alt="Superset"
width="500"
/>
**Superset** is a data exploration platform designed to be visual, intuitive
and interactive.
[this project used to be named **Panoramix**]
[this project used to be named **Caravel**, and **Panoramix** in the past]
Screenshots & Gifs
------------------
![img](http://g.recordit.co/xFXSvaGUts.gif)
---
![img](http://g.recordit.co/uZggYOdR5g.gif)
**View Dashboards**
![superset-dashboard](https://cloud.githubusercontent.com/assets/130878/20371438/a703a2a0-ac19-11e6-80c4-00a47c2eb644.gif)
---
![img](http://g.recordit.co/U70FWLpLvh.gif)
<br/>
**View/Edit a Slice**
![superset-explore-slice](https://cloud.githubusercontent.com/assets/130878/20372732/410392f4-ac22-11e6-9c6d-3ef512e81212.gif)
---
![img](http://i.imgur.com/x8t30YU.png)
<br/>
**Query and Visualize with SQL Lab**
![superset-sql-lab-visualization](https://cloud.githubusercontent.com/assets/130878/20372911/7c3b3358-ac23-11e6-8f24-923ef1b35715.gif)
---
![img](http://i.imgur.com/DRCnbq6.png)
<br/>
![superset-dashboard-misc](https://cloud.githubusercontent.com/assets/130878/20234704/0f40778c-a835-11e6-9556-983a62ea061b.png)
Caravel
![superset-edit-table](https://cloud.githubusercontent.com/assets/130878/20234705/0f415c88-a835-11e6-8b03-f7c35d56dd7d.png)
![superset-query-search](https://cloud.githubusercontent.com/assets/130878/20234706/0f430a10-a835-11e6-8a0d-8b26cc2e6bbd.png)
Superset
---------
Caravel's main goal is to make it easy to slice, dice and visualize data.
Superset's main goal is to make it easy to slice, dice and visualize data.
It empowers users to perform **analytics at the speed of thought**.
Caravel provides:
Superset provides:
* A quick way to intuitively visualize datasets by allowing users to create
and share interactive dashboards
* A rich set of visualizations to analyze your data, as well as a flexible
@@ -54,7 +64,7 @@ Caravel provides:
displayed in the UI, by defining which fields should show up in
which dropdown and which aggregation and function (metrics) are
made available to the user
* Deep integration with Druid allows for Caravel to stay blazing fast while
* Deep integration with Druid allows for Superset to stay blazing fast while
slicing and dicing large, realtime datasets
* Fast loading dashboards with configurable caching
@@ -62,7 +72,7 @@ Caravel provides:
Database Support
----------------
Caravel was originally designed on top of Druid.io, but quickly broadened
Superset was originally designed on top of Druid.io, but quickly broadened
its scope to support other databases through the use of SQLAlchemy, a Python
ORM that is compatible with
[most common databases](http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html).
@@ -83,55 +93,50 @@ power analytic dashboards and applications.*
Installation & Configuration
----------------------------
[See in the documentation](http://airbnb.io/caravel/installation.html)
[See in the documentation](http://airbnb.io/superset/installation.html)
More screenshots
----------------
![img](http://i.imgur.com/SAhDJCI.png)
![superset-security-menu](https://cloud.githubusercontent.com/assets/130878/20234707/0f565886-a835-11e6-9277-b4f5f4aa2fcc.png)
---
![img](http://i.imgur.com/iuLpv1c.png)
![superset-slice-bubble](https://cloud.githubusercontent.com/assets/130878/20234708/0f57f3d0-a835-11e6-8268-fcefe8f868c8.png)
---
![img](http://i.imgur.com/V2FWeZx.png)
![superset-slice-map](https://cloud.githubusercontent.com/assets/130878/20234709/0f5a5a44-a835-11e6-987a-1b6f8ac9922b.png)
---
![img](http://i.imgur.com/BeUtCzF.png)
![superset-slice-multiline](https://cloud.githubusercontent.com/assets/130878/20234710/0f632d68-a835-11e6-98d1-542dcb618193.png)
---
![img](http://i.imgur.com/phoY7jI.png)
![superset-slice-sankey](https://cloud.githubusercontent.com/assets/130878/20234711/0f639136-a835-11e6-8721-fe5e48dab8e7.png)
---
![img](http://i.imgur.com/NvIDgdC.png)
![superset-slice-view](https://cloud.githubusercontent.com/assets/130878/20234712/0f63c4c6-a835-11e6-8595-6091a6428fa9.png)
---
![img](http://i.imgur.com/DzwYyns.png)
![superset-sql-lab-2](https://cloud.githubusercontent.com/assets/130878/20234713/0f67b856-a835-11e6-9d50-7a52168f66fd.png)
![superset-sql-lab](https://cloud.githubusercontent.com/assets/130878/20234714/0f68f45a-a835-11e6-9467-f47ad0af7e79.png)
Resources
-------------
* [Caravel Google Group](https://groups.google.com/forum/#!forum/airbnb_caravel)
* [Gitter (live chat) Channel](https://gitter.im/airbnb/caravel)
* [Docker image 1](https://hub.docker.com/r/kochalex/caravel/)
[Docker image 2](https://hub.docker.com/r/amancevice/caravel/) (community contributed)
* [Superset Google Group](https://groups.google.com/forum/#!forum/airbnb_superset)
* [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)
Tip of the Hat
--------------
Caravel would not be possible without these great frameworks / libs
Superset would not be possible without these great frameworks / libs
* Flask App Builder - Allowing us to focus on building the app quickly while
getting the foundation for free
* The Flask ecosystem - Simply amazing. So much Plug, easy play.
* NVD3 - One of the best charting libraries out there
* Much more, check out the `install_requires` section in the [setup.py](https://github.com/airbnb/caravel/blob/master/setup.py) file!
* Much more, check out the `install_requires` section in the [setup.py](https://github.com/airbnb/superset/blob/master/setup.py) file!
Contributing
------------
Interested in contributing? Casual hacking? Check out [Contributing.MD](https://github.com/airbnb/caravel/blob/master/CONTRIBUTING.md)
Interested in contributing? Casual hacking? Check out [Contributing.MD](https://github.com/airbnb/superset/blob/master/CONTRIBUTING.md)

View File

@@ -1,5 +1,5 @@
# TODO
List of TODO items for Caravel
List of TODO items for Superset
## Important
* **Getting proper JS testing:** unit tests on the Python side are pretty
@@ -7,7 +7,7 @@ List of TODO items for Caravel
testing all the ajax-type calls
* **Viz Plugins:** Allow people to define and share visualization plugins.
ideally one would only need to drop in a set of files in a folder and
Caravel would discover and expose the plugins
Superset would discover and expose the plugins
## Features
* **Dashboard URL filters:** `{dash_url}#fltin__fieldname__value1,value2`

View File

@@ -29,7 +29,7 @@ script_location = migrations
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = scheme://localhost/caravel
sqlalchemy.url = scheme://localhost/superset
# Logging configuration
[loggers]

View File

@@ -1,4 +1,4 @@
[ignore: caravel/assets/node_modules/**]
[python: caravel/**.py]
[jinja2: caravel/**/templates/**.html]
[ignore: superset/assets/node_modules/**]
[python: superset/**.py]
[jinja2: superset/**/templates/**.html]
encoding = utf-8

File diff suppressed because it is too large Load Diff

View File

@@ -1,82 +0,0 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
error = (
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM8OI++=~~~~~~=+?IODMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMMMMD$~~~~~~~~~~~~~~~~~~~~~~~=$MMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMN8?:~~~~~~~~~~~~~~~~~~~~~~~~~~=+8NMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMO=~~~~~~~~~~~~~~~~~+I??~~~~~~~~~~~~~+DMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMNI~~~~~~~~~~~~~~~~~~IIIII=~~~~~~~~~~~~~~=NMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMM+=~~~~~~~~~~~~~~~~~~~=III+~~~~~~~~~~~~~~~~~?8MMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+++=~~~~8MMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMI=~~~~~~~~~~~~~~~~~~~~~~~~~III?I~~~~~~~~,:++++++~~8MMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMN7~~~~~~~~~~~~~~~~==+=~~~~~~=IIIII~~~~~~:. ..:=++=~=MMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMO=~~~~~~~~~~~~~~~~+++=~~~~~~~~??I?I~~~~~~. ...,~~~~IMMMMMMMMMMMMM\n"+
"MMMMMMMMMMM~~~~~~~~~~~~~~~~~+++:,~~~~~~~~~~~?=~~~~~:. ..~~~~~OMMMMMMMMMMMM\n"+
"MMMMMMMMM$=~~~~~~~~~~~~~~~=++:.. ..~~~~~~~~~~~~~~~~,. . . :~~~~~OMMMMMMMMMMM\n"+
"MMMMMMMMM~~~~~~~~~~~~~~~~+++,. .~~~~~~~~~~~~~~~.. .. . .~~~~~=OMMMMMMMMMM\n"+
"MMMMMMMM?~~~~~~~~~~~~~~~=+~. .~~~~~~~~~~~~~~. ,MMMMM,=~~~~~~NMMMMMMMMM\n"+
"MMMMMMMN~~~~~~~~~~~~~~~~~,. .,~~~~~~~~~~~~~.. ZMMM,+Z:~~~~~~$MMMMMMMMM\n"+
"MMMMMM8?~~~~~~~~~~~~~~~~~.. ..~~~~~~~~~~~~~:. DMMM,+D~~~~~~~~IMMMMMMMM\n"+
"MMMMMMI~~~~~~~~~~~~~~~~~~.. :MMMO~~~~~~~~~~~~~~~,.. ?MMMMMI~~~~~~~~~MMMMMMMM\n"+
"MMMMMM=~~~~~~~~~~~~~~~~~~.. MMM+=M:~~~~~~~~~~~~~:. .:IM$~~~~~~~~~~~8MMMMMMM\n"+
"MMMMMD~~~~~~~~~~~~~~~~~~~:. MMM:,M:~~~~~~~~~~~~~~~.......:~~~~~~~~~~$MMMMMMM\n"+
"MMMMMI~~~~~~~~~~~~~~~~~~~~, MMMMMM~~~~~~~~~~~~~~~~~~,..:~~~~~~~~~~~~+MMMMMMM\n"+
"MMMMD+~~~~~~~~~~~~~~~~~~~~~. $MMMM$~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=MMMMMMM\n"+
"MMMM8~~~~~~~~~~~~~~~~~~~~~~:. . .:~~~~~~,..:. .=~~~~~~~~~~~~~~~~~~~~MMMMMMM\n"+
"MMMMO~~~~~~~~~~~~~~~~~~~~~~~:, .:~~~~~=8.. .+ . =8ZI~~~~~~~~~~~~~~~~=MMMMMMM\n"+
"MMMMZ=~~~~~~~~~~~~~~~~~~~~~~~~:,,,:~~~~~~IZ8:. .O....888?~~~~~~~~~~~~~~~+MMMMMMM\n"+
"MMMMO=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~?888=...I~I88888O?~~~~~~~~~~~~~~7MMMMMMM\n"+
"MMMMO~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Z888OO88888888888O?~~~~~~~~~~~~~OMMMMMMM\n"+
"MMMMD+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=8888888888888888888~~~~~~~~~~~~+MMMMMMMM\n"+
"MMMMM7~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~?8888888888888888888?~~~~~~~~~~=$MMMMMMMM\n"+
"MMMMMD~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$8888888888888888888O~~~~~~~~~~8MMMMMMMMM\n"+
"MMMMMN=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Z88888888888888888ZZ7=~~~~~~~~?MMMMMMMMMM\n"+
"MMMMMMZ=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+Z88888888Z7I===~~~~~~~~~~~~~=OMMMMMMMMMMM\n"+
"MMMMMMN$~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$88888O7?=~~~~~~~~~~~~~~~~~~OMMMMMMMMMMMM\n"+
"MMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~I8OZ+~~~~~~~~~~~~~~~~~~~~=DMMMMMMMMMMMMMM\n"+
"MMMMMMMM8=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+$+=~~~~~~~~~~~~~~~~~~~~+MMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMD7~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$DMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMM?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~=$OMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMD7=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ZMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMZ7=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~78MMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMM8OI=~~~~~~~~~~~~~~~~~~~=+?ZDNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMNDZ7?++~=~==~+?IONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\n"+
"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM")
stacktrace="""
-------------------------------------------------------------------------------------------------------
=======================================================================================================
-------------------------------------------------------------------------------------------------------
___ ___ ___
( ) ( ) ( )
.--. | |_ .---. .--. | | ___ | |_ ___ .-. .---. .--. .--.
/ _ \ ( __) / .-, \ / \ | | ( ) ( __) ( ) \ / .-, \ / \ / \\
. .' `. ; | | (__) ; | | .-. ; | | ' / | | | ' .-. ; (__) ; | | .-. ; | .-. ;
| ' | | | | ___ .'` | | |(___) | |,' / | | ___ | / (___) .'` | | |(___) | | | |
_\_`.(___) | |( ) / .'| | | | | . '. | |( ) | | / .'| | | | | |/ |
( ). '. | | | | | / | | | | ___ | | `. \ | | | | | | | / | | | | ___ | ' _.'
| | `\ | | ' | | ; | ; | | '( ) | | \ \ | ' | | | | ; | ; | | '( ) | .'.-.
; '._,' ' ' `-' ; ' `-' | ' `-' | | | \ . ' `-' ; | | ' `-' | ' `-' | ' `-' /
'.___.' `.__. `.__.'_. `.__,' (___ ) (___) `.__. (___) `.__.'_. `.__,' `.__.'
-------------------------------------------------------------------------------------------------------
=======================================================================================================
-------------------------------------------------------------------------------------------------------
"""
boat = """\
+ +
)`.).
)``)``) .~~
).-'.-')|)
|-).-).-'_'-/
~~~\ `o-o-o' /~~~~
~~~'---.____/~~~"""

View File

@@ -1,3 +0,0 @@
{
"presets" : ["airbnb", "es2015", "react"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -1,130 +0,0 @@
export const RESET_STATE = 'RESET_STATE';
export const ADD_QUERY_EDITOR = 'ADD_QUERY_EDITOR';
export const CLONE_QUERY_TO_NEW_TAB = 'CLONE_QUERY_TO_NEW_TAB';
export const REMOVE_QUERY_EDITOR = 'REMOVE_QUERY_EDITOR';
export const MERGE_TABLE = 'MERGE_TABLE';
export const REMOVE_TABLE = 'REMOVE_TABLE';
export const START_QUERY = 'START_QUERY';
export const STOP_QUERY = 'STOP_QUERY';
export const END_QUERY = 'END_QUERY';
export const REMOVE_QUERY = 'REMOVE_QUERY';
export const EXPAND_TABLE = 'EXPAND_TABLE';
export const COLLAPSE_TABLE = 'COLLAPSE_TABLE';
export const QUERY_SUCCESS = 'QUERY_SUCCESS';
export const QUERY_FAILED = 'QUERY_FAILED';
export const QUERY_EDITOR_SETDB = 'QUERY_EDITOR_SETDB';
export const QUERY_EDITOR_SET_SCHEMA = 'QUERY_EDITOR_SET_SCHEMA';
export const QUERY_EDITOR_SET_TITLE = 'QUERY_EDITOR_SET_TITLE';
export const QUERY_EDITOR_SET_AUTORUN = 'QUERY_EDITOR_SET_AUTORUN';
export const QUERY_EDITOR_SET_SQL = 'QUERY_EDITOR_SET_SQL';
export const SET_DATABASES = 'SET_DATABASES';
export const ADD_WORKSPACE_QUERY = 'ADD_WORKSPACE_QUERY';
export const REMOVE_WORKSPACE_QUERY = 'REMOVE_WORKSPACE_QUERY';
export const SET_ACTIVE_QUERY_EDITOR = 'SET_ACTIVE_QUERY_EDITOR';
export const ADD_ALERT = 'ADD_ALERT';
export const REMOVE_ALERT = 'REMOVE_ALERT';
export const REFRESH_QUERIES = 'REFRESH_QUERIES';
export const SET_NETWORK_STATUS = 'SET_NETWORK_STATUS';
export function resetState() {
return { type: RESET_STATE };
}
export function setDatabases(databases) {
return { type: SET_DATABASES, databases };
}
export function addQueryEditor(queryEditor) {
return { type: ADD_QUERY_EDITOR, queryEditor };
}
export function cloneQueryToNewTab(query) {
return { type: CLONE_QUERY_TO_NEW_TAB, query };
}
export function setNetworkStatus(networkOn) {
return { type: SET_NETWORK_STATUS, networkOn };
}
export function addAlert(alert) {
return { type: ADD_ALERT, alert };
}
export function removeAlert(alert) {
return { type: REMOVE_ALERT, alert };
}
export function setActiveQueryEditor(queryEditor) {
return { type: SET_ACTIVE_QUERY_EDITOR, queryEditor };
}
export function removeQueryEditor(queryEditor) {
return { type: REMOVE_QUERY_EDITOR, queryEditor };
}
export function removeQuery(query) {
return { type: REMOVE_QUERY, query };
}
export function queryEditorSetDb(queryEditor, dbId) {
return { type: QUERY_EDITOR_SETDB, queryEditor, dbId };
}
export function queryEditorSetSchema(queryEditor, schema) {
return { type: QUERY_EDITOR_SET_SCHEMA, queryEditor, schema };
}
export function queryEditorSetAutorun(queryEditor, autorun) {
return { type: QUERY_EDITOR_SET_AUTORUN, queryEditor, autorun };
}
export function queryEditorSetTitle(queryEditor, title) {
return { type: QUERY_EDITOR_SET_TITLE, queryEditor, title };
}
export function queryEditorSetSql(queryEditor, sql) {
return { type: QUERY_EDITOR_SET_SQL, queryEditor, sql };
}
export function mergeTable(table) {
return { type: MERGE_TABLE, table };
}
export function expandTable(table) {
return { type: EXPAND_TABLE, table };
}
export function collapseTable(table) {
return { type: COLLAPSE_TABLE, table };
}
export function removeTable(table) {
return { type: REMOVE_TABLE, table };
}
export function startQuery(query) {
return { type: START_QUERY, query };
}
export function stopQuery(query) {
return { type: STOP_QUERY, query };
}
export function querySuccess(query, results) {
return { type: QUERY_SUCCESS, query, results };
}
export function queryFailed(query, msg) {
return { type: QUERY_FAILED, query, msg };
}
export function addWorkspaceQuery(query) {
return { type: ADD_WORKSPACE_QUERY, query };
}
export function removeWorkspaceQuery(query) {
return { type: REMOVE_WORKSPACE_QUERY, query };
}
export function refreshQueries(alteredQueries) {
return { type: REFRESH_QUERIES, alteredQueries };
}

View File

@@ -1,8 +0,0 @@
export const STATE_BSSTYLE_MAP = {
failed: 'danger',
pending: 'info',
running: 'warning',
success: 'success',
};
export const STATUS_OPTIONS = ['success', 'failed', 'running'];

View File

@@ -1,48 +0,0 @@
import React from 'react';
import { Button, OverlayTrigger, Tooltip } from 'react-bootstrap';
const ButtonWithTooltip = (props) => {
let tooltip = (
<Tooltip id="tooltip">
{props.tooltip}
</Tooltip>
);
return (
<OverlayTrigger
overlay={tooltip}
delayShow={300}
placement={props.placement}
delayHide={150}
>
<Button
onClick={props.onClick}
bsStyle={props.bsStyle}
bsSize={props.bsSize}
disabled={props.disabled}
className={props.className}
>
{props.children}
</Button>
</OverlayTrigger>
);
};
ButtonWithTooltip.defaultProps = {
onClick: () => {},
disabled: false,
placement: 'top',
bsStyle: 'default',
};
ButtonWithTooltip.propTypes = {
bsSize: React.PropTypes.string,
bsStyle: React.PropTypes.string,
children: React.PropTypes.element,
className: React.PropTypes.string,
disabled: React.PropTypes.bool,
onClick: React.PropTypes.func,
placement: React.PropTypes.string,
tooltip: React.PropTypes.string,
};
export default ButtonWithTooltip;

View File

@@ -1,55 +0,0 @@
import React from 'react';
import CopyToClipboard from '../../components/CopyToClipboard';
const propTypes = {
qe: React.PropTypes.object,
};
const defaultProps = {
qe: null,
};
export default class CopyQueryTabUrl extends React.Component {
constructor(props) {
super(props);
const uri = window.location.toString();
const search = window.location.search;
const cleanUri = search ? uri.substring(0, uri.indexOf('?')) : uri;
const query = search.substring(1);
this.state = {
uri,
cleanUri,
query,
};
}
getQueryLink() {
const params = [];
const qe = this.props.qe;
if (qe.dbId) params.push('dbid=' + qe.dbId);
if (qe.title) params.push('title=' + encodeURIComponent(qe.title));
if (qe.schema) params.push('schema=' + encodeURIComponent(qe.schema));
if (qe.autorun) params.push('autorun=' + qe.autorun);
if (qe.sql) params.push('sql=' + encodeURIComponent(qe.sql));
const queryString = params.join('&');
const queryLink = this.state.cleanUri + '?' + queryString;
return queryLink;
}
render() {
return (
<CopyToClipboard
inMenu
text={this.getQueryLink()}
copyNode={<span>share query</span>}
tooltipText="copy URL to clipboard"
shouldShowText={false}
/>
);
}
}
CopyQueryTabUrl.propTypes = propTypes;
CopyQueryTabUrl.defaultProps = defaultProps;

View File

@@ -1,65 +0,0 @@
const $ = window.$ = require('jquery');
import React from 'react';
import { bindActionCreators } from 'redux';
import Select from 'react-select';
import { connect } from 'react-redux';
import * as Actions from '../actions';
class DatabaseSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
databaseLoading: false,
databaseOptions: [],
};
}
componentDidMount() {
this.fetchDatabaseOptions();
}
changeDb(db) {
this.props.onChange(db);
}
fetchDatabaseOptions() {
this.setState({ databaseLoading: true });
const url = '/databaseasync/api/read?_flt_0_expose_in_sqllab=1';
$.get(url, (data) => {
const options = data.result.map((db) => ({ value: db.id, label: db.database_name }));
this.setState({ databaseOptions: options, databaseLoading: false });
this.props.actions.setDatabases(data.result);
});
}
render() {
return (
<div>
<Select
name="select-db"
placeholder={`Select a database (${this.state.databaseOptions.length})`}
options={this.state.databaseOptions}
value={this.props.databaseId}
isLoading={this.state.databaseLoading}
autosize={false}
onChange={this.changeDb.bind(this)}
/>
</div>
);
}
}
DatabaseSelect.propTypes = {
onChange: React.PropTypes.func,
actions: React.PropTypes.object,
databaseId: React.PropTypes.number,
};
DatabaseSelect.defaultProps = {
onChange: () => {},
databaseId: null,
};
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(null, mapDispatchToProps)(DatabaseSelect);

View File

@@ -1,58 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import QueryTable from './QueryTable';
import { Alert } from 'react-bootstrap';
const QueryHistory = (props) => {
const activeQeId = props.tabHistory[props.tabHistory.length - 1];
const queriesArray = [];
for (const id in props.queries) {
if (props.queries[id].sqlEditorId === activeQeId) {
queriesArray.push(props.queries[id]);
}
}
if (queriesArray.length > 0) {
return (
<QueryTable
columns={[
'state', 'started', 'duration', 'progress',
'rows', 'sql', 'output', 'actions',
]}
queries={queriesArray}
/>
);
}
return (
<Alert bsStyle="info">
No query history yet...
</Alert>
);
};
QueryHistory.defaultProps = {
queries: {},
};
QueryHistory.propTypes = {
queries: React.PropTypes.object,
tabHistory: React.PropTypes.array,
actions: React.PropTypes.object,
};
function mapStateToProps(state) {
return {
queries: state.queries,
tabHistory: state.tabHistory,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(QueryHistory);

View File

@@ -1,62 +0,0 @@
import React from 'react';
import Link from './Link';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import shortid from 'shortid';
class QueryLink extends React.Component {
popTab() {
const qe = {
id: shortid.generate(),
title: this.props.query.title,
dbId: this.props.query.dbId,
autorun: false,
sql: this.props.query.sql,
};
this.props.actions.addQueryEditor(qe);
}
render() {
return (
<div>
<div className="clearfix">
<div className="pull-left">
<a
href="#"
tooltip="Pop this query in a new tab"
onClick={this.popTab.bind(this)}
>
{this.props.query.title}
</a>
</div>
<div className="pull-right">
<Link
onClick={this.props.actions.removeWorkspaceQuery.bind(this, this.props.query)}
tooltip="Remove query from workspace"
href="#"
>
&times;
</Link>
</div>
</div>
<hr />
</div>
);
}
}
QueryLink.propTypes = {
query: React.PropTypes.object,
actions: React.PropTypes.object,
};
QueryLink.defaultProps = {
};
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(null, mapDispatchToProps)(QueryLink);

View File

@@ -1,142 +0,0 @@
const $ = window.$ = require('jquery');
import React from 'react';
import { Button } from 'react-bootstrap';
import Select from 'react-select';
import QueryTable from './QueryTable';
import DatabaseSelect from './DatabaseSelect';
import { STATUS_OPTIONS } from '../common';
class QuerySearch extends React.Component {
constructor(props) {
super(props);
this.state = {
userLoading: false,
userOptions: [],
databaseId: null,
userId: null,
searchText: null,
status: 'success',
queriesArray: [],
};
}
componentWillMount() {
this.fetchUsers();
this.refreshQueries();
}
onUserClicked(userId) {
this.setState({ userId }, () => { this.refreshQueries(); });
}
onDbClicked(dbId) {
this.setState({ databaseId: dbId }, () => { this.refreshQueries(); });
}
onChange(db) {
const val = (db) ? db.value : null;
this.setState({ databaseId: val });
}
insertParams(baseUrl, params) {
return baseUrl + '?' + params.join('&');
}
changeUser(user) {
const val = (user) ? user.value : null;
this.setState({ userId: val });
}
changeStatus(status) {
const val = (status) ? status.value : null;
this.setState({ status: val });
}
changeSearch(event) {
this.setState({ searchText: event.target.value });
}
fetchUsers() {
this.setState({ userLoading: true });
const url = '/users/api/read';
$.getJSON(url, (data, status) => {
if (status === 'success') {
const options = [];
for (let i = 0; i < data.pks.length; i++) {
options.push({ value: data.pks[i], label: data.result[i].username });
}
this.setState({ userOptions: options, userLoading: false });
}
});
}
refreshQueries() {
const params = [
`userId=${this.state.userId}`,
`databaseId=${this.state.databaseId}`,
`searchText=${this.state.searchText}`,
`status=${this.state.status}`,
];
const url = this.insertParams('/caravel/search_queries', params);
$.getJSON(url, (data, status) => {
if (status === 'success') {
const newQueriesArray = [];
for (const id in data) {
newQueriesArray.push(data[id]);
}
this.setState({ queriesArray: newQueriesArray });
}
});
}
render() {
return (
<div>
<div className="row space-1">
<div className="col-sm-2">
<Select
name="select-user"
placeholder="[User]"
options={this.state.userOptions}
value={this.state.userId}
isLoading={this.state.userLoading}
autosize={false}
onChange={this.changeUser.bind(this)}
/>
</div>
<div className="col-sm-2">
<DatabaseSelect
onChange={this.onChange.bind(this)}
databaseId={this.state.databaseId}
/>
</div>
<div className="col-sm-4">
<input
type="text"
onChange={this.changeSearch.bind(this)}
className="form-control input-sm"
placeholder="Search Results"
/>
</div>
<div className="col-sm-2">
<Select
name="select-state"
placeholder="[Query Status]"
options={STATUS_OPTIONS.map((s) => ({ value: s, label: s }))}
value={this.state.status}
isLoading={false}
autosize={false}
onChange={this.changeStatus.bind(this)}
/>
</div>
<Button bsSize="small" bsStyle="success" onClick={this.refreshQueries.bind(this)}>
Search
</Button>
</div>
<QueryTable
columns={[
'state', 'db', 'user',
'progress', 'rows', 'sql', 'querylink',
]}
onUserClicked={this.onUserClicked.bind(this)}
onDbClicked={this.onDbClicked.bind(this)}
queries={this.state.queriesArray}
/>
</div>
);
}
}
export default QuerySearch;

View File

@@ -1,95 +0,0 @@
import React from 'react';
import { Alert, Button, ButtonGroup } from 'react-bootstrap';
import { Table } from 'reactable';
import VisualizeModal from './VisualizeModal';
class ResultSet extends React.Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
showModal: false,
};
}
changeSearch(event) {
this.setState({ searchText: event.target.value });
}
showModal() {
this.setState({ showModal: true });
}
hideModal() {
this.setState({ showModal: false });
}
render() {
const results = this.props.query.results;
let controls = <div className="noControls" />;
if (this.props.showControls) {
controls = (
<div className="ResultSetControls">
<div className="clearfix">
<div className="pull-left">
<ButtonGroup>
<Button
bsSize="small"
onClick={this.showModal.bind(this)}
>
<i className="fa fa-line-chart m-l-1" /> Visualize
</Button>
<Button bsSize="small" href={'/caravel/csv/' + this.props.query.id}>
<i className="fa fa-file-text-o" /> .CSV
</Button>
</ButtonGroup>
</div>
<div className="pull-right">
<input
type="text"
onChange={this.changeSearch.bind(this)}
className="form-control input-sm"
placeholder="Search Results"
/>
</div>
</div>
</div>
);
}
if (results && results.data && results.data.length > 0) {
return (
<div>
<VisualizeModal
show={this.state.showModal}
query={this.props.query}
onHide={this.hideModal.bind(this)}
/>
{controls}
<div className="ResultSet">
<Table
data={results.data}
columns={results.columns.map((col) => col.name)}
sortable
className="table table-condensed table-bordered"
filterBy={this.state.searchText}
filterable={results.columns}
hideFilterInput
/>
</div>
</div>
);
}
return (<Alert bsStyle="warning">The query returned no data</Alert>);
}
}
ResultSet.propTypes = {
query: React.PropTypes.object,
showControls: React.PropTypes.bool,
search: React.PropTypes.bool,
searchText: React.PropTypes.string,
};
ResultSet.defaultProps = {
showControls: true,
search: true,
searchText: '',
};
export default ResultSet;

View File

@@ -1,85 +0,0 @@
import { Alert, Button, Tab, Tabs } from 'react-bootstrap';
import QueryHistory from './QueryHistory';
import ResultSet from './ResultSet';
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import shortid from 'shortid';
class SouthPane extends React.Component {
popSelectStar() {
const qe = {
id: shortid.generate(),
title: this.props.latestQuery.tempTable,
autorun: false,
dbId: this.props.latestQuery.dbId,
sql: `SELECT * FROM ${this.props.latestQuery.tempTable}`,
};
this.props.actions.addQueryEditor(qe);
}
render() {
let results = <div />;
const latestQuery = this.props.latestQuery;
if (latestQuery) {
if (['running', 'pending'].includes(latestQuery.state)) {
results = (
<img className="loading" alt="Loading.." src="/static/assets/images/loading.gif" />
);
} else if (latestQuery.state === 'failed') {
results = <Alert bsStyle="danger">{latestQuery.errorMessage}</Alert>;
} else if (latestQuery.state === 'success' && latestQuery.ctas) {
results = (
<div>
<Alert bsStyle="info">
Table [<strong>{latestQuery.tempTable}</strong>] was created
</Alert>
<p>
<Button
bsSize="small"
className="m-r-5"
onClick={this.popSelectStar.bind(this)}
>
Query in a new tab
</Button>
<Button bsSize="small">Visualize</Button>
</p>
</div>);
} else if (latestQuery.state === 'success') {
results = <ResultSet showControls search query={latestQuery} />;
}
} else {
results = <Alert bsStyle="info">Run a query to display results here</Alert>;
}
return (
<div className="SouthPane">
<Tabs bsStyle="tabs" id={shortid.generate()}>
<Tab title="Results" eventKey={1}>
<div style={{ overflow: 'auto' }}>
{results}
</div>
</Tab>
<Tab title="Query History" eventKey={2}>
<QueryHistory />
</Tab>
</Tabs>
</div>
);
}
}
SouthPane.propTypes = {
latestQuery: React.PropTypes.object,
actions: React.PropTypes.object,
};
SouthPane.defaultProps = {
};
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(null, mapDispatchToProps)(SouthPane);

View File

@@ -1,301 +0,0 @@
const $ = require('jquery');
import { now } from '../../modules/dates';
import React from 'react';
import {
Button,
ButtonGroup,
Col,
FormGroup,
InputGroup,
Form,
FormControl,
Label,
OverlayTrigger,
Row,
Tooltip,
} from 'react-bootstrap';
import AceEditor from 'react-ace';
import 'brace/mode/sql';
import 'brace/theme/github';
import 'brace/ext/language_tools';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
import shortid from 'shortid';
import SouthPane from './SouthPane';
import Timer from './Timer';
import SqlEditorLeftBar from './SqlEditorLeftBar';
class SqlEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
autorun: props.queryEditor.autorun,
sql: props.queryEditor.sql,
ctas: '',
};
}
componentDidMount() {
this.onMount();
}
onMount() {
if (this.state.autorun) {
this.setState({ autorun: false });
this.props.actions.queryEditorSetAutorun(this.props.queryEditor, false);
this.startQuery();
}
}
runQuery(runAsync = false) {
this.startQuery(runAsync);
}
startQuery(runAsync = false, ctas = false) {
const that = this;
const query = {
dbId: this.props.queryEditor.dbId,
id: shortid.generate(),
progress: 0,
sql: this.props.queryEditor.sql,
sqlEditorId: this.props.queryEditor.id,
startDttm: now(),
state: 'running',
tab: this.props.queryEditor.title,
};
if (runAsync) {
query.state = 'pending';
}
// Execute the Query
that.props.actions.startQuery(query);
const sqlJsonUrl = '/caravel/sql_json/';
const sqlJsonRequest = {
client_id: query.id,
database_id: this.props.queryEditor.dbId,
json: true,
runAsync,
schema: this.props.queryEditor.schema,
select_as_cta: ctas,
sql: this.props.queryEditor.sql,
sql_editor_id: this.props.queryEditor.id,
tab: this.props.queryEditor.title,
tmp_table_name: this.state.ctas,
};
$.ajax({
type: 'POST',
dataType: 'json',
url: sqlJsonUrl,
data: sqlJsonRequest,
success(results) {
if (!runAsync) {
that.props.actions.querySuccess(query, results);
}
},
error(err, textStatus, errorThrown) {
let msg;
try {
msg = err.responseJSON.error;
} catch (e) {
if (err.responseText !== undefined) {
msg = err.responseText;
}
}
if (textStatus === 'error' && errorThrown === '') {
msg = 'Could not connect to server';
} else if (msg === null) {
msg = `[${textStatus}] ${errorThrown}`;
}
that.props.actions.queryFailed(query, msg);
},
});
}
stopQuery() {
this.props.actions.stopQuery(this.props.latestQuery);
}
createTableAs() {
this.startQuery(true, true);
}
textChange(text) {
this.setState({ sql: text });
this.props.actions.queryEditorSetSql(this.props.queryEditor, text);
}
addWorkspaceQuery() {
this.props.actions.addWorkspaceQuery({
id: shortid.generate(),
sql: this.state.sql,
dbId: this.props.queryEditor.dbId,
schema: this.props.queryEditor.schema,
title: this.props.queryEditor.title,
});
}
ctasChange() {}
visualize() {}
ctasChanged(event) {
this.setState({ ctas: event.target.value });
}
sqlEditorHeight() {
// quick hack to make the white bg of the tab stretch full height.
const tabNavHeight = 40;
const navBarHeight = 56;
const mysteryVerticalHeight = 50;
return window.innerHeight - tabNavHeight - navBarHeight - mysteryVerticalHeight;
}
render() {
let runButtons = [];
if (this.props.database && this.props.database.allow_run_sync) {
runButtons.push(
<Button
bsSize="small"
bsStyle="primary"
style={{ width: '100px' }}
onClick={this.runQuery.bind(this, false)}
disabled={!(this.props.queryEditor.dbId)}
key={shortid.generate()}
>
<i className="fa fa-table" /> Run Query
</Button>
);
}
if (this.props.database && this.props.database.allow_run_async) {
runButtons.push(
<Button
bsSize="small"
bsStyle="primary"
style={{ width: '100px' }}
onClick={this.runQuery.bind(this, true)}
disabled={!(this.props.queryEditor.dbId)}
key={shortid.generate()}
>
<i className="fa fa-table" /> Run Async
</Button>
);
}
runButtons = (
<ButtonGroup bsSize="small" className="inline m-r-5 pull-left">
{runButtons}
</ButtonGroup>
);
if (this.props.latestQuery && this.props.latestQuery.state === 'running') {
runButtons = (
<ButtonGroup bsSize="small" className="inline m-r-5 pull-left">
<Button
bsStyle="primary"
bsSize="small"
style={{ width: '100px' }}
onClick={this.stopQuery.bind(this)}
>
<a className="fa fa-stop" /> Stop
</Button>
</ButtonGroup>
);
}
let limitWarning = null;
if (this.props.latestQuery && this.props.latestQuery.limit_reached) {
const tooltip = (
<Tooltip id="tooltip">
It appears that the number of rows in the query results displayed
was limited on the server side to
the {this.props.latestQuery.rows} limit.
</Tooltip>
);
limitWarning = (
<OverlayTrigger placement="left" overlay={tooltip}>
<Label bsStyle="warning" className="m-r-5">LIMIT</Label>
</OverlayTrigger>
);
}
let ctasControls;
if (this.props.database && this.props.database.allow_ctas) {
ctasControls = (
<FormGroup>
<InputGroup>
<FormControl
type="text"
bsSize="small"
className="input-sm"
placeholder="new table name"
onChange={this.ctasChanged.bind(this)}
/>
<InputGroup.Button>
<Button
bsSize="small"
disabled={this.state.ctas.length === 0}
onClick={this.createTableAs.bind(this)}
>
<i className="fa fa-table" /> CTAS
</Button>
</InputGroup.Button>
</InputGroup>
</FormGroup>
);
}
const editorBottomBar = (
<div className="sql-toolbar clearfix">
<div className="pull-left">
<Form inline>
{runButtons}
{ctasControls}
</Form>
</div>
<div className="pull-right">
{limitWarning}
<Timer query={this.props.latestQuery} />
</div>
</div>
);
return (
<div className="SqlEditor" style={{ minHeight: this.sqlEditorHeight() }}>
<Row>
<Col md={3}>
<SqlEditorLeftBar queryEditor={this.props.queryEditor} />
</Col>
<Col md={9}>
<AceEditor
mode="sql"
name={this.props.queryEditor.id}
theme="github"
minLines={7}
maxLines={30}
onChange={this.textChange.bind(this)}
height="200px"
width="100%"
editorProps={{ $blockScrolling: true }}
enableBasicAutocompletion
value={this.props.queryEditor.sql}
/>
{editorBottomBar}
<br />
<SouthPane latestQuery={this.props.latestQuery} sqlEditor={this} />
</Col>
</Row>
</div>
);
}
}
SqlEditor.propTypes = {
actions: React.PropTypes.object,
database: React.PropTypes.object,
latestQuery: React.PropTypes.object,
queryEditor: React.PropTypes.object,
};
SqlEditor.defaultProps = {
};
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SqlEditor);

View File

@@ -1,195 +0,0 @@
const $ = window.$ = require('jquery');
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
import Select from 'react-select';
import { Label, Button } from 'react-bootstrap';
import TableElement from './TableElement';
import DatabaseSelect from './DatabaseSelect';
class SqlEditorLeftBar extends React.Component {
constructor(props) {
super(props);
this.state = {
schemaLoading: false,
schemaOptions: [],
tableLoading: false,
tableOptions: [],
};
}
componentWillMount() {
this.fetchSchemas();
this.fetchTables();
}
onChange(db) {
const val = (db) ? db.value : null;
this.setState({ schemaOptions: [] });
this.props.actions.queryEditorSetDb(this.props.queryEditor, val);
if (!(db)) {
this.setState({ tableOptions: [] });
} else {
this.fetchTables(val, this.props.queryEditor.schema);
this.fetchSchemas(val);
}
}
resetState() {
this.props.actions.resetState();
}
fetchTables(dbId, schema) {
const actualDbId = dbId || this.props.queryEditor.dbId;
if (actualDbId) {
const actualSchema = schema || this.props.queryEditor.schema;
this.setState({ tableLoading: true });
this.setState({ tableOptions: [] });
const url = `/caravel/tables/${actualDbId}/${actualSchema}`;
$.get(url, (data) => {
let tableOptions = data.tables.map((s) => ({ value: s, label: s }));
const views = data.views.map((s) => ({ value: s, label: '[view] ' + s }));
tableOptions = [...tableOptions, ...views];
this.setState({ tableOptions });
this.setState({ tableLoading: false });
});
}
}
changeSchema(schemaOpt) {
const schema = (schemaOpt) ? schemaOpt.value : null;
this.props.actions.queryEditorSetSchema(this.props.queryEditor, schema);
this.fetchTables(this.props.queryEditor.dbId, schema);
}
fetchSchemas(dbId) {
const actualDbId = dbId || this.props.queryEditor.dbId;
if (actualDbId) {
this.setState({ schemaLoading: true });
const url = `/databasetablesasync/api/read?_flt_0_id=${actualDbId}`;
$.get(url, (data) => {
const schemas = data.result[0].all_schema_names;
const schemaOptions = schemas.map((s) => ({ value: s, label: s }));
this.setState({ schemaOptions });
this.setState({ schemaLoading: false });
});
}
}
closePopover(ref) {
this.refs[ref].hide();
}
changeTable(tableOpt) {
const tableName = tableOpt.value;
const qe = this.props.queryEditor;
let url = `/caravel/table/${qe.dbId}/${tableName}/${qe.schema}/`;
this.setState({ tableLoading: true });
$.get(url, (data) => {
this.props.actions.mergeTable({
dbId: this.props.queryEditor.dbId,
queryEditorId: this.props.queryEditor.id,
name: data.name,
indexes: data.indexes,
schema: qe.schema,
columns: data.columns,
expanded: true,
});
this.setState({ tableLoading: false });
})
.fail(() => {
this.props.actions.addAlert({
msg: 'Error occurred while fetching metadata',
bsStyle: 'danger',
});
this.setState({ tableLoading: false });
});
url = `/caravel/extra_table_metadata/${qe.dbId}/${tableName}/${qe.schema}/`;
$.get(url, (data) => {
const table = {
dbId: this.props.queryEditor.dbId,
queryEditorId: this.props.queryEditor.id,
schema: qe.schema,
name: tableName,
};
Object.assign(table, data);
this.props.actions.mergeTable(table);
});
}
render() {
let networkAlert = null;
if (!this.props.networkOn) {
networkAlert = <p><Label bsStyle="danger">OFFLINE</Label></p>;
}
const tables = this.props.tables.filter((t) => (t.queryEditorId === this.props.queryEditor.id));
const shouldShowReset = window.location.search === '?reset=1';
return (
<div className="clearfix sql-toolbar">
{networkAlert}
<div>
<DatabaseSelect
onChange={this.onChange.bind(this)}
databaseId={this.props.queryEditor.dbId}
/>
</div>
<div className="m-t-5">
<Select
name="select-schema"
placeholder={`Select a schema (${this.state.schemaOptions.length})`}
options={this.state.schemaOptions}
value={this.props.queryEditor.schema}
isLoading={this.state.schemaLoading}
autosize={false}
onChange={this.changeSchema.bind(this)}
/>
</div>
<div className="m-t-5">
<Select
name="select-table"
ref="selectTable"
isLoading={this.state.tableLoading}
placeholder={`Add a table (${this.state.tableOptions.length})`}
autosize={false}
value={this.state.tableName}
onChange={this.changeTable.bind(this)}
options={this.state.tableOptions}
/>
</div>
<hr />
<div className="m-t-5">
{tables.map((table) => (
<TableElement table={table} queryEditor={this.props.queryEditor} key={table.id} />
))}
</div>
{shouldShowReset &&
<Button bsSize="small" bsStyle="danger" onClick={this.resetState.bind(this)}>
<i className="fa fa-bomb" /> Reset State
</Button>
}
</div>
);
}
}
SqlEditorLeftBar.propTypes = {
queryEditor: React.PropTypes.object,
tables: React.PropTypes.array,
actions: React.PropTypes.object,
networkOn: React.PropTypes.bool,
};
SqlEditorLeftBar.defaultProps = {
tables: [],
};
function mapStateToProps(state) {
return {
tables: state.tables,
networkOn: state.networkOn,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SqlEditorLeftBar);

View File

@@ -1,39 +0,0 @@
import React from 'react';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { github } from 'react-syntax-highlighter/dist/styles';
const SqlShrink = (props) => {
const sql = props.sql || '';
let lines = sql.split('\n');
if (lines.length >= props.maxLines) {
lines = lines.slice(0, props.maxLines);
lines.push('{...}');
}
const shrunk = lines.map((line) => {
if (line.length > props.maxWidth) {
return line.slice(0, props.maxWidth) + '{...}';
}
return line;
})
.join('\n');
return (
<div>
<SyntaxHighlighter language="sql" style={github}>
{shrunk}
</SyntaxHighlighter>
</div>
);
};
SqlShrink.defaultProps = {
maxWidth: 60,
maxLines: 6,
};
SqlShrink.propTypes = {
sql: React.PropTypes.string,
maxWidth: React.PropTypes.number,
maxLines: React.PropTypes.number,
};
export default SqlShrink;

View File

@@ -1,170 +0,0 @@
import React from 'react';
import { DropdownButton, MenuItem, Tab, Tabs } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import SqlEditor from './SqlEditor';
import shortid from 'shortid';
import { getParamFromQuery, getLink } from '../../../utils/common';
import CopyQueryTabUrl from './CopyQueryTabUrl';
let queryCount = 1;
class TabbedSqlEditors extends React.Component {
constructor(props) {
super(props);
const uri = window.location.toString();
const search = window.location.search;
const cleanUri = search ? uri.substring(0, uri.indexOf('?')) : uri;
const query = search.substring(1);
this.state = {
uri,
cleanUri,
query,
};
}
componentWillMount() {
if (this.state.query) {
queryCount++;
const queryEditorProps = {
id: shortid.generate(),
title: getParamFromQuery(this.state.query, 'title'),
dbId: getParamFromQuery(this.state.query, 'dbid'),
schema: getParamFromQuery(this.state.query, 'schema'),
autorun: getParamFromQuery(this.state.query, 'autorun'),
sql: getParamFromQuery(this.state.query, 'sql'),
};
this.props.actions.addQueryEditor(queryEditorProps);
// Clean the url in browser history
window.history.replaceState({}, document.title, this.state.cleanUri);
}
}
getQueryLink(qe) {
const params = [];
if (qe.dbId) params.push('dbid=' + qe.dbId);
if (qe.title) params.push('title=' + qe.title);
if (qe.schema) params.push('schema=' + qe.schema);
if (qe.autorun) params.push('autorun=' + qe.autorun);
if (qe.sql) params.push('sql=' + qe.sql);
return getLink(this.state.cleanUri, params);
}
renameTab(qe) {
/* eslint no-alert: 0 */
const newTitle = prompt('Enter a new title for the tab');
if (newTitle) {
this.props.actions.queryEditorSetTitle(qe, newTitle);
}
}
activeQueryEditor() {
const qeid = this.props.tabHistory[this.props.tabHistory.length - 1];
for (let i = 0; i < this.props.queryEditors.length; i++) {
const qe = this.props.queryEditors[i];
if (qe.id === qeid) {
return qe;
}
}
return null;
}
newQueryEditor() {
queryCount++;
const activeQueryEditor = this.activeQueryEditor();
const qe = {
id: shortid.generate(),
title: `Untitled Query ${queryCount}`,
dbId: (activeQueryEditor) ? activeQueryEditor.dbId : null,
schema: (activeQueryEditor) ? activeQueryEditor.schema : null,
autorun: false,
sql: 'SELECT ...',
};
this.props.actions.addQueryEditor(qe);
}
handleSelect(key) {
if (key === 'add_tab') {
this.newQueryEditor();
} else {
this.props.actions.setActiveQueryEditor({ id: key });
}
}
render() {
const editors = this.props.queryEditors.map((qe, i) => {
let latestQuery = this.props.queries[qe.latestQueryId];
const database = this.props.databases[qe.dbId];
const state = (latestQuery) ? latestQuery.state : '';
const tabTitle = (
<div>
<div className={'circle ' + state} /> {qe.title} {' '}
<DropdownButton
bsSize="small"
id={'ddbtn-tab-' + i}
title=""
>
<MenuItem eventKey="1" onClick={this.props.actions.removeQueryEditor.bind(this, qe)}>
<i className="fa fa-close" /> close tab
</MenuItem>
<MenuItem eventKey="2" onClick={this.renameTab.bind(this, qe)}>
<i className="fa fa-i-cursor" /> rename tab
</MenuItem>
<MenuItem eventKey="3">
<i className="fa fa-clipboard" /> <CopyQueryTabUrl qe={qe} />
</MenuItem>
</DropdownButton>
</div>
);
return (
<Tab
key={qe.id}
title={tabTitle}
eventKey={qe.id}
>
<div className="panel panel-default">
<div className="panel-body">
<SqlEditor
queryEditor={qe}
latestQuery={latestQuery}
database={database}
/>
</div>
</div>
</Tab>);
});
return (
<Tabs
bsStyle="tabs"
activeKey={this.props.tabHistory[this.props.tabHistory.length - 1]}
onSelect={this.handleSelect.bind(this)}
id="a11y-query-editor-tabs"
>
{editors}
<Tab title={<div><i className="fa fa-plus-circle" />&nbsp;</div>} eventKey="add_tab" />
</Tabs>
);
}
}
TabbedSqlEditors.propTypes = {
actions: React.PropTypes.object,
databases: React.PropTypes.object,
queries: React.PropTypes.object,
queryEditors: React.PropTypes.array,
tabHistory: React.PropTypes.array,
};
TabbedSqlEditors.defaultProps = {
tabHistory: [],
queryEditors: [],
};
function mapStateToProps(state) {
return {
databases: state.databases,
queryEditors: state.queryEditors,
queries: state.queries,
tabHistory: state.tabHistory,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(TabbedSqlEditors);

View File

@@ -1,214 +0,0 @@
import React from 'react';
import { ButtonGroup, Well } from 'react-bootstrap';
import Link from './Link';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '../actions';
import shortid from 'shortid';
import ModalTrigger from '../../components/ModalTrigger';
import CopyToClipboard from '../../components/CopyToClipboard';
const propTypes = {
table: React.PropTypes.object,
queryEditor: React.PropTypes.object,
actions: React.PropTypes.object,
};
const defaultProps = {
table: null,
actions: {},
};
class TableElement extends React.Component {
setSelectStar() {
this.props.actions.queryEditorSetSql(this.props.queryEditor, this.selectStar());
}
selectStar() {
let cols = '';
this.props.table.columns.forEach((col, i) => {
cols += col.name;
if (i < this.props.table.columns.length - 1) {
cols += ', ';
}
});
let tableName = this.props.table.name;
if (this.props.table.schema) {
tableName = this.props.table.schema + '.' + tableName;
}
return `SELECT ${cols}\nFROM ${tableName}`;
}
popSelectStar() {
const qe = {
id: shortid.generate(),
title: this.props.table.name,
dbId: this.props.table.dbId,
autorun: true,
sql: this.selectStar(),
};
this.props.actions.addQueryEditor(qe);
}
collapseTable(e) {
e.preventDefault();
this.props.actions.collapseTable(this.props.table);
}
expandTable(e) {
e.preventDefault();
this.props.actions.expandTable(this.props.table);
}
removeTable() {
this.props.actions.removeTable(this.props.table);
}
render() {
const table = this.props.table;
let metadata = null;
let buttonToggle;
let header;
if (table.partitions) {
let partitionQuery;
let partitionClipBoard;
if (table.partitions.partitionQuery) {
partitionQuery = table.partitions.partitionQuery;
const tt = 'Copy partition query to clipboard';
partitionClipBoard = (
<CopyToClipboard
text={partitionQuery}
shouldShowText={false}
tooltipText={tt}
copyNode={<i className="fa fa-clipboard" />}
/>
);
}
let latest = [];
for (const k in table.partitions.latest) {
latest.push(`${k}=${table.partitions.latest[k]}`);
}
latest = latest.join('/');
header = (
<Well bsSize="small">
<div>
<small>
latest partition: {latest}
</small> {partitionClipBoard}
</div>
</Well>
);
}
if (table.expanded) {
buttonToggle = (
<a
href="#"
onClick={(e) => { this.collapseTable(e); }}
>
<strong>{table.name}</strong>
<small className="m-l-5"><i className="fa fa-minus" /></small>
</a>
);
metadata = (
<div>
{header}
<div className="table-columns">
{table.columns.map((col) => {
let name = col.name;
if (col.indexed) {
name = <strong>{col.name}</strong>;
}
return (
<div className="clearfix table-column" key={shortid.generate()}>
<div className="pull-left m-l-10">
{name}
</div>
<div className="pull-right text-muted">
<small> {col.type}</small>
</div>
</div>);
})}
<hr />
</div>
</div>
);
} else {
buttonToggle = (
<a
href="#"
onClick={(e) => { this.expandTable(e); }}
>
{table.name}
<small className="m-l-5"><i className="fa fa-plus" /></small>
</a>
);
}
let keyLink;
if (table.indexes && table.indexes.length > 0) {
keyLink = (
<ModalTrigger
modalTitle={
<div>
Keys for table <strong>{table.name}</strong>
</div>
}
modalBody={
<pre>{JSON.stringify(table.indexes, null, 4)}</pre>
}
triggerNode={
<Link
className="fa fa-key pull-left m-l-2"
tooltip={`View indexes (${table.indexes.length})`}
/>
}
/>
);
}
return (
<div className="TableElement">
<div className="clearfix">
<div className="pull-left">
{buttonToggle}
</div>
<div className="pull-right">
<ButtonGroup className="ws-el-controls pull-right">
{keyLink}
<Link
className="fa fa-pencil pull-left m-l-2"
onClick={this.setSelectStar.bind(this)}
tooltip="Run query in this tab"
href="#"
/>
<Link
className="fa fa-plus-circle pull-left m-l-2"
onClick={this.popSelectStar.bind(this)}
tooltip="Run query in a new tab"
href="#"
/>
<Link
className="fa fa-trash pull-left m-l-2"
onClick={this.removeTable.bind(this)}
tooltip="Remove from workspace"
href="#"
/>
</ButtonGroup>
</div>
</div>
<div>
{metadata}
</div>
</div>
);
}
}
TableElement.propTypes = propTypes;
TableElement.defaultProps = defaultProps;
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Actions, dispatch),
};
}
export default connect(null, mapDispatchToProps)(TableElement);
export { TableElement };

View File

@@ -1,23 +0,0 @@
import React from 'react';
import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';
const TableMetadata = function (props) {
return (
<BootstrapTable
condensed
data={props.table.columns}
>
<TableHeaderColumn dataField="id" isKey hidden>
id
</TableHeaderColumn>
<TableHeaderColumn dataField="name">Name</TableHeaderColumn>
<TableHeaderColumn dataField="type">Type</TableHeaderColumn>
</BootstrapTable>
);
};
TableMetadata.propTypes = {
table: React.PropTypes.object,
};
export default TableMetadata;

View File

@@ -1,61 +0,0 @@
import React from 'react';
import { now, fDuration } from '../../modules/dates';
import { STATE_BSSTYLE_MAP } from '../common.js';
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
clockStr: '',
};
}
componentWillMount() {
this.startTimer();
}
componentWillUnmount() {
this.stopTimer();
}
startTimer() {
if (!(this.timer)) {
this.timer = setInterval(this.stopwatch.bind(this), 30);
}
}
stopTimer() {
clearInterval(this.timer);
this.timer = null;
}
stopwatch() {
if (this.props && this.props.query) {
const endDttm = this.props.query.endDttm || now();
const clockStr = fDuration(this.props.query.startDttm, endDttm);
this.setState({ clockStr });
if (this.props.query.state !== 'running') {
this.stopTimer();
}
}
}
render() {
if (this.props.query && this.props.query.state === 'running') {
this.startTimer();
}
let timerSpan = null;
if (this.props && this.props.query) {
const bsStyle = STATE_BSSTYLE_MAP[this.props.query.state];
timerSpan = (
<span className={'inlineBlock m-r-5 label label-' + bsStyle}>
{this.state.clockStr}
</span>
);
}
return timerSpan;
}
}
Timer.propTypes = {
query: React.PropTypes.object,
};
Timer.defaultProps = {
query: null,
};
export default Timer;

View File

@@ -1,27 +0,0 @@
const $ = window.$ = require('jquery');
const jQuery = window.jQuery = $; // eslint-disable-line
require('bootstrap');
import React from 'react';
import { render } from 'react-dom';
import { initialState, sqlLabReducer } from './reducers';
import { enhancer } from '../reduxUtils';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './components/App';
require('./main.css');
let store = createStore(sqlLabReducer, initialState, enhancer());
// jquery hack to highlight the navbar menu
$('a:contains("SQL Lab")').parent().addClass('active');
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);

View File

@@ -1,5 +0,0 @@
require('../node_modules/select2/select2.css');
require('../node_modules/select2-bootstrap-css/select2-bootstrap.min.css');
require('../node_modules/jquery-ui/themes/base/jquery-ui.css');
require('select2');
require('../vendor/select2.sortable.js');

View File

@@ -1,59 +0,0 @@
import React, { PropTypes } from 'react';
import { Modal } from 'react-bootstrap';
import cx from 'classnames';
const propTypes = {
triggerNode: PropTypes.node.isRequired,
modalTitle: PropTypes.node.isRequired,
modalBody: PropTypes.node.isRequired,
beforeOpen: PropTypes.func,
isButton: PropTypes.bool,
};
const defaultProps = {
beforeOpen: () => {},
isButton: false,
};
export default class ModalTrigger extends React.Component {
constructor(props) {
super(props);
this.state = {
showModal: false,
};
this.open = this.open.bind(this);
this.close = this.close.bind(this);
}
close() {
this.setState({ showModal: false });
}
open(e) {
e.preventDefault();
this.props.beforeOpen();
this.setState({ showModal: true });
}
render() {
const classNames = cx({
'btn btn-default btn-sm': this.props.isButton,
});
return (
<a href="#" className={classNames} onClick={this.open}>
{this.props.triggerNode}
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>{this.props.modalTitle}</Modal.Title>
</Modal.Header>
<Modal.Body>
{this.props.modalBody}
</Modal.Body>
</Modal>
</a>
);
}
}
ModalTrigger.propTypes = propTypes;
ModalTrigger.defaultProps = defaultProps;

View File

@@ -1,42 +0,0 @@
import React, { PropTypes } from 'react';
const propTypes = {
modalId: PropTypes.string.isRequired,
title: PropTypes.string,
modalContent: PropTypes.node,
customButton: PropTypes.node,
};
function Modal({ modalId, title, modalContent, customButton }) {
return (
<div className="modal fade" id={modalId} role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 className="modal-title">{title}</h4>
</div>
<div className="modal-body">
{modalContent}
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-default"
data-dismiss="modal"
>
Cancel
</button>
{customButton}
</div>
</div>
</div>
</div>
);
}
Modal.propTypes = propTypes;
export default Modal;

View File

@@ -1,37 +0,0 @@
import React, { PropTypes } from 'react';
import ModalTrigger from './../../components/ModalTrigger';
const propTypes = {
slice: PropTypes.object.isRequired,
};
export default class DisplayQueryButton extends React.Component {
constructor(props) {
super(props);
this.state = {
viewSqlQuery: '',
};
this.beforeOpen = this.beforeOpen.bind(this);
}
beforeOpen() {
this.setState({
viewSqlQuery: this.props.slice.viewSqlQuery,
});
}
render() {
const modalBody = (<pre>{this.state.viewSqlQuery}</pre>);
return (
<ModalTrigger
isButton
triggerNode={<span>Query</span>}
modalTitle="Query"
modalBody={modalBody}
beforeOpen={this.beforeOpen}
/>
);
}
}
DisplayQueryButton.propTypes = propTypes;

View File

@@ -1,45 +0,0 @@
import React, { PropTypes } from 'react';
import cx from 'classnames';
import URLShortLinkButton from './URLShortLinkButton';
import EmbedCodeButton from './EmbedCodeButton';
import DisplayQueryButton from './DisplayQueryButton';
const propTypes = {
canDownload: PropTypes.string.isRequired,
slice: PropTypes.object.isRequired,
};
export default function ExploreActionButtons({ canDownload, slice }) {
const exportToCSVClasses = cx('btn btn-default btn-sm', {
'disabled disabledButton': !canDownload,
});
return (
<div className="btn-group results" role="group">
<URLShortLinkButton slice={slice} />
<EmbedCodeButton slice={slice} />
<a
href={slice.data.json_endpoint}
className="btn btn-default btn-sm"
title="Export to .json"
target="_blank"
>
<i className="fa fa-file-code-o"></i> .json
</a>
<a
href={slice.data.csv_endpoint}
className={exportToCSVClasses}
title="Export to .csv format"
target="_blank"
>
<i className="fa fa-file-text-o"></i> .csv
</a>
<DisplayQueryButton slice={slice} />
</div>
);
}
ExploreActionButtons.propTypes = propTypes;

View File

@@ -1,31 +0,0 @@
import React, { PropTypes } from 'react';
import classnames from 'classnames';
const propTypes = {
canAdd: PropTypes.string.isRequired,
onQuery: PropTypes.func.isRequired,
};
export default function QueryAndSaveBtns({ canAdd, onQuery }) {
const saveClasses = classnames('btn btn-default btn-sm', {
'disabled disabledButton': canAdd !== 'True',
});
return (
<div className="btn-group query-and-save">
<button type="button" className="btn btn-primary btn-sm" onClick={onQuery}>
<i className="fa fa-bolt"></i> Query
</button>
<button
type="button"
className={saveClasses}
data-target="#save_modal"
data-toggle="modal"
>
<i className="fa fa-plus-circle"></i> Save as
</button>
</div>
);
}
QueryAndSaveBtns.propTypes = propTypes;

View File

@@ -1,401 +0,0 @@
// Javascript for the explorer page
// Init explorer view -> load vis dependencies -> read data (from dynamic html) -> render slice
// nb: to add a new vis, you must also add a Python fn in viz.py
//
// js
const $ = window.$ = require('jquery');
const px = require('./../modules/caravel.js');
const utils = require('./../modules/utils.js');
const jQuery = window.jQuery = require('jquery'); // eslint-disable-line
import React from 'react';
import ReactDOM from 'react-dom';
import QueryAndSaveBtns from './components/QueryAndSaveBtns.jsx';
import ExploreActionButtons from './components/ExploreActionButtons.jsx';
require('jquery-ui');
$.widget.bridge('uitooltip', $.ui.tooltip); // Shutting down jq-ui tooltips
require('bootstrap');
require('./../caravel-select2.js');
// css
require('../../vendor/pygments.css');
require('../../stylesheets/explore.css');
let slice;
const getPanelClass = function (fieldPrefix) {
return (fieldPrefix === 'flt' ? 'filter' : 'having') + '_panel';
};
function prepForm() {
// Assigning the right id to form elements in filters
const fixId = function ($filter, fieldPrefix, i) {
$filter.attr('id', function () {
return fieldPrefix + '_' + i;
});
['col', 'op', 'eq'].forEach(function (fieldMiddle) {
const fieldName = fieldPrefix + '_' + fieldMiddle;
$filter.find('[id^=' + fieldName + '_]')
.attr('id', function () {
return fieldName + '_' + i;
})
.attr('name', function () {
return fieldName + '_' + i;
});
});
};
['flt', 'having'].forEach(function (fieldPrefix) {
let i = 1;
$('#' + getPanelClass(fieldPrefix) + ' #filters > div').each(function () {
fixId($(this), fieldPrefix, i);
i++;
});
});
}
function query(forceUpdate, pushState) {
let force = forceUpdate;
if (force === undefined) {
force = false;
}
$('.query-and-save button').attr('disabled', 'disabled');
if (force) { // Don't hide the alert message when the page is just loaded
$('div.alert').remove();
}
$('#is_cached').hide();
prepForm();
if (pushState !== false) {
// update the url after prepForm() fix the field ids
history.pushState({}, document.title, slice.querystring());
}
slice.render(force);
}
function saveSlice() {
const action = $('input[name=rdo_save]:checked').val();
if (action === 'saveas') {
const sliceName = $('input[name=new_slice_name]').val();
if (sliceName === '') {
utils.showModal({
title: 'Error',
body: 'You must pick a name for the new slice',
});
return;
}
document.getElementById('slice_name').value = sliceName;
}
const addToDash = $('input[name=addToDash]:checked').val();
if (addToDash === 'existing' && $('#save_to_dashboard_id').val() === '') {
utils.showModal({
title: 'Error',
body: 'You must pick an existing dashboard',
});
return;
} else if (addToDash === 'new' && $('input[name=new_dashboard_name]').val() === '') {
utils.showModal({
title: 'Error',
body: 'Please enter a name for the new dashboard',
});
return;
}
$('#action').val(action);
prepForm();
$('#query').submit();
}
function initExploreView() {
function getCollapsedFieldsets() {
let collapsedFieldsets = $('#collapsedFieldsets').val();
if (collapsedFieldsets !== undefined && collapsedFieldsets !== '') {
collapsedFieldsets = collapsedFieldsets.split('||');
} else {
collapsedFieldsets = [];
}
return collapsedFieldsets;
}
function toggleFieldset(legend, animation) {
const parent = legend.parent();
const fieldset = parent.find('.legend_label').text();
const collapsedFieldsets = getCollapsedFieldsets();
let index;
if (parent.hasClass('collapsed')) {
if (animation) {
parent.find('.panel-body').slideDown();
} else {
parent.find('.panel-body').show();
}
parent.removeClass('collapsed');
parent.find('span.collapser').text('[-]');
// removing from array, js is overcomplicated
index = collapsedFieldsets.indexOf(fieldset);
if (index !== -1) {
collapsedFieldsets.splice(index, 1);
}
} else { // not collapsed
if (animation) {
parent.find('.panel-body').slideUp();
} else {
parent.find('.panel-body').hide();
}
parent.addClass('collapsed');
parent.find('span.collapser').text('[+]');
index = collapsedFieldsets.indexOf(fieldset);
if (index === -1 && fieldset !== '' && fieldset !== undefined) {
collapsedFieldsets.push(fieldset);
}
}
$('#collapsedFieldsets').val(collapsedFieldsets.join('||'));
}
px.initFavStars();
$('#viz_type').change(function () {
$('#query').submit();
});
$('#datasource_id').change(function () {
window.location = $(this).find('option:selected').attr('url');
});
const collapsedFieldsets = getCollapsedFieldsets();
for (let i = 0; i < collapsedFieldsets.length; i++) {
toggleFieldset($('legend:contains("' + collapsedFieldsets[i] + '")'), false);
}
function formatViz(viz) {
const url = `/static/assets/images/viz_thumbnails/${viz.id}.png`;
const noImg = '/static/assets/images/noimg.png';
return $(
`<img class="viz-thumb-option" src="${url}" onerror="this.src='${noImg}';">` +
`<span>${viz.text}</span>`
);
}
$('.select2').select2({
dropdownAutoWidth: true,
});
$('.select2Sortable').select2({
dropdownAutoWidth: true,
});
$('.select2-with-images').select2({
dropdownAutoWidth: true,
dropdownCssClass: 'bigdrop',
formatResult: formatViz,
});
$('.select2Sortable').select2Sortable({
bindOrder: 'sortableStop',
});
$('form').show();
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
$('.ui-helper-hidden-accessible').remove(); // jQuery-ui 1.11+ creates a div for every tooltip
function addFilter(i, fieldPrefix) {
const cp = $('#' + fieldPrefix + '0').clone();
$(cp).appendTo('#' + getPanelClass(fieldPrefix) + ' #filters');
$(cp).show();
if (i !== undefined) {
$(cp).find('#' + fieldPrefix + '_eq_0').val(px.getParam(fieldPrefix + '_eq_' + i));
$(cp).find('#' + fieldPrefix + '_op_0').val(px.getParam(fieldPrefix + '_op_' + i));
$(cp).find('#' + fieldPrefix + '_col_0').val(px.getParam(fieldPrefix + '_col_' + i));
}
$(cp).find('select').select2();
$(cp).find('.remove').click(function () {
$(this)
.parent()
.parent()
.remove();
});
}
function setFilters() {
['flt', 'having'].forEach(function (prefix) {
for (let i = 1; i < 10; i++) {
const col = px.getParam(prefix + '_col_' + i);
if (col !== '') {
addFilter(i, prefix);
}
}
});
}
setFilters();
$(window).bind('popstate', function () {
// Browser back button
const returnLocation = history.location || document.location;
// Could do something more lightweight here, but we're not optimizing
// for the use of the back button anyways
returnLocation.reload();
});
$('#filter_panel #plus').click(function () {
addFilter(undefined, 'flt');
});
$('#having_panel #plus').click(function () {
addFilter(undefined, 'having');
});
function createChoices(term, data) {
const filtered = $(data).filter(function () {
return this.text.localeCompare(term) === 0;
});
if (filtered.length === 0) {
return {
id: term,
text: term,
};
}
return {};
}
function initSelectionToValue(element, callback) {
callback({
id: element.val(),
text: element.val(),
});
}
$('.select2_freeform').each(function () {
const parent = $(this).parent();
const name = $(this).attr('name');
const l = [];
let selected = '';
for (let i = 0; i < this.options.length; i++) {
l.push({
id: this.options[i].value,
text: this.options[i].text,
});
if (this.options[i].selected) {
selected = this.options[i].value;
}
}
parent.append(
`<input class="${$(this).attr('class')}" ` +
`name="${name}" type="text" value="${selected}">`
);
$(`input[name='${name}']`).select2({
createSearchChoice: createChoices,
initSelection: initSelectionToValue,
dropdownAutoWidth: true,
multiple: false,
data: l,
});
$(this).remove();
});
function prepSaveDialog() {
const setButtonsState = function () {
const addToDash = $('input[name=addToDash]:checked').val();
if (addToDash === 'existing' || addToDash === 'new') {
$('.gotodash').removeAttr('disabled');
} else {
$('.gotodash').prop('disabled', true);
}
};
const url = '/dashboardmodelviewasync/api/read?_flt_0_owners=' + $('#userid').val();
$.get(url, function (data) {
const choices = [];
for (let i = 0; i < data.pks.length; i++) {
choices.push({ id: data.pks[i], text: data.result[i].dashboard_title });
}
$('#save_to_dashboard_id').select2({
data: choices,
dropdownAutoWidth: true,
}).on('select2-selecting', function () {
$('#addToDash_existing').prop('checked', true);
setButtonsState();
});
});
$('input[name=addToDash]').change(setButtonsState);
$("input[name='new_dashboard_name']").on('focus', function () {
$('#add_to_new_dash').prop('checked', true);
setButtonsState();
});
$("input[name='new_slice_name']").on('focus', function () {
$('#save_as_new').prop('checked', true);
setButtonsState();
});
$('#btn_modal_save').on('click', () => saveSlice());
$('#btn_modal_save_goto_dash').click(() => {
document.getElementById('goto_dash').value = 'true';
saveSlice();
});
}
prepSaveDialog();
}
function renderExploreActions() {
const exploreActionsEl = document.getElementById('js-explore-actions');
ReactDOM.render(
<ExploreActionButtons
canDownload={exploreActionsEl.getAttribute('data-can-download')}
slice={slice}
/>,
exploreActionsEl
);
}
function initComponents() {
const queryAndSaveBtnsEl = document.getElementById('js-query-and-save-btns');
ReactDOM.render(
<QueryAndSaveBtns
canAdd={queryAndSaveBtnsEl.getAttribute('data-can-add')}
onQuery={() => query(true)}
/>,
queryAndSaveBtnsEl
);
renderExploreActions();
}
let exploreController = {
type: 'slice',
done: (sliceObj) => {
slice = sliceObj;
renderExploreActions();
const cachedSelector = $('#is_cached');
if (slice.data !== undefined && slice.data.is_cached) {
cachedSelector
.attr(
'title',
`Served from data cached at ${slice.data.cached_dttm}. Click [Query] to force refresh`)
.show()
.tooltip('fixTitle');
} else {
cachedSelector.hide();
}
},
error: (sliceObj) => {
slice = sliceObj;
renderExploreActions();
},
};
exploreController = Object.assign({}, utils.controllerInterface, exploreController);
$(document).ready(function () {
const data = $('.slice').data('slice');
initExploreView();
slice = px.Slice(data, exploreController);
// call vis render method, which issues ajax
// calls render on the slice for the first time
query(false, false);
slice.bindResizeToWindowResize();
initComponents();
});

View File

@@ -1,148 +0,0 @@
const $ = window.$ = require('jquery');
export const SET_DATASOURCE = 'SET_DATASOURCE';
export const SET_TIME_COLUMN_OPTS = 'SET_TIME_COLUMN_OPTS';
export const SET_TIME_GRAIN_OPTS = 'SET_TIME_GRAIN_OPTS';
export const SET_GROUPBY_COLUMN_OPTS = 'SET_GROUPBY_COLUMN_OPTS';
export const SET_METRICS_OPTS = 'SET_METRICS_OPTS';
export const SET_COLUMN_OPTS = 'SET_COLUMN_OPTS';
export const SET_ORDERING_OPTS = 'SET_ORDERING_OPTS';
export const TOGGLE_SEARCHBOX = 'TOGGLE_SEARCHBOX';
export const SET_FILTER_COLUMN_OPTS = 'SET_FILTER_COLUMN_OPTS';
export const ADD_FILTER = 'ADD_FILTER';
export const SET_FILTER = 'SET_FILTER';
export const REMOVE_FILTER = 'REMOVE_FILTER';
export const CHANGE_FILTER_FIELD = 'CHANGE_FILTER_FIELD';
export const CHANGE_FILTER_OP = 'CHANGE_FILTER_OP';
export const CHANGE_FILTER_VALUE = 'CHANGE_FILTER_VALUE';
export const RESET_FORM_DATA = 'RESET_FORM_DATA';
export const CLEAR_ALL_OPTS = 'CLEAR_ALL_OPTS';
export const SET_DATASOURCE_TYPE = 'SET_DATASOURCE_TYPE';
export const SET_FORM_DATA = 'SET_FORM_DATA';
export function setTimeColumnOpts(timeColumnOpts) {
return { type: SET_TIME_COLUMN_OPTS, timeColumnOpts };
}
export function setTimeGrainOpts(timeGrainOpts) {
return { type: SET_TIME_GRAIN_OPTS, timeGrainOpts };
}
export function setGroupByColumnOpts(groupByColumnOpts) {
return { type: SET_GROUPBY_COLUMN_OPTS, groupByColumnOpts };
}
export function setMetricsOpts(metricsOpts) {
return { type: SET_METRICS_OPTS, metricsOpts };
}
export function setColumnOpts(columnOpts) {
return { type: SET_COLUMN_OPTS, columnOpts };
}
export function setOrderingOpts(orderingOpts) {
return { type: SET_ORDERING_OPTS, orderingOpts };
}
export function setFilterColumnOpts(filterColumnOpts) {
return { type: SET_FILTER_COLUMN_OPTS, filterColumnOpts };
}
export function resetFormData() {
// Clear all form data when switching datasource
return { type: RESET_FORM_DATA };
}
export function clearAllOpts() {
return { type: CLEAR_ALL_OPTS };
}
export function setDatasourceType(datasourceType) {
return { type: SET_DATASOURCE_TYPE, datasourceType };
}
export function setFormOpts(datasourceId, datasourceType) {
return function (dispatch) {
const timeColumnOpts = [];
const groupByColumnOpts = [];
const metricsOpts = [];
const filterColumnOpts = [];
const timeGrainOpts = [];
const columnOpts = [];
const orderingOpts = [];
if (datasourceId) {
const params = [`datasource_id=${datasourceId}`, `datasource_type=${datasourceType}`];
const url = '/caravel/fetch_datasource_metadata?' + params.join('&');
$.get(url, (data, status) => {
if (status === 'success') {
data.time_columns.forEach((d) => {
if (d) timeColumnOpts.push({ value: d, label: d });
});
data.groupby_cols.forEach((d) => {
if (d) groupByColumnOpts.push({ value: d, label: d });
});
data.metrics.forEach((d) => {
if (d) metricsOpts.push({ value: d[1], label: d[0] });
});
data.filter_cols.forEach((d) => {
if (d) filterColumnOpts.push({ value: d, label: d });
});
data.time_grains.forEach((d) => {
if (d) timeGrainOpts.push({ value: d, label: d });
});
data.columns.forEach((d) => {
if (d) columnOpts.push({ value: d, label: d });
});
data.ordering_cols.forEach((d) => {
if (d) orderingOpts.push({ value: d, label: d });
});
// Repopulate options for controls
dispatch(setTimeColumnOpts(timeColumnOpts));
dispatch(setTimeGrainOpts(timeGrainOpts));
dispatch(setGroupByColumnOpts(groupByColumnOpts));
dispatch(setMetricsOpts(metricsOpts));
dispatch(setFilterColumnOpts(filterColumnOpts));
dispatch(setColumnOpts(columnOpts));
dispatch(setOrderingOpts(orderingOpts));
}
});
} else {
// Clear all Select options
dispatch(clearAllOpts());
}
};
}
export function setDatasource(datasourceId) {
return { type: SET_DATASOURCE, datasourceId };
}
export function toggleSearchBox(searchBox) {
return { type: TOGGLE_SEARCHBOX, searchBox };
}
export function addFilter(filter) {
return { type: ADD_FILTER, filter };
}
export function removeFilter(filter) {
return { type: REMOVE_FILTER, filter };
}
export function changeFilterField(filter, field) {
return { type: CHANGE_FILTER_FIELD, filter, field };
}
export function changeFilterOp(filter, op) {
return { type: CHANGE_FILTER_OP, filter, op };
}
export function changeFilterValue(filter, value) {
return { type: CHANGE_FILTER_VALUE, filter, value };
}
export function setFormData(key, value) {
return { type: SET_FORM_DATA, key, value };
}

View File

@@ -1,90 +0,0 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { Panel } from 'react-bootstrap';
import visMap from '../../../visualizations/main';
const propTypes = {
sliceName: PropTypes.string.isRequired,
vizType: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
sliceContainerId: PropTypes.string.isRequired,
jsonEndpoint: PropTypes.string.isRequired,
};
class ChartContainer extends React.Component {
componentDidMount() {
this.renderVis();
}
componentDidUpdate() {
this.renderVis();
}
getMockedSliceObject() {
return {
jsonEndpoint: () => this.props.jsonEndpoint,
container: {
html: () => {
// this should be a callback to clear the contents of the slice container
},
css: () => {
// dimension can be 'height'
// pixel string can be '300px'
// should call callback to adjust height of chart
},
},
width: () => this.chartContainerRef.getBoundingClientRect().width,
height: () => parseInt(this.props.height, 10) - 100,
selector: `#${this.props.sliceContainerId}`,
done: () => {
// finished rendering callback
},
};
}
renderVis() {
const slice = this.getMockedSliceObject();
visMap[this.props.vizType](slice).render();
}
render() {
return (
<div className="chart-container">
<Panel
style={{ height: this.props.height }}
header={
<div className="panel-title">{this.props.sliceName}</div>
}
>
<div
id={this.props.sliceContainerId}
ref={(ref) => { this.chartContainerRef = ref; }}
/>
</Panel>
</div>
);
}
}
ChartContainer.propTypes = propTypes;
function mapStateToProps(state) {
return {
sliceName: state.sliceName,
vizType: state.viz.formData.vizType,
sliceContainerId: `slice-container-${state.viz.formData.sliceId}`,
jsonEndpoint: state.viz.jsonEndPoint,
};
}
function mapDispatchToProps() {
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChartContainer);

View File

@@ -1,89 +0,0 @@
import React from 'react';
import Select from 'react-select';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { VIZ_TYPES } from '../constants';
const propTypes = {
actions: React.PropTypes.object,
datasources: React.PropTypes.array,
datasourceId: React.PropTypes.number,
datasourceType: React.PropTypes.string,
vizType: React.PropTypes.string,
};
const defaultProps = {
datasources: [],
datasourceId: null,
datasourceType: null,
vizType: null,
};
class ChartControl extends React.Component {
componentWillMount() {
if (this.props.datasourceId) {
this.props.actions.setFormOpts(this.props.datasourceId, this.props.datasourceType);
}
}
changeDatasource(datasourceOpt) {
const val = (datasourceOpt) ? datasourceOpt.value : null;
this.props.actions.setDatasource(val);
this.props.actions.resetFormData();
this.props.actions.setFormOpts(val, this.props.datasourceType);
}
changeViz(opt) {
const val = opt ? opt.value : null;
this.props.actions.setFormData('vizType', val);
}
render() {
return (
<div className="panel">
<div className="panel-header">Chart Options</div>
<div className="panel-body">
<h5 className="section-heading">Datasource</h5>
<div className="row">
<Select
name="select-datasource"
placeholder="Select a datasource"
options={this.props.datasources.map((d) => ({ value: d[0], label: d[1] }))}
value={this.props.datasourceId}
autosize={false}
onChange={this.changeDatasource.bind(this)}
/>
</div>
<h5 className="section-heading">Viz Type</h5>
<div className="row">
<Select
name="select-viztype"
placeholder="Select a viz type"
options={VIZ_TYPES}
value={this.props.vizType}
autosize={false}
onChange={this.changeViz.bind(this)}
/>
</div>
</div>
</div>
);
}
}
ChartControl.propTypes = propTypes;
ChartControl.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
datasources: state.datasources,
datasourceId: state.datasourceId,
datasourceType: state.datasourceType,
vizType: state.viz.formData.vizType,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChartControl);

View File

@@ -1,40 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { Panel } from 'react-bootstrap';
import { DefaultControls, VIZ_CONTROL_MAPPING } from '../constants';
const propTypes = {
vizType: React.PropTypes.string,
};
const defaultProps = {
vizType: null,
};
function ControlPanelsContainer(props) {
return (
<Panel>
<div className="scrollbar-container">
<div className="scrollbar-content">
{DefaultControls}
{VIZ_CONTROL_MAPPING[props.vizType]}
</div>
</div>
</Panel>
);
}
ControlPanelsContainer.propTypes = propTypes;
ControlPanelsContainer.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
vizType: state.viz.formData.vizType,
};
}
function mapDispatchToProps() {
return {};
}
export default connect(mapStateToProps, mapDispatchToProps)(ControlPanelsContainer);

View File

@@ -1,46 +0,0 @@
import React from 'react';
import ChartContainer from './ChartContainer';
import ControlPanelsContainer from './ControlPanelsContainer';
import QueryAndSaveBtns from '../../explore/components/QueryAndSaveBtns';
export default class ExploreViewContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
height: this.getHeight(),
};
}
getHeight() {
const navHeight = 90;
return `${window.innerHeight - navHeight}px`;
}
render() {
return (
<div
className="container-fluid"
style={{
height: this.state.height,
overflow: 'hidden',
}}
>
<div className="row">
<div className="col-sm-4">
<QueryAndSaveBtns
canAdd="True"
onQuery={() => {}}
/>
<br /><br />
<ControlPanelsContainer />
</div>
<div className="col-sm-8">
<ChartContainer
height={this.state.height}
/>
</div>
</div>
</div>
);
}
}

View File

@@ -1,127 +0,0 @@
import React from 'react';
// import { Tab, Row, Col, Nav, NavItem } from 'react-bootstrap';
import Select from 'react-select';
import { Button } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import shortid from 'shortid';
const propTypes = {
actions: React.PropTypes.object,
filterColumnOpts: React.PropTypes.array,
filters: React.PropTypes.array,
};
const defaultProps = {
filterColumnOpts: [],
filters: [],
};
class Filters extends React.Component {
constructor(props) {
super(props);
this.state = {
opOpts: ['in', 'not in'],
};
}
changeField(filter, fieldOpt) {
const val = (fieldOpt) ? fieldOpt.value : null;
this.props.actions.changeFilterField(filter, val);
}
changeOp(filter, opOpt) {
const val = (opOpt) ? opOpt.value : null;
this.props.actions.changeFilterOp(filter, val);
}
changeValue(filter, value) {
this.props.actions.changeFilterValue(filter, value);
}
removeFilter(filter) {
this.props.actions.removeFilter(filter);
}
addFilter() {
this.props.actions.addFilter({
id: shortid.generate(),
field: null,
op: null,
value: null,
});
}
render() {
const filters = this.props.filters.map((filter) => (
<div>
<Select
className="row"
multi={false}
name="select-column"
placeholder="Select column"
options={this.props.filterColumnOpts}
value={filter.field}
autosize={false}
onChange={this.changeField.bind(this, filter)}
/>
<div className="row">
<Select
className="col-sm-3"
multi={false}
name="select-op"
placeholder="Select operator"
options={this.state.opOpts.map((o) => ({ value: o, label: o }))}
value={filter.op}
autosize={false}
onChange={this.changeOp.bind(this, filter)}
/>
<div className="col-sm-6">
<input
type="text"
onChange={this.changeValue.bind(this, filter)}
className="form-control input-sm"
placeholder="Filter value"
/>
</div>
<div className="col-sm-3">
<Button
bsStyle="primary"
onClick={this.removeFilter.bind(this, filter)}
>
<i className="fa fa-minus" />
</Button>
</div>
</div>
</div>
)
);
return (
<div className="panel space-1">
<div className="panel-header">Filters</div>
<div className="panel-body">
{filters}
<Button
bsStyle="primary"
onClick={this.addFilter.bind(this)}
>
<i className="fa fa-plus" />Add Filter
</Button>
</div>
</div>
);
}
}
Filters.propTypes = propTypes;
Filters.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
filterColumnOpts: state.filterColumnOpts,
filters: state.filters,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Filters);

View File

@@ -1,68 +0,0 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import SelectArray from './SelectArray';
const propTypes = {
actions: React.PropTypes.object,
metricsOpts: React.PropTypes.array,
metrics: React.PropTypes.array,
groupByColumnOpts: React.PropTypes.array,
groupByColumns: React.PropTypes.array,
};
const defaultProps = {
metricsOpts: [],
metrics: [],
groupByColumnOpts: [],
groupByColumns: [],
};
const GroupBy = (props) => {
const selects = [
{
key: 'groupByColumns',
title: 'Group By',
options: props.groupByColumnOpts,
value: props.groupByColumns,
multi: true,
width: '12',
},
{
key: 'metrics',
title: 'Metrics',
options: props.metricsOpts,
value: props.metrics,
multi: true,
width: '12',
}];
return (
<div className="panel">
<div className="panel-header">GroupBy</div>
<div className="panel-body">
<SelectArray selectArray={selects} />
</div>
</div>
);
};
GroupBy.propTypes = propTypes;
GroupBy.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
metricsOpts: state.metricsOpts,
metrics: state.viz.formData.metrics,
groupByColumnOpts: state.groupByColumnOpts,
groupByColumns: state.viz.formData.groupByColumns,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(GroupBy);

View File

@@ -1,68 +0,0 @@
import React from 'react';
import SelectArray from './SelectArray';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
const propTypes = {
actions: React.PropTypes.object,
columnOpts: React.PropTypes.array,
columns: React.PropTypes.array,
orderingOpts: React.PropTypes.array,
orderings: React.PropTypes.array,
};
const defaultProps = {
columnOpts: [],
columns: [],
orderingOpts: [],
orderings: [],
};
const NotGroupBy = (props) => {
const selects = [
{
key: 'columns',
title: 'Columns',
options: props.columnOpts,
value: props.columns,
multi: true,
width: '12',
},
{
key: 'orderings',
title: 'Orderings',
options: props.orderingOpts,
value: props.orderings,
multi: true,
width: '12',
}];
return (
<div className="panel">
<div className="panel-header">Not GroupBy</div>
<div className="panel-body">
<SelectArray selectArray={selects} />
</div>
</div>
);
};
NotGroupBy.propTypes = propTypes;
NotGroupBy.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
columnOpts: state.columnOpts,
columns: state.viz.formData.columns,
orderingOpts: state.orderingOpts,
orderings: state.viz.formData.orderings,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(NotGroupBy);

View File

@@ -1,61 +0,0 @@
import React from 'react';
import SelectArray from './SelectArray';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { timestampOptions, rowLimitOptions } from '../constants';
const propTypes = {
actions: React.PropTypes.object,
timeStampFormat: React.PropTypes.string,
rowLimit: React.PropTypes.number,
};
const defaultProps = {
timeStampFormat: null,
rowLimit: null,
};
const Options = (props) => {
const selects = [
{
key: 'timeStampFormat',
title: 'Timestamp Format',
options: timestampOptions.map((t) => ({ value: t[0], label: t[1] })),
value: props.timeStampFormat,
width: '12',
},
{
key: 'rowLimit',
title: 'Row Limit',
options: rowLimitOptions.map((r) => ({ value: r, label: r })),
value: props.rowLimit,
width: '12',
}];
return (
<div className="panel">
<div className="panel-header">Options</div>
<div className="panel-body">
<SelectArray selectArray={selects} />
</div>
</div>
);
};
Options.propTypes = propTypes;
Options.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
timeStampFormat: state.viz.formData.timeStampFormat,
rowLimit: state.viz.formData.rowLimit,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Options);

View File

@@ -1,74 +0,0 @@
import React from 'react';
import Select from 'react-select';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
const propTypes = {
actions: React.PropTypes.object,
selectArray: React.PropTypes.arrayOf(
React.PropTypes.shape({
key: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired,
options: React.PropTypes.array.isRequired,
value: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.array,
]),
width: React.PropTypes.string,
multi: React.PropTypes.bool,
})
).isRequired,
};
const defaultProps = {
selectArray: [],
};
class SelectArray extends React.Component {
changeSelectData(key, multi, opt) {
if (multi) this.props.actions.setFormData(key, opt);
else {
const val = opt ? opt.value : null;
this.props.actions.setFormData(key, val);
}
}
render() {
const selects = this.props.selectArray.map((obj) => (
<div
className={(obj.width) ? `col-sm-${obj.width}` : 'col-sm-6'}
key={obj.key}
>
<h5 className="section-heading">{obj.title}</h5>
<Select
multi={obj.multi}
name={`select-${obj.key}`}
options={obj.options}
value={obj.value}
autosize={false}
onChange={this.changeSelectData.bind(this, obj.key, obj.multi)}
/>
</div>
));
return (
<div>
{selects}
</div>
);
}
}
SelectArray.propTypes = propTypes;
SelectArray.defaultProps = defaultProps;
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SelectArray);

View File

@@ -1,55 +0,0 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
const propTypes = {
actions: React.PropTypes.object,
};
class SqlClause extends React.Component {
onChange(key, event) {
this.props.actions.setFormData(key, event.target.value);
}
render() {
return (
<div className="panel">
<div className="panel-header">SQL</div>
<div className="panel-body">
<div className="row">
<h5 className="section-heading">Where</h5>
<input
type="text"
onChange={this.onChange.bind(this, 'where')}
className="form-control input-sm"
placeholder="Where Clause"
/>
</div>
<div className="row">
<h5 className="section-heading">Having</h5>
<input
type="text"
onChange={this.onChange.bind(this, 'having')}
className="form-control input-sm"
placeholder="Having Clause"
/>
</div>
</div>
</div>
);
}
}
SqlClause.propTypes = propTypes;
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SqlClause);

View File

@@ -1,88 +0,0 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { sinceOptions, untilOptions } from '../constants';
import SelectArray from './SelectArray';
const propTypes = {
actions: React.PropTypes.object,
datasourceType: React.PropTypes.string,
timeColumnOpts: React.PropTypes.array,
timeColumn: React.PropTypes.string,
timeGrainOpts: React.PropTypes.array,
timeGrain: React.PropTypes.string,
since: React.PropTypes.string,
until: React.PropTypes.string,
};
const defaultProps = {
timeColumnOpts: [],
timeColumn: null,
timeGrainOpts: [],
timeGrain: null,
since: null,
until: null,
};
const TimeFilter = (props) => {
const isDatasourceTypeTable = props.datasourceType === 'table';
const timeColumnTitle = isDatasourceTypeTable ? 'Time Column' : 'Time Granularity';
const timeGrainTitle = isDatasourceTypeTable ? 'Time Grain' : 'Origin';
const selects = [
{
key: 'timeColumn',
title: timeColumnTitle,
options: props.timeColumnOpts,
value: props.timeColumn,
},
{
key: 'timeGrain',
title: timeGrainTitle,
options: props.timeGrainOpts,
value: props.timeGrain,
},
{
key: 'since',
title: 'Since',
options: sinceOptions.map((s) => ({ value: s, label: s })),
value: props.since,
},
{
key: 'until',
title: 'Until',
options: untilOptions.map((u) => ({ value: u, label: u })),
value: props.until,
}];
return (
<div className="panel">
<div className="panel-header">Time Filter</div>
<div className="panel-body">
<SelectArray selectArray={selects} />
</div>
</div>
);
};
TimeFilter.propTypes = propTypes;
TimeFilter.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
datasourceType: state.datasourceType,
timeColumnOpts: state.timeColumnOpts,
timeColumn: state.viz.formData.timeColumn,
timeGrainOpts: state.timeGrainOpts,
timeGrain: state.viz.formData.timeGrain,
since: state.viz.formData.since,
until: state.viz.formData.until,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(TimeFilter);

View File

@@ -1,76 +0,0 @@
import React from 'react';
import TimeFilter from './components/TimeFilter';
import ChartControl from './components/ChartControl';
import GroupBy from './components/GroupBy';
import SqlClause from './components/SqlClause';
import Filters from './components/Filters';
import NotGroupBy from './components/NotGroupBy';
import Options from './components/Options';
export const VIZ_TYPES = [
{ value: 'dist_bar', label: 'Distribution - Bar Chart', requiresTime: false },
{ value: 'pie', label: 'Pie Chart', requiresTime: false },
{ value: 'line', label: 'Time Series - Line Chart', requiresTime: true },
{ value: 'bar', label: 'Time Series - Bar Chart', requiresTime: true },
{ value: 'compare', label: 'Time Series - Percent Change', requiresTime: true },
{ value: 'area', label: 'Time Series - Stacked', requiresTime: true },
{ value: 'table', label: 'Table View', requiresTime: false },
{ value: 'markup', label: 'Markup', requiresTime: false },
{ value: 'pivot_table', label: 'Pivot Table', requiresTime: false },
{ value: 'separator', label: 'Separator', requiresTime: false },
{ value: 'word_cloud', label: 'Word Cloud', requiresTime: false },
{ value: 'treemap', label: 'Treemap', requiresTime: false },
{ value: 'cal_heatmap', label: 'Calendar Heatmap', requiresTime: true },
{ value: 'box_plot', label: 'Box Plot', requiresTime: false },
{ value: 'bubble', label: 'Bubble Chart', requiresTime: false },
{ value: 'big_number', label: 'Big Number with Trendline', requiresTime: false },
{ value: 'bubble', label: 'Bubble Chart', requiresTime: false },
{ value: 'histogram', label: 'Histogram', requiresTime: false },
{ value: 'sunburst', label: 'Sunburst', requiresTime: false },
{ value: 'sankey', label: 'Sankey', requiresTime: false },
{ value: 'directed_force', label: 'Directed Force Layout', requiresTime: false },
{ value: 'world_map', label: 'World Map', requiresTime: false },
{ value: 'filter_box', label: 'Filter Box', requiresTime: false },
{ value: 'iframe', label: 'iFrame', requiresTime: false },
{ value: 'para', label: 'Parallel Coordinates', requiresTime: false },
{ value: 'heatmap', label: 'Heatmap', requiresTime: false },
{ value: 'horizon', label: 'Horizon', requiresTime: false },
{ value: 'mapbox', label: 'Mapbox', requiresTime: false },
];
export const sinceOptions = ['1 hour ago', '12 hours ago', '1 day ago',
'7 days ago', '28 days ago', '90 days ago', '1 year ago'];
export const untilOptions = ['now', '1 day ago', '7 days ago',
'28 days ago', '90 days ago', '1 year ago'];
export const timestampOptions = [
['smart_date', 'Adaptative formating'],
['%m/%d/%Y', '"%m/%d/%Y" | 01/14/2019'],
['%Y-%m-%d', '"%Y-%m-%d" | 2019-01-14'],
['%Y-%m-%d %H:%M:%S',
'"%Y-%m-%d %H:%M:%S" | 2019-01-14 01:32:10'],
['%H:%M:%S', '"%H:%M:%S" | 01:32:10'],
];
export const rowLimitOptions = [10, 50, 100, 250, 500, 1000, 5000, 10000, 50000];
export const DefaultControls = (
<div>
<ChartControl />
<TimeFilter />
<GroupBy />
<SqlClause />
<Filters />
</div>
);
export const TableVizControls = (
<div>
<NotGroupBy />
<Options />
</div>
);
export const VIZ_CONTROL_MAPPING = {
table: TableVizControls,
};

View File

@@ -1,48 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ExploreViewContainer from './components/ExploreViewContainer';
import { createStore, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { initialState } from './stores/store';
const exploreViewContainer = document.getElementById('js-explore-view-container');
const bootstrapData = JSON.parse(exploreViewContainer.getAttribute('data-bootstrap'));
import { exploreReducer } from './reducers/exploreReducer';
const bootstrappedState = Object.assign(initialState, {
datasources: bootstrapData.datasources,
datasourceId: parseInt(bootstrapData.datasource_id, 10),
datasourceType: bootstrapData.datasource_type,
sliceName: bootstrapData.viz.form_data.slice_name,
viz: {
jsonEndPoint: bootstrapData.viz.json_endpoint,
data: bootstrapData.viz.data,
formData: {
sliceId: bootstrapData.viz.form_data.slice_id,
vizType: bootstrapData.viz.form_data.viz_type,
timeColumn: bootstrapData.viz.form_data.granularity_sqla,
timeGrain: bootstrapData.viz.form_data.time_grain_sqla,
metrics: [bootstrapData.viz.form_data.metrics].map((m) => ({ value: m, label: m })),
since: bootstrapData.viz.form_data.since,
until: bootstrapData.viz.form_data.until,
having: bootstrapData.viz.form_data.having,
where: bootstrapData.viz.form_data.where,
rowLimit: bootstrapData.viz.form_data.row_limit,
timeStampFormat: bootstrapData.viz.form_data.table_timestamp_format,
},
},
});
const store = createStore(exploreReducer, bootstrappedState,
compose(applyMiddleware(thunk))
);
ReactDOM.render(
<Provider store={store}>
<ExploreViewContainer />
</Provider>,
exploreViewContainer
);

View File

@@ -1,89 +0,0 @@
import { defaultFormData, defaultOpts } from '../stores/store';
import * as actions from '../actions/exploreActions';
import { addToArr, removeFromArr, alterInArr } from '../../../utils/reducerUtils';
const setFormInViz = function (state, action) {
const newFormData = Object.assign({}, state);
newFormData[action.key] = action.value;
return newFormData;
};
const setVizInState = function (state, action) {
switch (action.type) {
case actions.SET_FORM_DATA:
return Object.assign(
{},
state,
{ formData: setFormInViz(state.formData, action) }
);
default:
return state;
}
};
export const exploreReducer = function (state, action) {
const actionHandlers = {
[actions.SET_DATASOURCE]() {
return Object.assign({}, state, { datasourceId: action.datasourceId });
},
[actions.SET_TIME_COLUMN_OPTS]() {
return Object.assign({}, state, { timeColumnOpts: action.timeColumnOpts });
},
[actions.SET_TIME_GRAIN_OPTS]() {
return Object.assign({}, state, { timeGrainOpts: action.timeGrainOpts });
},
[actions.SET_GROUPBY_COLUMN_OPTS]() {
return Object.assign({}, state, { groupByColumnOpts: action.groupByColumnOpts });
},
[actions.SET_METRICS_OPTS]() {
return Object.assign({}, state, { metricsOpts: action.metricsOpts });
},
[actions.SET_COLUMN_OPTS]() {
return Object.assign({}, state, { columnOpts: action.columnOpts });
},
[actions.SET_ORDERING_OPTS]() {
return Object.assign({}, state, { orderingOpts: action.orderingOpts });
},
[actions.TOGGLE_SEARCHBOX]() {
return Object.assign({}, state, { searchBox: action.searchBox });
},
[actions.SET_FILTER_COLUMN_OPTS]() {
return Object.assign({}, state, { filterColumnOpts: action.filterColumnOpts });
},
[actions.ADD_FILTER]() {
return addToArr(state, 'filters', action.filter);
},
[actions.REMOVE_FILTER]() {
return removeFromArr(state, 'filters', action.filter);
},
[actions.CHANGE_FILTER_FIELD]() {
return alterInArr(state, 'filters', action.filter, { field: action.field });
},
[actions.CHANGE_FILTER_OP]() {
return alterInArr(state, 'filters', action.filter, { op: action.op });
},
[actions.CHANGE_FILTER_VALUE]() {
return alterInArr(state, 'filters', action.filter, { value: action.value });
},
[actions.RESET_FORM_DATA]() {
return Object.assign({}, state, defaultFormData);
},
[actions.CLEAR_ALL_OPTS]() {
return Object.assign({}, state, defaultOpts);
},
[actions.SET_DATASOURCE_TYPE]() {
return Object.assign({}, state, { datasourceType: action.datasourceType });
},
[actions.SET_FORM_DATA]() {
return Object.assign(
{},
state,
{ viz: setVizInState(state.viz, action) }
);
},
};
if (action.type in actionHandlers) {
return actionHandlers[action.type]();
}
return state;
};

View File

@@ -1,52 +0,0 @@
// TODO: add datasource_type here after druid support is added
export const defaultFormData = {
sliceId: null,
vizType: null,
timeColumn: null,
timeGrain: null,
groupByColumns: [],
metrics: [],
since: null,
until: null,
having: null,
where: null,
columns: [],
orderings: [],
timeStampFormat: 'smart_date',
rowLimit: 50000,
searchBox: false,
whereClause: '',
havingClause: '',
filters: [],
};
export const initialState = {
datasources: null,
datasourceId: null,
datasourceType: null,
timeColumnOpts: [],
timeGrainOpts: [],
timeGrain: null,
groupByColumnOpts: [],
metricsOpts: [],
columnOpts: [],
orderingOpts: [],
searchBox: false,
whereClause: '',
havingClause: '',
filters: [],
filterColumnOpts: [],
viz: {
formData: defaultFormData,
},
};
export const defaultOpts = {
timeColumnOpts: [],
timeGrainOpts: [],
groupByColumnOpts: [],
metricsOpts: [],
filterColumnOpts: [],
columnOpts: [],
orderingOpts: [],
};

View File

@@ -1,17 +0,0 @@
const $ = window.$ = require('jquery');
/* eslint no-unused-vars: 0 */
const jQuery = window.jQuery = $;
const px = require('./modules/caravel.js');
const utils = require('./modules/utils.js');
require('bootstrap');
const standaloneController = Object.assign(
{}, utils.controllerInterface, { type: 'standalone' });
$(document).ready(function () {
const data = $('.slice').data('slice');
const slice = px.Slice(data, standaloneController);
slice.render();
slice.bindResizeToWindowResize();
});

View File

@@ -1,50 +0,0 @@
import { it, describe } from 'mocha';
import { expect } from 'chai';
import shortid from 'shortid';
import * as actions from '../../../../javascripts/explorev2/actions/exploreActions';
import { initialState } from '../../../../javascripts/explorev2/stores/store';
import { exploreReducer } from '../../../../javascripts/explorev2/reducers/exploreReducer';
describe('reducers', () => {
it('should return new state with datasource id', () => {
const newState = exploreReducer(initialState, actions.setDatasource(1));
expect(newState.datasourceId).to.equal(1);
});
it('should return new state with search box toggled', () => {
const newState = exploreReducer(initialState, actions.toggleSearchBox(true));
expect(newState.searchBox).to.equal(true);
});
it('should return new state with added filter', () => {
const newFilter = {
id: shortid.generate(),
eq: 'value',
op: 'in',
col: 'vals',
};
const newState = exploreReducer(initialState, actions.addFilter(newFilter));
expect(newState.filters).to.deep.equal([newFilter]);
});
it('should return new state with removed filter', () => {
const filter1 = {
id: shortid.generate(),
eq: 'value',
op: 'in',
col: 'vals1',
};
const filter2 = {
id: shortid.generate(),
eq: 'value',
op: 'not in',
col: 'vals2',
};
const testState = {
initialState,
filters: [filter1, filter2],
};
const newState = exploreReducer(testState, actions.removeFilter(filter1));
expect(newState.filters).to.deep.equal([filter2]);
});
});

View File

@@ -1,30 +0,0 @@
import React from 'react';
import Select from 'react-select';
import { Button } from 'react-bootstrap';
import QuerySearch from '../../../javascripts/SqlLab/components/QuerySearch';
import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';
describe('QuerySearch', () => {
it('should render', () => {
expect(
React.isValidElement(<QuerySearch />)
).to.equal(true);
});
it('should have two Select', () => {
const wrapper = shallow(<QuerySearch />);
expect(wrapper.find(Select)).to.have.length(2);
});
it('should have one input for searchText', () => {
const wrapper = shallow(<QuerySearch />);
expect(wrapper.find('input')).to.have.length(1);
});
it('should have one Button', () => {
const wrapper = shallow(<QuerySearch />);
expect(wrapper.find(Button)).to.have.length(1);
});
});

View File

@@ -1,158 +0,0 @@
import React from 'react';
import Link from '../../../javascripts/SqlLab/components/Link';
import { Button } from 'react-bootstrap';
import { TableElement } from '../../../javascripts/SqlLab/components/TableElement';
import { shallow } from 'enzyme';
import { describe, it } from 'mocha';
import { expect } from 'chai';
describe('TableElement', () => {
const mockedProps = {
'table': {
"dbId": 1,
"queryEditorId": "rJ-KP47a",
"schema": "caravel",
"name": "ab_user",
"id": "r11Vgt60",
"indexes": [
{
"unique": true,
"column_names": [
"username"
],
"type": "UNIQUE",
"name": "username"
},
{
"unique": true,
"column_names": [
"email"
],
"type": "UNIQUE",
"name": "email"
},
{
"unique": false,
"column_names": [
"created_by_fk"
],
"name": "created_by_fk"
},
{
"unique": false,
"column_names": [
"changed_by_fk"
],
"name": "changed_by_fk"
}
],
"columns": [
{
"indexed": false,
"longType": "INTEGER(11)",
"type": "INTEGER",
"name": "id"
},
{
"indexed": false,
"longType": "VARCHAR(64)",
"type": "VARCHAR",
"name": "first_name"
},
{
"indexed": false,
"longType": "VARCHAR(64)",
"type": "VARCHAR",
"name": "last_name"
},
{
"indexed": true,
"longType": "VARCHAR(64)",
"type": "VARCHAR",
"name": "username"
},
{
"indexed": false,
"longType": "VARCHAR(256)",
"type": "VARCHAR",
"name": "password"
},
{
"indexed": false,
"longType": "TINYINT(1)",
"type": "TINYINT",
"name": "active"
},
{
"indexed": true,
"longType": "VARCHAR(64)",
"type": "VARCHAR",
"name": "email"
},
{
"indexed": false,
"longType": "DATETIME",
"type": "DATETIME",
"name": "last_login"
},
{
"indexed": false,
"longType": "INTEGER(11)",
"type": "INTEGER",
"name": "login_count"
},
{
"indexed": false,
"longType": "INTEGER(11)",
"type": "INTEGER",
"name": "fail_login_count"
},
{
"indexed": false,
"longType": "DATETIME",
"type": "DATETIME",
"name": "created_on"
},
{
"indexed": false,
"longType": "DATETIME",
"type": "DATETIME",
"name": "changed_on"
},
{
"indexed": true,
"longType": "INTEGER(11)",
"type": "INTEGER",
"name": "created_by_fk"
},
{
"indexed": true,
"longType": "INTEGER(11)",
"type": "INTEGER",
"name": "changed_by_fk"
}
],
"expanded": true
}
}
it('should just render', () => {
expect(
React.isValidElement(<TableElement />)
).to.equal(true);
});
it('should render with props', () => {
expect(
React.isValidElement(<TableElement {...mockedProps} />)
).to.equal(true);
});
it('has 3 Link elements', () => {
const wrapper = shallow(<TableElement {...mockedProps} />);
expect(wrapper.find(Link)).to.have.length(3);
});
it('has 14 columns', () => {
const wrapper = shallow(<TableElement {...mockedProps} />);
expect(wrapper.find('div.table-column')).to.have.length(14);
});
});

View File

@@ -1,28 +0,0 @@
import * as r from '../../../javascripts/SqlLab/reducers';
import * as actions from '../../../javascripts/SqlLab/actions';
import { describe, it } from 'mocha';
import { expect } from 'chai';
describe('sqlLabReducer', () => {
describe('CLONE_QUERY_TO_NEW_TAB', () => {
const testQuery = { sql: 'SELECT * FROM...', dbId: 1, id: 1 };
const state = Object.assign(r.initialState, { queries: [testQuery] });
const newState = r.sqlLabReducer(state, actions.cloneQueryToNewTab(testQuery));
it('should have at most one more tab', () => {
expect(newState.queryEditors).have.length(2);
});
it('should have the same SQL as the cloned query', () => {
expect(newState.queryEditors[1].sql).to.equal(testQuery.sql);
});
it('should prefix the new tab title with "Copy of"', () => {
expect(newState.queryEditors[1].title).to.include('Copy of');
});
it('should push the cloned tab onto tab history stack', () => {
expect(newState.tabHistory[1]).to.eq(newState.queryEditors[1].id);
});
});
});

View File

@@ -1,17 +0,0 @@
.scrollbar-container {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
}
.scrollbar-content {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
overflow: scroll;
margin-right: 0px;
margin-bottom: 100px;
}

View File

@@ -1,146 +0,0 @@
/**
* jQuery Select2 Sortable
* - enable select2 to be sortable via normal select element
*
* author : Vafour
* modified : Kevin Provance (kprovance)
* inspired by : jQuery Chosen Sortable (https://github.com/mrhenry/jquery-chosen-sortable)
* License : GPL
*/
(function ($) {
$.fn.extend({
select2SortableOrder: function () {
var $this = this.filter('[multiple]');
$this.each(function () {
var $select = $(this);
// skip elements not select2-ed
if (typeof ($select.data('select2')) !== 'object') {
return false;
}
var $select2 = $select.siblings('.select2-container');
var sorted;
// Opt group names
var optArr = [];
$select.find('optgroup').each(function(idx, val) {
optArr.push (val);
});
$select.find('option').each(function(idx, val) {
var groupName = $(this).parent('optgroup').prop('label');
var optVal = this;
if (groupName === undefined) {
if (this.value !== '' && !this.selected) {
optArr.push (optVal);
}
}
});
sorted = $($select2.find('.select2-choices li[class!="select2-search-field"]').map(function () {
if (!this) {
return undefined;
}
var id = $(this).data('select2Data').id;
return $select.find('option[value="' + id + '"]')[0];
}));
sorted.push.apply(sorted, optArr);
$select.children().remove();
$select.append(sorted);
});
return $this;
},
select2Sortable: function () {
var args = Array.prototype.slice.call(arguments, 0);
var $this = this.filter('[multiple]'),
validMethods = ['destroy'];
if (args.length === 0 || typeof (args[0]) === 'object') {
var defaultOptions = {
bindOrder: 'formSubmit', // or sortableStop
sortableOptions: {
placeholder: 'ui-state-highlight',
items: 'li:not(.select2-search-field)',
tolerance: 'pointer'
}
};
var options = $.extend(defaultOptions, args[0]);
// Init select2 only if not already initialized to prevent select2 configuration loss
if (typeof ($this.data('select2')) !== 'object') {
$this.select2();
}
$this.each(function () {
var $select = $(this)
var $select2choices = $select.siblings('.select2-container').find('.select2-choices');
// Init jQuery UI Sortable
$select2choices.sortable(options.sortableOptions);
switch (options.bindOrder) {
case 'sortableStop':
// apply options ordering in sortstop event
$select2choices.on("sortstop.select2sortable", function (event, ui) {
$select.select2SortableOrder();
});
$select.on('change', function (e) {
$(this).select2SortableOrder();
});
break;
default:
// apply options ordering in form submit
$select.closest('form').unbind('submit.select2sortable').on('submit.select2sortable', function () {
$select.select2SortableOrder();
});
break;
}
});
}
else if (typeof (args[0] === 'string')) {
if ($.inArray(args[0], validMethods) == -1) {
throw "Unknown method: " + args[0];
}
if (args[0] === 'destroy') {
$this.select2SortableDestroy();
}
}
return $this;
},
select2SortableDestroy: function () {
var $this = this.filter('[multiple]');
$this.each(function () {
var $select = $(this)
var $select2choices = $select.parent().find('.select2-choices');
// unbind form submit event
$select.closest('form').unbind('submit.select2sortable');
// unbind sortstop event
$select2choices.unbind("sortstop.select2sortable");
// destroy select2Sortable
$select2choices.sortable('destroy');
});
return $this;
}
});
}(jQuery));

View File

@@ -1,195 +0,0 @@
import d3 from 'd3';
import { formatDate } from '../javascripts/modules/dates';
require('./big_number.css');
function bigNumberVis(slice) {
const div = d3.select(slice.selector);
function render() {
d3.json(slice.jsonEndpoint(), function (error, payload) {
// Define the percentage bounds that define color from red to green
if (error !== null) {
slice.error(error.responseText, error);
return;
}
div.html(''); // reset
const fd = payload.form_data;
const json = payload.data;
const f = d3.format(fd.y_axis_format);
const fp = d3.format('+.1%');
const width = slice.width();
const height = slice.height();
const svg = div.append('svg');
svg.attr('width', width);
svg.attr('height', height);
const data = json.data;
let vCompare;
let v;
if (fd.viz_type === 'big_number') {
v = data[data.length - 1][1];
} else {
v = data[0][0];
}
if (json.compare_lag > 0) {
const pos = data.length - (json.compare_lag + 1);
if (pos >= 0) {
vCompare = (v / data[pos][1]) - 1;
}
}
const dateExt = d3.extent(data, (d) => d[0]);
const valueExt = d3.extent(data, (d) => d[1]);
const margin = 20;
const scaleX = d3.time.scale.utc().domain(dateExt).range([margin, width - margin]);
const scaleY = d3.scale.linear().domain(valueExt).range([height - (margin), margin]);
const colorRange = [d3.hsl(0, 1, 0.3), d3.hsl(120, 1, 0.3)];
const scaleColor = d3.scale
.linear().domain([-1, 1])
.interpolate(d3.interpolateHsl)
.range(colorRange)
.clamp(true);
const line = d3.svg.line()
.x(function (d) {
return scaleX(d[0]);
})
.y(function (d) {
return scaleY(d[1]);
})
.interpolate('basis');
let y = height / 2;
let g = svg.append('g');
// Printing big number
g.append('g').attr('class', 'digits')
.attr('opacity', 1)
.append('text')
.attr('x', width / 2)
.attr('y', y)
.attr('class', 'big')
.attr('alignment-baseline', 'middle')
.attr('id', 'bigNumber')
.style('font-weight', 'bold')
.style('cursor', 'pointer')
.text(f(v))
.style('font-size', d3.min([height, width]) / 3.5)
.style('text-anchor', 'middle')
.attr('fill', 'black');
// Printing big number subheader text
if (json.subheader !== null) {
g.append('text')
.attr('x', width / 2)
.attr('y', (height / 16) * 12)
.text(json.subheader)
.attr('id', 'subheader_text')
.style('font-size', d3.min([height, width]) / 8)
.style('text-anchor', 'middle');
}
if (fd.viz_type === 'big_number') {
// Drawing trend line
g.append('path')
.attr('d', function () {
return line(data);
})
.attr('stroke-width', 5)
.attr('opacity', 0.5)
.attr('fill', 'none')
.attr('stroke-linecap', 'round')
.attr('stroke', 'grey');
g = svg.append('g')
.attr('class', 'digits')
.attr('opacity', 1);
if (vCompare !== null) {
y = (height / 8) * 3;
}
const c = scaleColor(vCompare);
// Printing compare %
if (vCompare) {
g.append('text')
.attr('x', width / 2)
.attr('y', (height / 16) * 12)
.text(fp(vCompare) + json.compare_suffix)
.style('font-size', d3.min([height, width]) / 8)
.style('text-anchor', 'middle')
.attr('fill', c)
.attr('stroke', c);
}
const gAxis = svg.append('g').attr('class', 'axis').attr('opacity', 0);
g = gAxis.append('g');
const xAxis = d3.svg.axis()
.scale(scaleX)
.orient('bottom')
.ticks(4)
.tickFormat(formatDate);
g.call(xAxis);
g.attr('transform', 'translate(0,' + (height - margin) + ')');
g = gAxis.append('g').attr('transform', 'translate(' + (width - margin) + ',0)');
const yAxis = d3.svg.axis()
.scale(scaleY)
.orient('left')
.tickFormat(d3.format(fd.y_axis_format))
.tickValues(valueExt);
g.call(yAxis);
g.selectAll('text')
.style('text-anchor', 'end')
.attr('y', '-7')
.attr('x', '-4');
g.selectAll('text')
.style('font-size', '10px');
div.on('mouseover', function () {
const el = d3.select(this);
el.selectAll('path')
.transition()
.duration(500)
.attr('opacity', 1)
.style('stroke-width', '2px');
el.selectAll('g.digits')
.transition()
.duration(500)
.attr('opacity', 0.1);
el.selectAll('g.axis')
.transition()
.duration(500)
.attr('opacity', 1);
})
.on('mouseout', function () {
const el = d3.select(this);
el.select('path')
.transition()
.duration(500)
.attr('opacity', 0.5)
.style('stroke-width', '5px');
el.selectAll('g.digits')
.transition()
.duration(500)
.attr('opacity', 1);
el.selectAll('g.axis')
.transition()
.duration(500)
.attr('opacity', 0);
});
}
slice.done(payload);
});
}
return {
render,
resize: render,
};
}
module.exports = bigNumberVis;

View File

@@ -1,54 +0,0 @@
// JS
import d3 from 'd3';
// CSS
require('./cal_heatmap.css');
require('../node_modules/cal-heatmap/cal-heatmap.css');
const CalHeatMap = require('cal-heatmap');
function calHeatmap(slice) {
const div = d3.select(slice.selector);
const render = function () {
d3.json(slice.jsonEndpoint(), function (error, json) {
const data = json.data;
if (error !== null) {
slice.error(error.responseText, error);
return;
}
div.selectAll('*').remove();
const cal = new CalHeatMap();
const timestamps = data.timestamps;
const extents = d3.extent(Object.keys(timestamps), (key) => timestamps[key]);
const step = (extents[1] - extents[0]) / 5;
try {
cal.init({
start: data.start,
data: timestamps,
itemSelector: slice.selector,
tooltip: true,
domain: data.domain,
subDomain: data.subdomain,
range: data.range,
browsing: true,
legend: [extents[0], extents[0] + step, extents[0] + step * 2, extents[0] + step * 3],
});
} catch (e) {
slice.error(e);
}
slice.done(json);
});
};
return {
render,
resize: render,
};
}
module.exports = calHeatmap;

View File

@@ -1,182 +0,0 @@
/* eslint-disable no-param-reassign */
import d3 from 'd3';
require('./directed_force.css');
/* Modified from http://bl.ocks.org/d3noob/5141278 */
function directedForceVis(slice) {
const div = d3.select(slice.selector);
const render = function () {
const width = slice.width();
const height = slice.height() - 25;
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText, error);
return;
}
const linkLength = json.form_data.link_length || 200;
const charge = json.form_data.charge || -500;
const links = json.data;
const nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function (link) {
link.source = nodes[link.source] || (nodes[link.source] = {
name: link.source,
});
link.target = nodes[link.target] || (nodes[link.target] = {
name: link.target,
});
link.value = Number(link.value);
const targetName = link.target.name;
const sourceName = link.source.name;
if (nodes[targetName].total === undefined) {
nodes[targetName].total = link.value;
}
if (nodes[sourceName].total === undefined) {
nodes[sourceName].total = 0;
}
if (nodes[targetName].max === undefined) {
nodes[targetName].max = 0;
}
if (link.value > nodes[targetName].max) {
nodes[targetName].max = link.value;
}
if (nodes[targetName].min === undefined) {
nodes[targetName].min = 0;
}
if (link.value > nodes[targetName].min) {
nodes[targetName].min = link.value;
}
nodes[targetName].total += link.value;
});
/* eslint-disable no-use-before-define */
// add the curvy lines
function tick() {
path.attr('d', function (d) {
const dx = d.target.x - d.source.x;
const dy = d.target.y - d.source.y;
const dr = Math.sqrt(dx * dx + dy * dy);
return (
'M' +
d.source.x + ',' +
d.source.y + 'A' +
dr + ',' + dr + ' 0 0,1 ' +
d.target.x + ',' +
d.target.y
);
});
node.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
}
/* eslint-enable no-use-before-define */
const force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(linkLength)
.charge(charge)
.on('tick', tick)
.start();
div.selectAll('*').remove();
const svg = div.append('svg')
.attr('width', width)
.attr('height', height);
// build the arrow.
svg.append('svg:defs').selectAll('marker')
.data(['end']) // Different link/path types can be defined here
.enter()
.append('svg:marker') // This section adds in the arrows
.attr('id', String)
.attr('viewBox', '0 -5 10 10')
.attr('refX', 15)
.attr('refY', -1.5)
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5');
const edgeScale = d3.scale.linear()
.range([0.1, 0.5]);
// add the links and the arrows
const path = svg.append('svg:g').selectAll('path')
.data(force.links())
.enter()
.append('svg:path')
.attr('class', 'link')
.style('opacity', function (d) {
return edgeScale(d.value / d.target.max);
})
.attr('marker-end', 'url(#end)');
// define the nodes
const node = svg.selectAll('.node')
.data(force.nodes())
.enter()
.append('g')
.attr('class', 'node')
.on('mouseenter', function () {
d3.select(this)
.select('circle')
.transition()
.style('stroke-width', 5);
d3.select(this)
.select('text')
.transition()
.style('font-size', 25);
})
.on('mouseleave', function () {
d3.select(this)
.select('circle')
.transition()
.style('stroke-width', 1.5);
d3.select(this)
.select('text')
.transition()
.style('font-size', 12);
})
.call(force.drag);
// add the nodes
const ext = d3.extent(d3.values(nodes), function (d) {
return Math.sqrt(d.total);
});
const circleScale = d3.scale.linear()
.domain(ext)
.range([3, 30]);
node.append('circle')
.attr('r', function (d) {
return circleScale(Math.sqrt(d.total));
});
// add the text
node.append('text')
.attr('x', 6)
.attr('dy', '.35em')
.text(function (d) {
return d.name;
});
slice.done(json);
});
};
return {
render,
resize: render,
};
}
module.exports = directedForceVis;

View File

@@ -1,229 +0,0 @@
import d3 from 'd3';
import { colorScalerFactory } from '../javascripts/modules/colors';
const $ = require('jquery');
d3.tip = require('d3-tip');
require('./heatmap.css');
// Inspired from http://bl.ocks.org/mbostock/3074470
// https://jsfiddle.net/cyril123/h0reyumq/
function heatmapVis(slice) {
function refresh() {
const margin = {
top: 10,
right: 10,
bottom: 35,
left: 35,
};
d3.json(slice.jsonEndpoint(), function (error, payload) {
if (error) {
slice.error(error.responseText, error);
return;
}
const data = payload.data;
// Dynamically adjusts based on max x / y category lengths
function adjustMargins() {
const pixelsPerCharX = 4.5; // approx, depends on font size
const pixelsPerCharY = 6.8; // approx, depends on font size
let longestX = 1;
let longestY = 1;
let datum;
for (let i = 0; i < data.length; i++) {
datum = data[i];
longestX = Math.max(longestX, datum.x.length || 1);
longestY = Math.max(longestY, datum.y.length || 1);
}
margin.left = Math.ceil(Math.max(margin.left, pixelsPerCharY * longestY));
margin.bottom = Math.ceil(Math.max(margin.bottom, pixelsPerCharX * longestX));
}
function ordScale(k, rangeBands, reverse = false) {
let domain = {};
$.each(data, function (i, d) {
domain[d[k]] = true;
});
domain = Object.keys(domain).sort(function (a, b) {
return b - a;
});
if (reverse) {
domain.reverse();
}
if (rangeBands === undefined) {
return d3.scale.ordinal().domain(domain).range(d3.range(domain.length));
}
return d3.scale.ordinal().domain(domain).rangeBands(rangeBands);
}
slice.container.html('');
const matrix = {};
const fd = payload.form_data;
adjustMargins();
const width = slice.width();
const height = slice.height();
const hmWidth = width - (margin.left + margin.right);
const hmHeight = height - (margin.bottom + margin.top);
const fp = d3.format('.3p');
const xScale = ordScale('x');
const yScale = ordScale('y', undefined, true);
const xRbScale = ordScale('x', [0, hmWidth]);
const yRbScale = ordScale('y', [hmHeight, 0]);
const X = 0;
const Y = 1;
const heatmapDim = [xRbScale.domain().length, yRbScale.domain().length];
const color = colorScalerFactory(fd.linear_color_scheme);
const scale = [
d3.scale.linear()
.domain([0, heatmapDim[X]])
.range([0, hmWidth]),
d3.scale.linear()
.domain([0, heatmapDim[Y]])
.range([0, hmHeight]),
];
const container = d3.select(slice.selector);
const canvas = container.append('canvas')
.attr('width', heatmapDim[X])
.attr('height', heatmapDim[Y])
.style('width', hmWidth + 'px')
.style('height', hmHeight + 'px')
.style('image-rendering', fd.canvas_image_rendering)
.style('left', margin.left + 'px')
.style('top', margin.top + 'px')
.style('position', 'absolute');
const svg = container.append('svg')
.attr('width', width)
.attr('height', height)
.style('left', '0px')
.style('top', '0px')
.style('position', 'absolute');
const rect = svg.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.append('rect')
.style('fill-opacity', 0)
.attr('stroke', 'black')
.attr('width', hmWidth)
.attr('height', hmHeight);
const tip = d3.tip()
.attr('class', 'd3-tip')
.offset(function () {
const k = d3.mouse(this);
const x = k[0] - (hmWidth / 2);
return [k[1] - 20, x];
})
.html(function () {
let s = '';
const k = d3.mouse(this);
const m = Math.floor(scale[0].invert(k[0]));
const n = Math.floor(scale[1].invert(k[1]));
if (m in matrix && n in matrix[m]) {
const obj = matrix[m][n];
s += '<div><b>' + fd.all_columns_x + ': </b>' + obj.x + '<div>';
s += '<div><b>' + fd.all_columns_y + ': </b>' + obj.y + '<div>';
s += '<div><b>' + fd.metric + ': </b>' + obj.v + '<div>';
s += '<div><b>%: </b>' + fp(obj.perc) + '<div>';
tip.style('display', null);
} else {
// this is a hack to hide the tooltip because we have map it to a single <rect>
// d3-tip toggles opacity and calling hide here is undone by the lib after this call
tip.style('display', 'none');
}
return s;
});
rect.call(tip);
const xAxis = d3.svg.axis()
.scale(xRbScale)
.tickValues(xRbScale.domain().filter(
function (d, i) {
return !(i % (parseInt(fd.xscale_interval, 10)));
}))
.orient('bottom');
const yAxis = d3.svg.axis()
.scale(yRbScale)
.tickValues(yRbScale.domain().filter(
function (d, i) {
return !(i % (parseInt(fd.yscale_interval, 10)));
}))
.orient('left');
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(' + margin.left + ',' + (margin.top + hmHeight) + ')')
.call(xAxis)
.selectAll('text')
.style('text-anchor', 'end')
.attr('transform', 'rotate(-45)');
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.call(yAxis);
rect.on('mousemove', tip.show);
rect.on('mouseout', tip.hide);
const context = canvas.node().getContext('2d');
context.imageSmoothingEnabled = false;
// Compute the pixel colors; scaled by CSS.
function createImageObj() {
const imageObj = new Image();
const image = context.createImageData(heatmapDim[0], heatmapDim[1]);
const pixs = {};
$.each(data, function (i, d) {
const c = d3.rgb(color(d.perc));
const x = xScale(d.x);
const y = yScale(d.y);
pixs[x + (y * xScale.domain().length)] = c;
if (matrix[x] === undefined) {
matrix[x] = {};
}
if (matrix[x][y] === undefined) {
matrix[x][y] = d;
}
});
let p = -1;
for (let i = 0; i < heatmapDim[0] * heatmapDim[1]; i++) {
let c = pixs[i];
let alpha = 255;
if (c === undefined) {
c = d3.rgb('#F00');
alpha = 0;
}
image.data[++p] = c.r;
image.data[++p] = c.g;
image.data[++p] = c.b;
image.data[++p] = alpha;
}
context.putImageData(image, 0, 0);
imageObj.src = canvas.node().toDataURL();
}
createImageObj();
slice.done(payload);
});
}
return {
render: refresh,
resize: refresh,
};
}
module.exports = heatmapVis;

View File

@@ -1,25 +0,0 @@
const $ = require('jquery');
function iframeWidget(slice) {
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
const url = slice.render_template(payload.form_data.url);
slice.container.html('<iframe style="width:100%;"></iframe>');
const iframe = slice.container.find('iframe');
iframe.css('height', slice.height());
iframe.attr('src', url);
slice.done(payload);
})
.fail(function (xhr) {
slice.error(xhr.responseText, xhr);
});
}
return {
render: refresh,
resize: refresh,
};
}
module.exports = iframeWidget;

View File

@@ -1,22 +0,0 @@
const $ = require('jquery');
require('./markup.css');
function markupWidget(slice) {
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
slice.container.html(payload.data.html);
slice.done(payload);
})
.fail(function (xhr) {
slice.error(xhr.responseText, xhr);
});
}
return {
render: refresh,
resize: refresh,
};
}
module.exports = markupWidget;

View File

@@ -1,363 +0,0 @@
// JS
import { category21 } from '../javascripts/modules/colors';
import { timeFormatFactory, formatDate } from '../javascripts/modules/dates';
const d3 = require('d3');
const nv = require('nvd3');
// CSS
require('../node_modules/nvd3/build/nv.d3.min.css');
require('./nvd3_vis.css');
const minBarWidth = 15;
const animationTime = 1000;
const addTotalBarValues = function (chart, data, stacked) {
const svg = d3.select('svg');
const format = d3.format('.3s');
const countSeriesDisplayed = data.length;
const totalStackedValues = stacked && data.length !== 0 ?
data[0].values.map(function (bar, iBar) {
const bars = data.map(function (series) {
return series.values[iBar];
});
return d3.sum(bars, function (d) {
return d.y;
});
}) : [];
const rectsToBeLabeled = svg.selectAll('g.nv-group').filter(
function (d, i) {
if (!stacked) {
return true;
}
return i === countSeriesDisplayed - 1;
}).selectAll('rect.positive');
const groupLabels = svg.select('g.nv-barsWrap').append('g');
rectsToBeLabeled.each(
function (d, index) {
const rectObj = d3.select(this);
const transformAttr = rectObj.attr('transform');
const yPos = parseFloat(rectObj.attr('y'));
const xPos = parseFloat(rectObj.attr('x'));
const rectWidth = parseFloat(rectObj.attr('width'));
const t = groupLabels.append('text')
.attr('x', xPos) // rough position first, fine tune later
.attr('y', yPos - 5)
.text(format(stacked ? totalStackedValues[index] : d.y))
.attr('transform', transformAttr)
.attr('class', 'bar-chart-label');
const labelWidth = t.node().getBBox().width;
t.attr('x', xPos + rectWidth / 2 - labelWidth / 2); // fine tune
});
};
function nvd3Vis(slice) {
let chart;
let colorKey = 'key';
const render = function () {
d3.json(slice.jsonEndpoint(), function (error, payload) {
slice.container.html('');
// Check error first, otherwise payload can be null
if (error) {
slice.error(error.responseText, error);
return;
}
// Calculates the longest label size for stretching bottom margin
function calculateStretchMargins(payloadData) {
let stretchMargin = 0;
const pixelsPerCharX = 4.5; // approx, depends on font size
let maxLabelSize = 10; // accomodate for shorter labels
payloadData.data.forEach((d) => {
const axisLabels = d.values;
for (let i = 0; i < axisLabels.length; i++) {
maxLabelSize = Math.max(axisLabels[i].x.length, maxLabelSize);
}
});
stretchMargin = Math.ceil(pixelsPerCharX * maxLabelSize);
return stretchMargin;
}
let width = slice.width();
const fd = payload.form_data;
const barchartWidth = function () {
let bars;
if (fd.bar_stacked) {
bars = d3.max(payload.data, function (d) { return d.values.length; });
} else {
bars = d3.sum(payload.data, function (d) { return d.values.length; });
}
if (bars * minBarWidth > width) {
return bars * minBarWidth;
}
return width;
};
const vizType = fd.viz_type;
const f = d3.format('.3s');
const reduceXTicks = fd.reduce_x_ticks || false;
let stacked = false;
let row;
nv.addGraph(function () {
switch (vizType) {
case 'line':
if (fd.show_brush) {
chart = nv.models.lineWithFocusChart();
chart.focus.xScale(d3.time.scale.utc());
chart.x2Axis
.showMaxMin(fd.x_axis_showminmax)
.staggerLabels(false);
} else {
chart = nv.models.lineChart();
}
// To alter the tooltip header
// chart.interactiveLayer.tooltip.headerFormatter(function(){return '';});
chart.xScale(d3.time.scale.utc());
chart.interpolate(fd.line_interpolation);
chart.xAxis
.showMaxMin(fd.x_axis_showminmax)
.staggerLabels(false);
break;
case 'bar':
chart = nv.models.multiBarChart()
.showControls(fd.show_controls)
.groupSpacing(0.1);
if (!reduceXTicks) {
width = barchartWidth();
}
chart.width(width);
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
stacked = fd.bar_stacked;
chart.stacked(stacked);
if (fd.show_bar_value) {
setTimeout(function () {
addTotalBarValues(chart, payload.data, stacked);
}, animationTime);
}
break;
case 'dist_bar':
chart = nv.models.multiBarChart()
.showControls(fd.show_controls)
.reduceXTicks(reduceXTicks)
.rotateLabels(45)
.groupSpacing(0.1); // Distance between each group of bars.
chart.xAxis
.showMaxMin(false);
stacked = fd.bar_stacked;
chart.stacked(stacked);
if (fd.show_bar_value) {
setTimeout(function () {
addTotalBarValues(chart, payload.data, stacked);
}, animationTime);
}
if (!reduceXTicks) {
width = barchartWidth();
}
chart.width(width);
break;
case 'pie':
chart = nv.models.pieChart();
colorKey = 'x';
chart.valueFormat(f);
if (fd.donut) {
chart.donut(true);
}
chart.labelsOutside(fd.labels_outside);
chart.labelThreshold(0.05) // Configure the minimum slice size for labels to show up
.labelType(fd.pie_label_type);
chart.cornerRadius(true);
break;
case 'column':
chart = nv.models.multiBarChart()
.reduceXTicks(false)
.rotateLabels(45);
break;
case 'compare':
chart = nv.models.cumulativeLineChart();
chart.xScale(d3.time.scale.utc());
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
break;
case 'bubble':
row = (col1, col2) => `<tr><td>${col1}</td><td>${col2}</td></tr>`;
chart = nv.models.scatterChart();
chart.showDistX(true);
chart.showDistY(true);
chart.tooltip.contentGenerator(function (obj) {
const p = obj.point;
let s = '<table>';
s += (
`<tr><td style="color: ${p.color};">` +
`<strong>${p[fd.entity]}</strong> (${p.group})` +
'</td></tr>');
s += row(fd.x, f(p.x));
s += row(fd.y, f(p.y));
s += row(fd.size, f(p.size));
s += '</table>';
return s;
});
chart.pointRange([5, fd.max_bubble_size * fd.max_bubble_size]);
break;
case 'area':
chart = nv.models.stackedAreaChart();
chart.showControls(fd.show_controls);
chart.style(fd.stacked_style);
chart.xScale(d3.time.scale.utc());
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
break;
case 'box_plot':
colorKey = 'label';
chart = nv.models.boxPlotChart();
chart.x(function (d) {
return d.label;
});
chart.staggerLabels(true);
chart.maxBoxWidth(75); // prevent boxes from being incredibly wide
break;
default:
throw new Error('Unrecognized visualization for nvd3' + vizType);
}
if ('showLegend' in chart && typeof fd.show_legend !== 'undefined') {
chart.showLegend(fd.show_legend);
}
let height = slice.height() - 15;
chart.height(height);
slice.container.css('height', height + 'px');
if ((vizType === 'line' || vizType === 'area') && fd.rich_tooltip) {
chart.useInteractiveGuideline(true);
}
if (fd.y_axis_zero) {
chart.forceY([0]);
} else if (fd.y_log_scale) {
chart.yScale(d3.scale.log());
}
if (fd.x_log_scale) {
chart.xScale(d3.scale.log());
}
let xAxisFormatter;
if (vizType === 'bubble') {
xAxisFormatter = d3.format('.3s');
} else if (fd.x_axis_format === 'smart_date') {
xAxisFormatter = formatDate;
chart.xAxis.tickFormat(xAxisFormatter);
} else if (fd.x_axis_format !== undefined) {
xAxisFormatter = timeFormatFactory(fd.x_axis_format);
chart.xAxis.tickFormat(xAxisFormatter);
}
if (chart.hasOwnProperty('x2Axis')) {
chart.x2Axis.tickFormat(xAxisFormatter);
height += 30;
}
if (vizType === 'bubble') {
chart.xAxis.tickFormat(d3.format('.3s'));
} else if (fd.x_axis_format === 'smart_date') {
chart.xAxis.tickFormat(formatDate);
} else if (fd.x_axis_format !== undefined) {
chart.xAxis.tickFormat(timeFormatFactory(fd.x_axis_format));
}
if (chart.yAxis !== undefined) {
chart.yAxis.tickFormat(d3.format('.3s'));
}
if (fd.y_axis_format) {
chart.yAxis.tickFormat(d3.format(fd.y_axis_format));
if (chart.y2Axis !== undefined) {
chart.y2Axis.tickFormat(d3.format(fd.y_axis_format));
}
}
chart.color((d) => category21(d[colorKey]));
if (fd.x_axis_label && fd.x_axis_label !== '' && chart.xAxis) {
let distance = 0;
if (fd.bottom_margin) {
distance = fd.bottom_margin - 50;
}
chart.xAxis.axisLabel(fd.x_axis_label).axisLabelDistance(distance);
}
if (fd.y_axis_label && fd.y_axis_label !== '' && chart.yAxis) {
chart.yAxis.axisLabel(fd.y_axis_label);
chart.margin({ left: 90 });
}
if (fd.bottom_margin === 'auto') {
if (vizType === 'dist_bar') {
const stretchMargin = calculateStretchMargins(payload);
chart.margin({ bottom: stretchMargin });
} else {
chart.margin({ bottom: 50 });
}
} else {
chart.margin({ bottom: fd.bottom_margin });
}
let svg = d3.select(slice.selector).select('svg');
if (svg.empty()) {
svg = d3.select(slice.selector).append('svg');
}
svg
.datum(payload.data)
.transition().duration(500)
.attr('height', height)
.attr('width', width)
.call(chart);
if (fd.show_markers) {
svg.selectAll('.nv-point')
.style('stroke-opacity', 1)
.style('fill-opacity', 1);
}
return chart;
});
slice.done(payload);
});
};
const update = function () {
if (chart && chart.update) {
chart.update();
}
};
return {
render,
resize: update,
};
}
module.exports = nvd3Vis;

View File

@@ -1,105 +0,0 @@
const $ = require('jquery');
import d3 from 'd3';
d3.parcoords = require('../vendor/parallel_coordinates/d3.parcoords.js');
d3.divgrid = require('../vendor/parallel_coordinates/divgrid.js');
require('../vendor/parallel_coordinates/d3.parcoords.css');
require('./parallel_coordinates.css');
function parallelCoordVis(slice) {
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
const fd = payload.form_data;
const data = payload.data;
let cols = fd.metrics;
if (fd.include_series) {
cols = [fd.series].concat(fd.metrics);
}
const ttypes = {};
ttypes[fd.series] = 'string';
fd.metrics.forEach(function (v) {
ttypes[v] = 'number';
});
let ext = d3.extent(data, function (d) {
return d[fd.secondary_metric];
});
ext = [ext[0], (ext[1] - ext[0]) / 2, ext[1]];
const cScale = d3.scale.linear()
.domain(ext)
.range(['red', 'grey', 'blue'])
.interpolate(d3.interpolateLab);
const color = function (d) {
return cScale(d[fd.secondary_metric]);
};
const container = d3.select(slice.selector);
container.selectAll('*').remove();
const effHeight = fd.show_datatable ? (slice.height() / 2) : slice.height();
container.append('div')
.attr('id', 'parcoords_' + slice.container_id)
.style('height', effHeight + 'px')
.classed('parcoords', true);
const parcoords = d3.parcoords()('#parcoords_' + slice.container_id)
.width(slice.width())
.color(color)
.alpha(0.5)
.composite('darken')
.height(effHeight)
.data(data)
.dimensions(cols)
.types(ttypes)
.render()
.createAxes()
.shadows()
.reorderable()
.brushMode('1D-axes');
if (fd.show_datatable) {
// create data table, row hover highlighting
const grid = d3.divgrid();
container.append('div')
.style('height', effHeight + 'px')
.datum(data)
.call(grid)
.classed('parcoords grid', true)
.selectAll('.row')
.on({
mouseover(d) {
parcoords.highlight([d]);
},
mouseout: parcoords.unhighlight,
});
// update data table on brush event
parcoords.on('brush', function (d) {
d3.select('.grid')
.datum(d)
.call(grid)
.selectAll('.row')
.on({
mouseover(dd) {
parcoords.highlight([dd]);
},
mouseout: parcoords.unhighlight,
});
});
}
slice.done(payload);
})
.fail(function (xhr) {
slice.error(xhr.responseText, xhr);
});
}
return {
render: refresh,
resize: refresh,
};
}
module.exports = parallelCoordVis;

View File

@@ -1,38 +0,0 @@
import { fixDataTableBodyHeight } from '../javascripts/modules/utils';
const $ = require('jquery');
require('datatables.net-bs');
require('./pivot_table.css');
require('datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
module.exports = function (slice) {
const container = slice.container;
function refresh() {
$.getJSON(slice.jsonEndpoint(), function (json) {
const fd = json.form_data;
container.html(json.data);
if (fd.groupby.length === 1) {
const height = container.height();
const table = container.find('table').DataTable({
paging: false,
searching: false,
bInfo: false,
scrollY: height + 'px',
scrollCollapse: true,
scrollX: true,
});
table.column('-1').order('desc').draw();
fixDataTableBodyHeight(
container.find('.dataTables_wrapper'), height);
}
slice.done(json);
}).fail(function (xhr) {
slice.error(xhr.responseText, xhr);
});
}
return {
render: refresh,
resize: refresh,
};
};

View File

@@ -1,184 +0,0 @@
/* eslint-disable no-param-reassign */
import { category21 } from '../javascripts/modules/colors';
import d3 from 'd3';
d3.sankey = require('d3-sankey').sankey;
require('./sankey.css');
function sankeyVis(slice) {
const div = d3.select(slice.selector);
const render = function () {
const margin = {
top: 5,
right: 5,
bottom: 5,
left: 5,
};
const width = slice.width() - margin.left - margin.right;
const height = slice.height() - margin.top - margin.bottom;
const formatNumber = d3.format(',.2f');
div.selectAll('*').remove();
const svg = div.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
const tooltip = div.append('div')
.attr('class', 'sankey-tooltip')
.style('opacity', 0);
const sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
const path = sankey.link();
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText, error);
return;
}
let nodes = {};
// Compute the distinct nodes from the links.
const links = json.data.map(function (row) {
const link = Object.assign({}, row);
link.source = nodes[link.source] || (nodes[link.source] = { name: link.source });
link.target = nodes[link.target] || (nodes[link.target] = { name: link.target });
link.value = Number(link.value);
return link;
});
nodes = d3.values(nodes);
sankey
.nodes(nodes)
.links(links)
.layout(32);
function getTooltipHtml(d) {
let html;
if (d.sourceLinks) { // is node
html = d.name + " Value: <span class='emph'>" + formatNumber(d.value) + '</span>';
} else {
const val = formatNumber(d.value);
const sourcePercent = d3.round((d.value / d.source.value) * 100, 1);
const targetPercent = d3.round((d.value / d.target.value) * 100, 1);
html = [
"<div class=''>Path Value: <span class='emph'>", val, '</span></div>',
"<div class='percents'>",
"<span class='emph'>",
(isFinite(sourcePercent) ? sourcePercent : '100'),
'%</span> of ', d.source.name, '<br/>',
"<span class='emph'>" +
(isFinite(targetPercent) ? targetPercent : '--') +
'%</span> of ', d.target.name, 'target',
'</div>',
].join('');
}
return html;
}
function onmouseover(d) {
tooltip
.html(function () { return getTooltipHtml(d); })
.transition()
.duration(200)
.style('left', (d3.event.layerX + 10) + 'px')
.style('top', (d3.event.layerY + 10) + 'px')
.style('opacity', 0.95);
}
function onmouseout() {
tooltip.transition()
.duration(100)
.style('opacity', 0);
}
const link = svg.append('g').selectAll('.link')
.data(links)
.enter()
.append('path')
.attr('class', 'link')
.attr('d', path)
.style('stroke-width', (d) => Math.max(1, d.dy))
.sort((a, b) => b.dy - a.dy)
.on('mouseover', onmouseover)
.on('mouseout', onmouseout);
function dragmove(d) {
d3.select(this)
.attr(
'transform',
`translate(${d.x},${(d.y = Math.max(0, Math.min(height - d.dy, d3.event.y)))})`
);
sankey.relayout();
link.attr('d', path);
}
const node = svg.append('g').selectAll('.node')
.data(nodes)
.enter()
.append('g')
.attr('class', 'node')
.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.call(d3.behavior.drag()
.origin(function (d) {
return d;
})
.on('dragstart', function () {
this.parentNode.appendChild(this);
})
.on('drag', dragmove)
);
node.append('rect')
.attr('height', function (d) {
return d.dy;
})
.attr('width', sankey.nodeWidth())
.style('fill', function (d) {
d.color = category21(d.name.replace(/ .*/, ''));
return d.color;
})
.style('stroke', function (d) {
return d3.rgb(d.color).darker(2);
})
.on('mouseover', onmouseover)
.on('mouseout', onmouseout);
node.append('text')
.attr('x', -6)
.attr('y', function (d) {
return d.dy / 2;
})
.attr('dy', '.35em')
.attr('text-anchor', 'end')
.attr('transform', null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
.attr('x', 6 + sankey.nodeWidth())
.attr('text-anchor', 'start');
slice.done(json);
});
};
return {
render,
resize: render,
};
}
module.exports = sankeyVis;

View File

@@ -1,389 +0,0 @@
/* eslint-disable no-underscore-dangle, no-param-reassign */
import d3 from 'd3';
import { category21 } from '../javascripts/modules/colors';
import { wrapSvgText } from '../javascripts/modules/utils';
require('./sunburst.css');
// Modified from http://bl.ocks.org/kerryrodden/7090426
function sunburstVis(slice) {
const container = d3.select(slice.selector);
const render = function () {
// vars with shared scope within this function
const margin = { top: 10, right: 5, bottom: 10, left: 5 };
const containerWidth = slice.width();
const containerHeight = slice.height();
const breadcrumbHeight = containerHeight * 0.085;
const visWidth = containerWidth - margin.left - margin.right;
const visHeight = containerHeight - margin.top - margin.bottom - breadcrumbHeight;
const radius = Math.min(visWidth, visHeight) / 2;
let colorByCategory = true; // color by category if primary/secondary metrics match
let maxBreadcrumbs;
let breadcrumbDims; // set based on data
let totalSize; // total size of all segments; set after loading the data.
let colorScale;
let breadcrumbs;
let vis;
let arcs;
let gMiddleText; // dom handles
// Helper + path gen functions
const partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function (d) { return d.m1; });
const arc = d3.svg.arc()
.startAngle((d) => d.x)
.endAngle((d) => d.x + d.dx)
.innerRadius(function (d) {
return Math.sqrt(d.y);
})
.outerRadius(function (d) {
return Math.sqrt(d.y + d.dy);
});
const formatNum = d3.format('.3s');
const formatPerc = d3.format('.3p');
container.select('svg').remove();
const svg = container.append('svg:svg')
.attr('width', containerWidth)
.attr('height', containerHeight);
function createBreadcrumbs(rawData) {
const firstRowData = rawData.data[0];
// -2 bc row contains 2x metrics, +extra for %label and buffer
maxBreadcrumbs = (firstRowData.length - 2) + 1;
breadcrumbDims = {
width: visWidth / maxBreadcrumbs,
height: breadcrumbHeight * 0.8, // more margin
spacing: 3,
tipTailWidth: 10,
};
breadcrumbs = svg.append('svg:g')
.attr('class', 'breadcrumbs')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
breadcrumbs.append('svg:text')
.attr('class', 'end-label');
}
// Given a node in a partition layout, return an array of all of its ancestor
// nodes, highest first, but excluding the root.
function getAncestors(node) {
const path = [];
let current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
const points = [];
points.push('0,0');
points.push(breadcrumbDims.width + ',0');
points.push(
breadcrumbDims.width + breadcrumbDims.tipTailWidth + ',' + (breadcrumbDims.height / 2));
points.push(breadcrumbDims.width + ',' + breadcrumbDims.height);
points.push('0,' + breadcrumbDims.height);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(breadcrumbDims.tipTailWidth + ',' + (breadcrumbDims.height / 2));
}
return points.join(' ');
}
function updateBreadcrumbs(sequenceArray, percentageString) {
const g = breadcrumbs.selectAll('g')
.data(sequenceArray, function (d) {
return d.name + d.depth;
});
// Add breadcrumb and label for entering nodes.
const entering = g.enter().append('svg:g');
entering.append('svg:polygon')
.attr('points', breadcrumbPoints)
.style('fill', function (d) {
return colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1);
});
entering.append('svg:text')
.attr('x', (breadcrumbDims.width + breadcrumbDims.tipTailWidth) / 2)
.attr('y', breadcrumbDims.height / 4)
.attr('dy', '0.35em')
.style('fill', function (d) {
// Make text white or black based on the lightness of the background
const col = d3.hsl(colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1));
return col.l < 0.5 ? 'white' : 'black';
})
.attr('class', 'step-label')
.text(function (d) { return d.name.replace(/_/g, ' '); })
.call(wrapSvgText, breadcrumbDims.width, breadcrumbDims.height / 2);
// Set position for entering and updating nodes.
g.attr('transform', function (d, i) {
return 'translate(' + i * (breadcrumbDims.width + breadcrumbDims.spacing) + ', 0)';
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
breadcrumbs.select('.end-label')
.attr('x', (sequenceArray.length + 0.5) * (breadcrumbDims.width + breadcrumbDims.spacing))
.attr('y', breadcrumbDims.height / 2)
.attr('dy', '0.35em')
.text(percentageString);
// Make the breadcrumb trail visible, if it's hidden.
breadcrumbs.style('visibility', null);
}
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseenter(d) {
const sequenceArray = getAncestors(d);
const parentOfD = sequenceArray[sequenceArray.length - 2] || null;
const absolutePercentage = (d.m1 / totalSize).toPrecision(3);
const conditionalPercentage = parentOfD ? (d.m1 / parentOfD.m1).toPrecision(3) : null;
const absolutePercString = formatPerc(absolutePercentage);
const conditionalPercString = parentOfD ? formatPerc(conditionalPercentage) : '';
// 3 levels of text if inner-most level, 4 otherwise
const yOffsets = ['-25', '7', '35', '60'];
let offsetIndex = 0;
// If metrics match, assume we are coloring by category
const metricsMatch = Math.abs(d.m1 - d.m2) < 0.00001;
gMiddleText.selectAll('*').remove();
gMiddleText.append('text')
.attr('class', 'path-abs-percent')
.attr('y', yOffsets[offsetIndex++])
.text(absolutePercString + ' of total');
if (conditionalPercString) {
gMiddleText.append('text')
.attr('class', 'path-cond-percent')
.attr('y', yOffsets[offsetIndex++])
.text(conditionalPercString + ' of parent');
}
gMiddleText.append('text')
.attr('class', 'path-metrics')
.attr('y', yOffsets[offsetIndex++])
.text('m1: ' + formatNum(d.m1) + (metricsMatch ? '' : ', m2: ' + formatNum(d.m2)));
gMiddleText.append('text')
.attr('class', 'path-ratio')
.attr('y', yOffsets[offsetIndex++])
.text((metricsMatch ? '' : ('m2/m1: ' + formatPerc(d.m2 / d.m1))));
// Reset and fade all the segments.
arcs.selectAll('path')
.style('stroke-width', null)
.style('stroke', null)
.style('opacity', 0.7);
// Then highlight only those that are an ancestor of the current segment.
arcs.selectAll('path')
.filter(function (node) {
return (sequenceArray.indexOf(node) >= 0);
})
.style('opacity', 1)
.style('stroke-width', '2px')
.style('stroke', '#000');
updateBreadcrumbs(sequenceArray, absolutePercString);
}
// Restore everything to full opacity when moving off the visualization.
function mouseleave() {
// Hide the breadcrumb trail
breadcrumbs.style('visibility', 'hidden');
gMiddleText.selectAll('*').remove();
// Deactivate all segments during transition.
arcs.selectAll('path').on('mouseenter', null);
// Transition each segment to full opacity and then reactivate it.
arcs.selectAll('path')
.transition()
.duration(200)
.style('opacity', 1)
.style('stroke', null)
.style('stroke-width', null)
.each('end', function () {
d3.select(this).on('mouseenter', mouseenter);
});
}
function buildHierarchy(rows) {
const root = {
name: 'root',
children: [],
};
// each record [groupby1val, groupby2val, (<string> or 0)n, m1, m2]
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const m1 = Number(row[row.length - 2]);
const m2 = Number(row[row.length - 1]);
const levels = row.slice(0, row.length - 2);
if (isNaN(m1)) { // e.g. if this is a header row
continue;
}
let currentNode = root;
for (let level = 0; level < levels.length; level++) {
const children = currentNode.children || [];
const nodeName = levels[level];
// If the next node has the name '0', it will
const isLeafNode = (level >= levels.length - 1) || levels[level + 1] === 0;
let childNode;
let currChild;
if (!isLeafNode) {
// Not yet at the end of the sequence; move down the tree.
let foundChild = false;
for (let k = 0; k < children.length; k++) {
currChild = children[k];
if (currChild.name === nodeName &&
currChild.level === level) {
// must match name AND level
childNode = currChild;
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {
name: nodeName,
children: [],
level,
};
children.push(childNode);
}
currentNode = childNode;
} else if (nodeName !== 0) {
// Reached the end of the sequence; create a leaf node.
childNode = {
name: nodeName,
m1,
m2,
};
children.push(childNode);
}
}
}
function recurse(node) {
if (node.children) {
let sums;
let m1 = 0;
let m2 = 0;
for (let i = 0; i < node.children.length; i++) {
sums = recurse(node.children[i]);
m1 += sums[0];
m2 += sums[1];
}
node.m1 = m1;
node.m2 = m2;
}
return [node.m1, node.m2];
}
recurse(root);
return root;
}
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(rawData) {
const tree = buildHierarchy(rawData.data);
vis = svg.append('svg:g')
.attr('class', 'sunburst-vis')
.attr('transform', (
'translate(' +
`${(margin.left + (visWidth / 2))},` +
`${(margin.top + breadcrumbHeight + (visHeight / 2))}` +
')'
))
.on('mouseleave', mouseleave);
arcs = vis.append('svg:g')
.attr('id', 'arcs');
gMiddleText = vis.append('svg:g')
.attr('class', 'center-label');
// Bounding circle underneath the sunburst, to make it easier to detect
// when the mouse leaves the parent g.
arcs.append('svg:circle')
.attr('r', radius)
.style('opacity', 0);
// For efficiency, filter nodes to keep only those large enough to see.
const nodes = partition.nodes(tree)
.filter(function (d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
let ext;
if (rawData.form_data.metric !== rawData.form_data.secondary_metric) {
colorByCategory = false;
ext = d3.extent(nodes, (d) => d.m2 / d.m1);
colorScale = d3.scale.linear()
.domain([ext[0], ext[0] + ((ext[1] - ext[0]) / 2), ext[1]])
.range(['#00D1C1', 'white', '#FFB400']);
}
const path = arcs.data([tree]).selectAll('path')
.data(nodes)
.enter()
.append('svg:path')
.attr('display', function (d) {
return d.depth ? null : 'none';
})
.attr('d', arc)
.attr('fill-rule', 'evenodd')
.style('fill', (d) => colorByCategory ? category21(d.name) : colorScale(d.m2 / d.m1))
.style('opacity', 1)
.on('mouseenter', mouseenter);
// Get total size of the tree = value of root node from partition.
totalSize = path.node().__data__.value;
}
d3.json(slice.jsonEndpoint(), function (error, rawData) {
if (error !== null) {
slice.error(error.responseText, error);
return;
}
createBreadcrumbs(rawData);
createVisualization(rawData);
slice.done(rawData);
});
};
return {
render,
resize: render,
};
}
module.exports = sunburstVis;

View File

@@ -1,154 +0,0 @@
const $ = require('jquery');
import d3 from 'd3';
import { fixDataTableBodyHeight } from '../javascripts/modules/utils';
import { timeFormatFactory, formatDate } from '../javascripts/modules/dates';
require('./table.css');
require('datatables.net-bs');
require('datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
function tableVis(slice) {
const fC = d3.format('0,000');
let timestampFormatter;
function refresh() {
function onError(xhr) {
slice.error(xhr.responseText, xhr);
return;
}
function onSuccess(json) {
const data = json.data;
const fd = json.form_data;
// Removing metrics (aggregates) that are strings
const realMetrics = [];
for (const k in data.records[0]) {
if (fd.metrics.indexOf(k) > -1 && !isNaN(data.records[0][k])) {
realMetrics.push(k);
}
}
const metrics = realMetrics;
function col(c) {
const arr = [];
for (let i = 0; i < data.records.length; i++) {
arr.push(data.records[i][c]);
}
return arr;
}
const maxes = {};
for (let i = 0; i < metrics.length; i++) {
maxes[metrics[i]] = d3.max(col(metrics[i]));
}
if (fd.table_timestamp_format === 'smart_date') {
timestampFormatter = formatDate;
} else if (fd.table_timestamp_format !== undefined) {
timestampFormatter = timeFormatFactory(fd.table_timestamp_format);
}
const div = d3.select(slice.selector);
div.html('');
const table = div.append('table')
.classed(
'dataframe dataframe table table-striped table-bordered ' +
'table-condensed table-hover dataTable no-footer', true)
.attr('width', '100%');
table.append('thead').append('tr')
.selectAll('th')
.data(data.columns)
.enter()
.append('th')
.text(function (d) {
return d;
});
table.append('tbody')
.selectAll('tr')
.data(data.records)
.enter()
.append('tr')
.selectAll('td')
.data((row) => data.columns.map((c) => {
let val = row[c];
if (c === 'timestamp') {
val = timestampFormatter(val);
}
return {
col: c,
val,
isMetric: metrics.indexOf(c) >= 0,
};
}))
.enter()
.append('td')
.style('background-image', function (d) {
if (d.isMetric) {
const perc = Math.round((d.val / maxes[d.col]) * 100);
return (
`linear-gradient(to right, lightgrey, lightgrey ${perc}%, ` +
`rgba(0,0,0,0) ${perc}%`
);
}
return null;
})
.attr('title', (d) => {
if (!isNaN(d.val)) {
return fC(d.val);
}
return null;
})
.attr('data-sort', function (d) {
return (d.isMetric) ? d.val : null;
})
.on('click', function (d) {
if (!d.isMetric) {
const td = d3.select(this);
if (td.classed('filtered')) {
slice.removeFilter(d.col, [d.val]);
d3.select(this).classed('filtered', false);
} else {
d3.select(this).classed('filtered', true);
slice.addFilter(d.col, [d.val]);
}
}
})
.style('cursor', function (d) {
return (!d.isMetric) ? 'pointer' : '';
})
.html((d) => {
if (d.isMetric) {
return slice.d3format(d.col, d.val);
}
return d.val;
});
const height = slice.container.height();
const datatable = slice.container.find('.dataTable').DataTable({
paging: false,
aaSorting: [],
searching: fd.include_search,
bInfo: false,
scrollY: height + 'px',
scrollCollapse: true,
scrollX: true,
});
fixDataTableBodyHeight(
slice.container.find('.dataTables_wrapper'), height);
// Sorting table by main column
if (fd.metrics.length > 0) {
const mainMetric = fd.metrics[0];
datatable.column(data.columns.indexOf(mainMetric)).order('desc').draw();
}
slice.done(json);
slice.container.parents('.widget').find('.tooltip').remove();
}
$.getJSON(slice.jsonEndpoint(), onSuccess).fail(onError);
}
return {
render: refresh,
resize() {},
};
}
module.exports = tableVis;

View File

@@ -1,77 +0,0 @@
/* eslint-disable no-use-before-define */
import d3 from 'd3';
import cloudLayout from 'd3-cloud';
import { category21 } from '../javascripts/modules/colors';
function wordCloudChart(slice) {
const chart = d3.select(slice.selector);
function refresh() {
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText, error);
return;
}
const data = json.data;
const range = [
json.form_data.size_from,
json.form_data.size_to,
];
const rotation = json.form_data.rotation;
let fRotation;
if (rotation === 'square') {
fRotation = () => ~~(Math.random() * 2) * 90;
} else if (rotation === 'flat') {
fRotation = () => 0;
} else {
fRotation = () => (~~(Math.random() * 6) - 3) * 30;
}
const size = [slice.width(), slice.height()];
const scale = d3.scale.linear()
.range(range)
.domain(d3.extent(data, function (d) {
return d.size;
}));
function draw(words) {
chart.selectAll('*').remove();
chart.append('svg')
.attr('width', layout.size()[0])
.attr('height', layout.size()[1])
.append('g')
.attr('transform', `translate(${layout.size()[0] / 2},${layout.size()[1] / 2})`)
.selectAll('text')
.data(words)
.enter()
.append('text')
.style('font-size', (d) => d.size + 'px')
.style('font-family', 'Impact')
.style('fill', (d) => category21(d.text))
.attr('text-anchor', 'middle')
.attr('transform', (d) => `translate(${d.x}, ${d.y}) rotate(${d.rotate})`)
.text((d) => d.text);
}
const layout = cloudLayout()
.size(size)
.words(data)
.padding(5)
.rotate(fRotation)
.font('serif')
.fontSize((d) => scale(d.size))
.on('end', draw);
layout.start();
slice.done(json);
});
}
return {
render: refresh,
resize: refresh,
};
}
module.exports = wordCloudChart;

View File

@@ -1,106 +0,0 @@
// JS
const d3 = require('d3');
const Datamap = require('datamaps');
// CSS
require('./world_map.css');
function worldMapChart(slice) {
const render = function () {
const container = slice.container;
const div = d3.select(slice.selector);
container.css('height', slice.height());
d3.json(slice.jsonEndpoint(), function (error, json) {
div.selectAll('*').remove();
if (error !== null) {
slice.error(error.responseText, error);
return;
}
const fd = json.form_data;
// Ignore XXX's to get better normalization
let data = json.data.filter((d) => (d.country && d.country !== 'XXX'));
const ext = d3.extent(data, function (d) {
return d.m1;
});
const extRadius = d3.extent(data, function (d) {
return d.m2;
});
const radiusScale = d3.scale.linear()
.domain([extRadius[0], extRadius[1]])
.range([1, fd.max_bubble_size]);
const colorScale = d3.scale.linear()
.domain([ext[0], ext[1]])
.range(['#FFF', 'black']);
data = data.map((d) => Object.assign({}, d, {
radius: radiusScale(d.m2),
fillColor: colorScale(d.m1),
}));
const mapData = {};
data.forEach((d) => {
mapData[d.country] = d;
});
const f = d3.format('.3s');
container.show();
const map = new Datamap({
element: slice.container.get(0),
data,
fills: {
defaultFill: '#ddd',
},
geographyConfig: {
popupOnHover: true,
highlightOnHover: true,
borderWidth: 1,
borderColor: '#fff',
highlightBorderColor: '#fff',
highlightFillColor: '#005a63',
highlightBorderWidth: 1,
popupTemplate: (geo, d) => (
`<div class="hoverinfo"><strong>${d.name}</strong><br>${f(d.m1)}</div>`
),
},
bubblesConfig: {
borderWidth: 1,
borderOpacity: 1,
borderColor: '#005a63',
popupOnHover: true,
radius: null,
popupTemplate: (geo, d) => (
`<div class="hoverinfo"><strong>${d.name}</strong><br>${f(d.m2)}</div>`
),
fillOpacity: 0.5,
animate: true,
highlightOnHover: true,
highlightFillColor: '#005a63',
highlightBorderColor: 'black',
highlightBorderWidth: 2,
highlightBorderOpacity: 1,
highlightFillOpacity: 0.85,
exitDelay: 100,
key: JSON.stringify,
},
});
map.updateChoropleth(mapData);
if (fd.show_bubbles) {
map.bubbles(data);
div.selectAll('circle.datamaps-bubble').style('fill', '#005a63');
}
slice.done(json);
});
};
return { render, resize: render };
}
module.exports = worldMapChart;

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