Compare commits

...

612 Commits
0.8.8 ... push

Author SHA1 Message Date
Bogdan Kyryliuk
c308339573 Admin / Alpha permission cleanup and fixes. 2016-11-21 13:31:43 -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
Bogdan
c198535292 Change slice ids in the position json during dashboard import. (#1380)
* Change slice ids in the position json during dashboard import.

* Update slice ids in the dashboard json metadata.
2016-10-20 09:17:51 -07:00
ShengyaoQian
ece69fbb75 Fix migration for make creator owners (#1262) 2016-10-20 08:51:13 -07:00
Ryan Ye
458651fa3e Add parens for custom where and having (#1337) 2016-10-20 08:22:21 -07:00
Maxime Beauchemin
b2f7081c6f bumping versions of JS packages to latest (#1352)
* bumping versions of JS packages to latest

* Pining mapbox-gl back to previous version

* bump mapbox-gl to 0.26 ad react-map-gl to 1.7.0
2016-10-19 10:06:11 -07:00
Maxime Beauchemin
c255e89219 [sqllab] show partition metadata for Presto (#1342) 2016-10-19 09:17:08 -07:00
Nick Barnwell
2edce5bf8a Enable "Run Query in New Tab" in SQL Lab (#1343)
* Enable "Clone to New Tab" btn in QueryHistoryTable

Method #1; doesn't feel very clean.

Going to attempt to reimplement using an action and changing state directly
through the reducer rather than creating a new QueryEditor object directly from
the QueryTable

* Move Clone Logic to Action

* Implement PR feedback

* Clean up reducer action; fix bug

Bug => Attempting to clone anything other than the most recent Query for a given
TabbedQueryEditor would throw an exception, because we depended on lastQueryId
to find the title of the QueryEditor to clone. Since you can only activate a
clone from the currently active tab, we can instead fetch the ID of the Editor
to copy the Title of from the tip of tabHistory.

* Tests for Reducer Action

* Fix CodeClimate feedback
2016-10-18 16:02:29 -07:00
Maxime Beauchemin
8f299448ea [bugfix] text as subquery fails with 'Series Limit' (#1347) 2016-10-17 18:36:49 -07:00
Maxime Beauchemin
ecb951bb74 Specify the metric to order by for Series Limit (#1351) 2016-10-17 18:34:26 -07:00
Riccardo Magliocchetti
0dff6a9030 Add quarter time grain to postgresql (#1362)
The timestamp returned is the start date of the period.

Reference:
https://www.postgresql.org/docs/9.1/static/functions-datetime.html
2016-10-17 18:32:47 -07:00
vera-liu
2095095895 Fixed big number issue (#1355) 2016-10-15 10:40:16 -07:00
Maxime Beauchemin
4fc8a17f2a [hotfix] use instead of prod for Travis build, take 3 2016-10-15 08:40:10 -07:00
Maxime Beauchemin
3cb737f8c8 [hotfix] use instead of prod for Travis build, take2 2016-10-15 08:11:37 -07:00
Maxime Beauchemin
7449aa813b [hotfix] use instead of prod for Travis build 2016-10-15 07:58:41 -07:00
Maxime Beauchemin
7a3bcc227c [bugfix] NaN issue in Big Number viz (#1346) 2016-10-14 15:08:52 -07:00
Alanna Scott
b669a14081 [explore-v2] make chart container work with existing visualization files (#1333)
* make chart container work with nvd3_vis.js

* map vis to module, remove unneeded components

* fix linting

* use existing query and save btns, don't fork more things

* comment out chart and exploreviecontainer specs

* make a change because i think the js test is failing spuriously
2016-10-14 12:54:18 -07:00
Alanna Scott
9db4cc8c6d add node/npm versions to contributing.md (#1344)
* add node and npm versions

* use lower case npm

* update wording
2016-10-14 00:00:12 -07:00
ComputeThis
65c744f242 Fix utc time calculation if provided datetime has tz info (#1287) 2016-10-13 21:03:20 -07:00
Maxime Beauchemin
82bcadf7f8 Moving 'CSS TEmplates' to the Manage menu category (#1336) 2016-10-13 20:55:44 -07:00
Bogdan
f0f8478922 Revert "Override the role with perms for give datasources." (#1345) 2016-10-13 19:21:21 -07:00
Bogdan
40e7057bce Override the role with perms for give datasources. (#1335)
* Override the role with perms for give datasources.

* Address comments.
2016-10-13 18:18:03 -07:00
Bogdan
11a8e3591d Some dashboard import/export fixes. (#1340) 2016-10-13 14:36:39 -07:00
Maxime Beauchemin
5bea3986b2 [hotfix] handling json errors in explore view 2016-10-12 19:42:03 -07:00
Maxime Beauchemin
89cb726284 [hotfix] explore errors are not raise properly 2 2016-10-12 17:46:31 -07:00
Maxime Beauchemin
4e9392d21b [hotfix] explore errors are not raise properly 2016-10-12 17:20:42 -07:00
vera-liu
b785d27241 Taking out object spread operator (#1311)
* Changed eslint channel in codeclimate.yml for object spread

* Revert "Changed eslint channel in codeclimate.yml for object spread"

This reverts commit b9deb8ce84478f9c8f48ca7d41662e7222f40376.

* Took out the object spread operator
2016-10-12 15:54:51 -07:00
Alanna Scott
451860afca remove #app styling (#1312) 2016-10-12 14:04:38 -07:00
Ryan Ye
4cf4e3805c Bugfix when there's only date filter in FilterBox (no groupby) (#1326) 2016-10-12 13:51:40 -07:00
Maxime Beauchemin
ef2670ca32 Using inheritance scheme to organize db specific code (#1294)
* Using inheritance scheme to organize db specific code

* Addressing comments
2016-10-12 13:50:47 -07:00
Bogdan
8626c80d3a Stop duplicating datasources (#1321)
* Prevent creating duplicate tables.

* Stop duplicating the tables and druid datasouces.

* Use sqlalchemy func.count
2016-10-12 12:48:24 -07:00
Alanna Scott
5cb3cc2ed8 polyfill es2015 in older browsers and for phantomjs (#1323)
* use babel polyfill

* alphabetize entry files, only add babel-polyfill once for each entry
2016-10-12 10:41:26 -07:00
Bogdan
73cd2ea3b1 Import / export of the dashboards. (#1197)
* Implement import / export dashboard functionality.

* Address comments from discussion.

* Add function descriptions.

* Minor fixes

* Fix tests for python 3.

* Export datasources.

* Implement tables import.

* Json.loads does not support trailing commas.

* Improve alter_dict func

* Resolve comments.

* Refactor tests

* Move params_dict and alter_params to the ImportMixin

* Fix flask menues.
2016-10-11 17:54:40 -07:00
Dennis O'Brien
cd2ab42abc do not overwrite the stored password with the masked password (#1209)
* do not over-write the stored password with a masked password.
his addresses issue #1199

* added a test to validate that sending a password-masked sqlalchemy_uri does not over-write the stored sqlalchemy_uri

* requery the database object after the update.
use self.assertEqual for more informative error messages.
2016-10-11 16:49:40 -07:00
Maxime Beauchemin
bf1f5ea3de [sqllab] use encodeURIComponent for copy query URL (#1317)
had to bump babel-preset-airbnb for object-rest-spread to work in my
env.

Change the menu icon, text and tooltip to clarify the usage
2016-10-11 15:50:36 -07:00
Mihail Diordiev
79460abdd2 [SQLLab] Fix the usage of Redux DevTools Enhancer (#1278)
* Fix the usage of Redux DevTools Enhancer

* Fix ESLint error
2016-10-11 15:27:25 -07:00
Juho Lamminmäki
1e6e144d24 Fixed viewing dashboards as anonymous (#1320) 2016-10-11 08:04:00 -07:00
Alanna Scott
fe66557bbb [explore-v2] hook up ExploreViewContainer to state and add specs (#1300)
* add getParams func to common

* get data from redux state

* specs for chart container and explore view container
2016-10-10 13:46:00 -07:00
vera-liu
f8e2ce6ff3 Change status color in tab to match with success (#1247) 2016-10-10 13:32:38 -07:00
Bogdan
19677438c2 Add cascade delete to the 1 to composite relationships. (#1295) 2016-10-09 19:06:22 -07:00
Alanna Scott
9012b11101 add ImmutableMultiDict back to views.py (#1298) 2016-10-07 18:27:19 -07:00
Maxime Beauchemin
f70d301f0d Refactor the explore view (#1252)
* Refactor the explore view

* Fixing the tests

* Addressing comments
2016-10-07 16:24:39 -07:00
vera-liu
b7d1f78f5e Put formData in store (#1281)
* Put formData in store

* Reform actions and reducers

* Maded modifications based on comments:
2016-10-07 14:53:36 -07:00
Maxime Beauchemin
3384e7598e Fixing explore actions & slice controller interactions (#1292)
* Fixing explore actions & slice controller interactions

* Addressing a comment
2016-10-07 14:06:26 -07:00
Alanna Scott
382b8e85da [explore v2] add scrollbar to control panel container (#1284)
* add scroll bars to control panel container

* make query and save-btns block elements

* don't use react component, use custom styles

* move style to stylesheet
2016-10-07 11:57:05 -07:00
Maxime Beauchemin
0a3121c243 [doc] installation, load examples before init 2016-10-07 10:57:26 -07:00
vera-liu
ecfe1a2417 Updated eslinter for object rest spread (#1289) 2016-10-07 10:18:23 -07:00
Stefano Ordine
609ae22bda less number of default workers. (#1206) 2016-10-07 10:16:41 -07:00
Francesco Riosa
94578cb6a7 reduce chunk size for countries table (#1279)
One user reported the load of the "countries" table exceed
max_allowed_packet which in some configurations can be as low as 1MB.

Changing the chunk size from 500 to 50 has a small cost on the initial
load of the data (one additional second to the 17 taken previously)
while being more universally usable without changing the configuration
of the mysql server.
The new packet size is estimated to be about 500KB.

The committer has not checked other tables
2016-10-07 09:16:26 -07:00
Alanna Scott
8a5f050f6c [explore v2] fix explorev2 chart errors (#1277)
* fix prototypes and arrow function

* only show line chart if viz type is line

* split render lines function

* fix arrow-body linter
2016-10-06 13:07:27 -07:00
vera-liu
5c5b393f2f Change userId, dbId to username and dbname (#1274) 2016-10-06 09:53:33 -07:00
Alanna Scott
f837733d85 [explorev2] chart and controls (#1251)
* create structure for new forked explore view (#1099)

* create structure for new forked explore view

* update component name

* add bootstrap data pattern

* remove console.log

* Created store and reducers (#1108)

* Created store and reducers

* Added spec

* Modifications based on comments

* do use bootstrap data for now

* don't deal with bootstrap data for now

* use victory as a base

* import fake line data, add fake panels, make chart fixed

* add fetch support

* get slice data from json endpoint

* render chart with slicejson

* update chart and label demo

* remove fetch config

* remove dummy control panels

* should be a func

* make TimeSeriesLineChart

* add a comment

* inner height for height

* don't need fetch yet

* trailing comma breaks in package json

* pass in viz data from props

* add style sheet

* set height on explore container

* add legend

* make chart responsive to window resize

* can't use head_css in template bc overrides head_css in basic

* fix linting

* break labelItem into own SFC, make legend SFC

* add propTypes and fix linter
2016-10-05 19:41:16 -07:00
vera-liu
66b498de25 Added controls for Table Viz (#1253)
* Added controls for Table Viz

* Change control panel container to stateless

* Changed specs

* Resolved conflicts
2016-10-05 14:53:51 -07:00
vera-liu
659bf6d7e8 Moved time column and grains to models.py (#1255) 2016-10-05 13:02:35 -07:00
Riccardo Magliocchetti
a8a16900e7 docs: add libsasl as system requirement on linux (#1257)
* docs: add libsasl as system requirement on linux

* docs: add openldap as system dependencies on linux too

Fix #1256
2016-10-05 13:00:38 -07:00
Riccardo Magliocchetti
e50b59e553 docs: document that gunicorn does not work on windows (#1258)
And suggest to use a supported platform instead.

Fix #1236
2016-10-05 12:14:09 -07:00
Riccardo Magliocchetti
231804e2b4 CHANGELOG: Add proper credit to tan31989 for #744 (#1259)
As he's the author of the patch
2016-10-05 12:13:38 -07:00
vera-liu
421a86ade5 Some polish on query search (#1222)
* Some polish
- Changed query search icon
- CopyToClipboard in action bar

* Added dbId as linked-button, made modifications based on comments

* Fix duplicated import (linting)
2016-10-05 11:43:05 -07:00
Maxime Beauchemin
140a055e4e [docs] add line in installation instructions 2016-10-05 08:56:55 -07:00
Maxime Beauchemin
5bf86d91ec [docs] suggest to upgrade pip and setuptools 2016-10-04 21:59:40 -07:00
Maxime Beauchemin
715cdd98fb Changelog for 0.11.0 2016-10-04 21:39:33 -07:00
Maxime Beauchemin
7a01d9dbcb v0.11.0 2016-10-04 21:27:19 -07:00
Bogdan
58dfa436ee Do not shadow _ function. (#1254) 2016-10-04 18:30:15 -07:00
vera-liu
8ab5e5015a Added access check + Druid in endpoint (#1224)
* Explore control panel - Chart control, TimeFilter, GroupBy, Filters (#1205)

* create structure for new forked explore view (#1099)

* create structure for new forked explore view

* update component name

* add bootstrap data pattern

* remove console.log

* Associate version to entry files (#1060)

* Associate version to entry files

* Modified path joins for configs

* Made changes based on comments

* Created store and reducers (#1108)

* Created store and reducers

* Added spec

* Modifications based on comments

* Explore control panel components: Chart control, Time filter, SQL,
GroupBy and Filters

* Modifications based on comments

* Added access check + Druid in endpoint

* pull grains to constants

* Switch explore.html to old version
2016-10-04 13:44:45 -07:00
vera-liu
a92190c3ae Fix npm linting error with version string (#1249) 2016-10-04 10:40:46 -07:00
Maxime Beauchemin
055fb6110f bump version to 0.10.0.dev0 2016-10-04 08:46:31 -07:00
Maxime Beauchemin
19ab3e2fbd Adding a 'Misc Charts' dashboard as part of the examples (#1208)
This showcases some of the visualization types that were there only as
slices, not as part of any dashboards.
2016-10-04 08:18:17 -07:00
Alanna Scott
ae54ac9d58 [sql-lab] fix react warnings in dev (#1232)
* rename file to match class

* fix react warnings

* fix proptype typo

* add missing keys
2016-10-03 23:58:47 -07:00
Alanna Scott
e6e902e8df [explore-v2] setup, basic layout, control panels, v2 url (#1233)
* Explore control panel - Chart control, TimeFilter, GroupBy, Filters (#1205)

* create structure for new forked explore view (#1099)

* create structure for new forked explore view

* update component name

* add bootstrap data pattern

* remove console.log

* Associate version to entry files (#1060)

* Associate version to entry files

* Modified path joins for configs

* Made changes based on comments

* Created store and reducers (#1108)

* Created store and reducers

* Added spec

* Modifications based on comments

* Explore control panel components: Chart control, Time filter, SQL,
GroupBy and Filters

* Modifications based on comments

* accommodate old and new explore urls

* move bootstrap data up in scope

* fix code climate issues

* fix long lines

* fix syntax error
2016-10-03 22:47:39 -07:00
vera-liu
d8638dbcf3 revert devtool alert (#1238) 2016-10-03 16:21:33 -07:00
Bogdan
9795e4a532 [SQLLab] Fix updating the database state. (#1225) 2016-10-03 10:40:27 -07:00
Bogdan
e11ef994bb [SQLLab] user server for the query limit check. (#1230) 2016-10-03 09:59:08 -07:00
Maxime Beauchemin
472679bb38 [security] allow for requesting access when denied on a dashboard view (#1192)
* Request access on dashboard view

* Fixing the unit tests

* Refactored much in the tests
2016-10-02 18:03:19 -07:00
vera-liu
d066f8b726 Added alert to install redux devtool (#1228)
* Added alert to install redux devtool

* Change to warning
2016-09-30 15:02:52 -07:00
vera-liu
aa5bbe6149 Fixed error of inserializable json for druid test (#1213)
* Fixed error of inserializable json for druid test

* Fixed indentation
2016-09-29 09:12:43 -07:00
Bogdan
9c83b900ae Bring DB in sync with the models.py (#1172)
* Bring DB in sync with the models.py

* Make sure that migrations run on multiple dbs
2016-09-28 13:55:06 -07:00
Maxime Beauchemin
f0289cef3a [minor] fixing the icons in the navbar
the top item on the navbar had no icon, and the content of the menu had
two items with the same icon
2016-09-28 10:44:55 -07:00
vera-liu
96844c5c12 Share query (#1154)
* In the tab's dropdown menu under SQL editors, copy query link option is
added. A url with copied query will pop up a new editor tab.

* Made changes based on comments

* Move copy query button to right bottom of sql editor box

* Added in Alanna's code for copy url under menu item

* Fixed linting issues
2016-09-27 22:48:01 -07:00
Maxime Beauchemin
1a29163530 A few bugfixes 2016-09-27 10:20:20 -07:00
Maxime Beauchemin
b67906cfe1 [hotfix] dashboard doesn't have filter_immune_slices pre-save 2016-09-26 17:11:03 -07:00
Maxime Beauchemin
153667505f [hotfix] slice page is broken 2016-09-26 16:51:11 -07:00
Aveplatter
b6f4062874 Update countries.md (#1194) 2016-09-26 10:23:59 -07:00
prokh
44be42c922 Remove duplicate code for property name of SqlaTable (#1190) 2016-09-25 08:41:44 -07:00
vera-liu
5f6ef84c4e Vliu query search (#1187)
* Query search page under SQL Lab tab

* Modifications based on comments

* Hash

* Added spec and endpoint test with modifications
based on second round comments

* Changed permission menu in https://github.com/airbnb/caravel/pull/1095/files
2016-09-23 17:41:24 -07:00
vera-liu
551c97112c Revert "Query Search Page" (#1186) 2016-09-23 16:28:21 -07:00
vera-liu
d5c5c0d6ac Query Search Page (#1122)
* Query search page under SQL Lab tab

* Modifications based on comments

* Hash

* Added spec and endpoint test with modifications
based on second round comments

* Changed permission menu in https://github.com/airbnb/caravel/pull/1095/files
2016-09-23 16:13:18 -07:00
Maxime Beauchemin
98902599ff [hotfix] issues around empty params 2016-09-23 15:07:33 -07:00
Maxime Beauchemin
7f3c205c46 [hotfix] reactable bump to 0.14.0 2016-09-23 14:28:13 -07:00
Maxime Beauchemin
199342a2d3 Improving the docs around managing roles (#1183) 2016-09-23 13:22:56 -07:00
Maxime Beauchemin
d397c0bbf8 [hotfix] FilterBox has issues with react-select version in prod 2016-09-23 13:20:39 -07:00
Maxime Beauchemin
6e5a93a6e1 [hotfix] result set always updates 2016-09-23 12:06:57 -07:00
Bogdan
df89bec712 Infer types. Smart defaults for the visualize window. Basic implementation. (#1134)
* Implement smart suggestions for the visualize flow.

* Address JS comments.

* Implement caravel dataframe wrapper.
2016-09-23 11:14:38 -07:00
Maxime Beauchemin
fc921d63a1 Simplifying source_registry (#1180) 2016-09-22 21:19:03 -07:00
Maxime Beauchemin
aed473d0d2 [filtering] define combo of slice/fields unafected by filtering (#1179)
* [FilterBox] dashboard date range filtering

* [filtering] define combo of slice/fields unafected by filtering

* adding an entry to the docs

* Addressed comments
2016-09-22 20:12:48 -07:00
Maxime Beauchemin
7115c5458d [FilterBox] dashboard date range filtering (#1165)
* [FilterBox] dashboard date range filtering

* Addressing comments
2016-09-22 14:30:39 -07:00
Maxime Beauchemin
8cb0bea57c [sqllab] db migration - setting Database.allow_run_sync=True (#1174) 2016-09-22 14:09:00 -07:00
Dennis O'Brien
1fa18922fa when adding a new database use Database.set_sqlalchemy_uri so that the password is stored encrypted. (#1177)
This fixes a regression I introduced with PR #1137
2016-09-22 14:08:42 -07:00
Maxime Beauchemin
49cefc8b00 Improve the Test Connection error message (#1175) 2016-09-22 13:09:10 -07:00
Bogdan
cbc70d3738 Implement permission request/approve flow. (#1095)
* Implement permission request/approve flow

* Address the comments.

* Refactor the code to support multiple datasources.

* Reformat the queries.
2016-09-22 09:53:14 -07:00
Bogdan
b855e2f1a6 Add dashboard creator as owner of the dashboard (#1166)
* Add dashboard creator as owner of the dashboard

* Address comments.
2016-09-22 08:17:27 -07:00
Dennis O'Brien
bc7d0ffad2 Fix TEST CONNECTION on a newly added database. (#1168)
This addresses issue #1167
If the database name passed in the request is not found in the db, test using the sqlalchemy uri passed.
2016-09-22 08:17:05 -07:00
Alanna Scott
2f2ed229fb [redux] move some redux utils to shared file (#1164)
* move some redux utils to shared file so they can be used in the new export view too

* enhancer is a func now
2016-09-21 21:54:03 -07:00
Maxime Beauchemin
b5875764ed [security] allowing to set static headers as configuration (#1126)
* [security] setting X-Frame-Options=SAMEORIGIN to prevent clickjacking

* Changing to a more flexible approach
2016-09-21 14:41:42 -07:00
Maxime Beauchemin
f1e80a8e1b Adding indexes to table metadata (#1160) 2016-09-21 14:40:33 -07:00
ShengyaoQian
5a0e06e7a2 Generalize switch between different datasources (#1078)
* Generalize switch between different datasources.

* Fix previous migration since slice model changed

* Fix warm up cache and other small stuff

* Adding modules and datasources through config

* Replace tabs w/ spaces

* Fix other style issues

* Change add method for SliceModelView to pick the first non-empty ds

* Remove tests on slice add redirect

* Change way of db migration

* Fix styling

* Fix create slice

* Small fixes

* Fix code climate check

* Adding notes on how to create new datasource in CONTRIBUTING.md

* Fix last merge

* A commit just to trigger travis build again

* Add migration to merge two heads

* Fix codeclimate

* Simplify source_registry

* Fix codeclimate

* Remove all getter methods
2016-09-21 09:52:05 -07:00
Maxime Beauchemin
ed2feaf84b fix build with utf-8 connection string option to mysql (#1159) 2016-09-20 17:30:57 -07:00
Maxime Beauchemin
a1338ed52e changing the travis build matrix for faster builds 2016-09-20 14:18:31 -07:00
Maxime Beauchemin
d41463ba72 changing the tox build matrix ordering 2016-09-20 14:15:07 -07:00
Alanna Scott
0e7af8d8a6 [explore] refactor slice action button group (#1074)
* pull explore actions button group into component

* use button component

* make sure we render all action buttons

* test that embed code is correct

* don't need before each

* generalize modal trigger for use with plain links or icons
2016-09-20 13:45:27 -07:00
Maxime Beauchemin
32980a653c [big_number] fix subheader is missing (#1146) 2016-09-20 12:24:29 -07:00
Bob Ziuchkovski
d15a212e64 Add support for Werkzeug ProxyFix middleware (#1150)
Add an ENABLE_PROXY_FIX config param.  When set to True, insert the Werkzeug ProxyFix
middleware.  This middleware extracts and applies the X-Forwarded-* headers that are
inserted by common proxies and load balancers.  Fixes #1139.
2016-09-20 12:24:15 -07:00
Maxime Beauchemin
1ce8acc154 Adding license to package.json 2016-09-19 23:48:16 -07:00
Maxime Beauchemin
e8088d5c9a More improvements to SQL Lab (#1104)
* Handling timeouts

* Fixing timer on non-utc server

* Allowing async with results

* [bugfix] database is not selected

* Making sure the session is up and running

* Cleaning up query results and query objects

* Picking a groupby and metric field on visualize flow

* Showing local time in query history

* Using pull-left pull-right instead of grid layout for table metdata

Long column name were looking weird and icons were wrapping oddly

* Linting

* Eliminating east buttons under the sql editor

* Sort database dropdown by name

* Linting

* Allowing non-SELECT statements to run

* Adding a db config

* Making sqla checkout check cross-db
2016-09-19 15:28:10 -07:00
Riccardo Magliocchetti
8081080709 log: be more robust against malformed input (#1127)
Refs #1113
2016-09-19 15:26:57 -07:00
Riccardo Magliocchetti
8c619e8383 models: fix slice creation (#1130)
Fix #1128
2016-09-19 15:23:15 -07:00
Maxime Beauchemin
1c544c9845 Enable list with checkboxes only on Table->Columns view (#1138) 2016-09-19 15:22:37 -07:00
Dennis O'Brien
ca66ba4893 Fix initialization of Database sqlalchemy_uri and password (#1137)
* move initialization of Database sqlalchemy_uri and password from DatabaseView.pre_add to utils.get_or_create_main_db.
Unit tests for mysql and postgres include username and password in the SQLALCHEMY_DATABASE_URI.

* modified test_testconn to work with sqlalchemy uri with a username and password.
2016-09-19 15:14:00 -07:00
Riccardo Magliocchetti
afa1f0916b Make sql test connection work with saved Database instance (#694)
Fix #596
2016-09-17 12:32:41 -07:00
Riccardo Magliocchetti
69d37d8b2a Fix double escaping of dttm expressions (#744) (#1103)
If ddtm_expr is an expression with special characters then timestamp_grain escapes
the special characters already escaped.

Solution discussed with sqlalchemy upstream:
https://bitbucket.org/zzzeek/sqlalchemy/issues/3737/literal_column-given-a-specific-sql

Fix #617
2016-09-17 12:30:50 -07:00
Maxime Beauchemin
b62d7e3e8e [security] prevent XSS on FAB list views (#1125)
* [security] prevent XSS on FAB list views

* addressing comments
2016-09-16 16:25:42 -07:00
Maxime Beauchemin
e8f1baba43 [explore] giving more room to Slice title (#1118)
* [explore] giving more room to Slice title

* h2->h3 for slice title
2016-09-16 15:41:34 -07:00
vera-liu
ffe6fb849f Apply stretchMargin only to distribution bar (#1124)
* When the label size is too short, the constant for calculating
margin_size does not apply. Also nvd3 auto-adjust font-size of axis
labels.
Temporary solution here: Setting a fixed font-size on nvd3 axis labels
and a minimum threshold for label size.

* Only stretch margin for dist_bar
2016-09-16 15:39:50 -07:00
Maxime Beauchemin
3602d940eb [hotfix] lint 2016-09-16 09:47:21 -07:00
Maxime Beauchemin
9389f89889 [welcome] removing calendar heatmap from welcome page (#1119) 2016-09-16 08:32:59 -07:00
Maxime Beauchemin
edcc2a11c6 Fixing druid culster perms to mirror sqla databases (#1123) 2016-09-16 08:32:37 -07:00
Maxime Beauchemin
2adc8a0274 [explore] clarifying force-refresh message 2016-09-16 08:20:06 -07:00
vera-liu
2432c3155a Associate version to entry files (#1060)
* Associate version to entry files

* Modified path joins for configs

* Made changes based on comments
2016-09-15 17:20:18 -07:00
vera-liu
2132f6715e When the label size is too short, the constant for calculating (#1120)
margin_size does not apply. Also nvd3 auto-adjust font-size of axis
labels.
Temporary solution here: Setting a fixed font-size on nvd3 axis labels
and a minimum threshold for label size.
2016-09-15 16:48:35 -07:00
Maxime Beauchemin
e895807158 [bugfix] namespacing the mapbox css 2016-09-14 21:24:06 -07:00
Maxime Beauchemin
c87f34285a Animated GIFs on README (#1109) 2016-09-14 16:39:40 -07:00
Maxime Beauchemin
17a317554c [bugfix] filter_immune_slices doesn't work (#1110) 2016-09-14 14:19:37 -07:00
Bogdan
a871ee7858 Clean up the druid sync api. (#1101) 2016-09-13 17:30:36 -07:00
Ryan Ye
2e6b4b121f Time grain support for unix-timestamp columns (#1093)
* Add time grain support for time columnd in unix timestamp

* Fix datetime parsing for unix epoch

Since we've already converted unix epoch to datetime type,
we shouldn't specify 'unit' parameter in pandas.to_datetime

* Fix SQLite timestamp to datetime conversion
2016-09-13 11:58:47 -07:00
Maxime Beauchemin
df533d30fc [sql lab] specify schema name when generating vanila query (#1096)
* [sql lab] specify schema name when generating vanila query

* Fixing some react warnings
2016-09-12 23:09:18 -07:00
vera-liu
1f761c61dd Single quote filter values with comma (#1084)
* Single quote filter values with comma

* refactor for codeclimate limite

* Added unit tests and tooltip
2016-09-12 13:41:05 -07:00
Maxime Beauchemin
9bf5620887 [sqllab] hide SqlEditor textarea to prevent flicker 2016-09-12 09:23:14 -07:00
Maxime Beauchemin
1971bf653c Numerous improvements to SQL Lab (#1088)
* Improving the Visualize flow

* Fixed the timer

* CTAS

* Expiclit engine handling

* make tab full height, stretch for longer content (#1081)

* Better error handling for queries

* Hooked and fixed CSV export

* Linting

* Tying in the dttm in the viz flow

* Indicator showing when going offline

* Addressing comments, fixing the build

* Fixing unit tests
2016-09-11 07:39:07 -07:00
Bogdan
c20ee0c129 Filter Druid Datasources by user permissions. (#1090) 2016-09-09 17:12:09 -07:00
Maxime Beauchemin
6aadc6ec13 Simplifying the flow to add a table (#1068)
When specifying a table reference that can not be found, the system used
to still create the object, which would result in confusion and bad
error messages down the line. Now it will fail and not create the
object.

I also removed fields that are not necessary to worry about when
initially creating the table.
2016-09-09 16:39:11 -07:00
Alanna Scott
8eb4cbf66e only show the reset state button if location param (#1075) 2016-09-08 13:54:00 -07:00
Alanna Scott
0e0eaa0ccd [sql-lab] ui polish (#1079)
* we don't need tooltips on accordion menu, known ui pattern

* use consistent type sizes, bump body type down to 14px

* make editor same height as 3 selects

* table meta data accordion menu

- prevent default on accordion clicks
- always show table actions, they are more discoverable like that
- polish spacing/layout

* remove hover effect on table actions
2016-09-08 12:31:45 -07:00
Bogdan
d454fb402b Add refresh druid datasources endpoint. (#1065) 2016-09-07 17:27:35 -07:00
vera-liu
9ae231aeb8 adjust bottom margin according to label sizes on x-axis (#1029)
* adjust bottom margin according to label sizes on x-axis
Note: same as the method in heatmap.js

* add default bottom_margin to dropdown

* Change default to auto
2016-09-07 14:07:02 -07:00
Bogdan
e783219a76 Add cache warmup endpoint (#1063)
* Refactor permissions in the views.py.

* Add get_viz to the slice.

* Add warm up cache endpoint.

* Resolve comments.
2016-09-06 13:58:09 -07:00
Bogdan
49e4f70f78 Disable complexity check. (#1064) 2016-09-06 12:11:56 -07:00
Maxime Beauchemin
62c71110df Adding a ShrinkSql component (#1058) 2016-09-06 11:20:58 -07:00
Bogdan
544b3f350f Add codeclimate cli instructions. (#1043)
* Add codeclimate cli instructions.

Add the instructions for the users that want to mess around with the codeclimate cli.
It is useful to test locally the config changes.

Reviewers:
* @mistercrunch 
* @ascott 
* @vera-liu

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md
2016-09-02 18:43:28 -07:00
vera-liu
9914901099 Change the font on axis (#1028)
A minor css change to make histogram viz look consistent
2016-09-02 10:01:13 -07:00
Maxime Beauchemin
badac7379e [touchup] using input-sm on text fields in explore view 2016-09-01 14:59:28 -07:00
Maxime Beauchemin
7dd01cff6f [bugfix] btn timer contianer was left behind 2016-09-01 14:44:20 -07:00
Maxime Beauchemin
4b77710016 [SQL Lab] Adding DB options for SQL LAb (#1054)
* [SQL Lab] Adding DB options for SQL LAb

each db can be exposed or not in SQL Lab
CTAS is an option
target_schema placeholder (not hooked yet, but would force the CTAS to
target a specific schema)

* Addressing comments
2016-09-01 14:21:46 -07:00
Maxime Beauchemin
1667d15f31 Reactivating coveralls.io 2016-08-31 23:55:20 -07:00
Chris Williams
bae21194c1 Chris/remove redirect from slice id endpoint (#1044)
* call explore() directly from '/caravel/slice/id/' endpoint instead of redirecting to it.

* pass slice_id instead of slice_params in 'caravel/slice/id/' endpoint to avoid logging errors. add fix for MultiDict bug

* address max's comments

* remove the /slice/id/ endpoint in preference of /datasource_type/datasource_id/slice_id/

* pep8
2016-08-31 16:45:03 -07:00
Bogdan
4f125eedb5 Add codeclimate labels. (#1050)
@mistercrunch, do you know how to change the text on the labels?
2016-08-31 13:30:36 -07:00
Bogdan
f300ee1010 Small codeclimate fixes. (#1033) 2016-08-31 13:28:42 -07:00
Bogdan
85d03f5e18 Set up istanbul with codeclimate and travis. (#1046)
* Set up istanbul with codeclimate and travis.

* Put the codeclimate tocken inside .travis.yml
2016-08-31 09:27:07 -07:00
Maxime Beauchemin
3f889492f9 Fixing the build [that I broke] 2016-08-30 23:27:56 -07:00
maxime.beauchemin@airbnb.com
508feb2bad [hotfix] getting presto on track 2016-08-31 00:11:00 +00:00
Maxime Beauchemin
9f8eef498c [theme] a little bit less blue (#1024)
* [theme] a little bit less blue

* Addressing comments
2016-08-30 15:02:09 -07:00
Maxime Beauchemin
561828c2f8 [SQL Lab] moving the db/schema/table select to the left (#1038) 2016-08-30 11:08:41 -07:00
Maxime Beauchemin
fc1e63761c Adding celery_tests.py 2016-08-29 22:11:57 -07:00
Maxime Beauchemin
38b8db8051 SQL Lab - A multi-tab SQL editor (#514)
* Carapal react mockup

This is really just a mock up written in React to try different
components. It could become scaffolding to build a prototype, or not.

* Merging in Alanna's theme tweaks for SQL lab

* Tweak the display of the alert message in navbar

* Sketching the middleware refresh for Queries

* Adjustments

* Implement timer sync.

* CTAS

* Refactor the queries to be stored as a dict. (#994)

* Download csv endpoint. (#992)

* CSV download engdpoint.

* Use lower case booleans.

* Replcate loop with the object lookup by key.

* First changes for the sync

* Address comments

* Fix query deletions. Update only the queries from the store.

* Sync queries using tmp_id.

* simplify

* Fix the tests in the carapal. (#1023)

* Sync queries using tmp_id.

* Fix the unit tests

* Bux fixes. Pass 2.

* Tweakin' & linting

* Adding alpha label to the SQL LAb navbar entry

* Fixing the python unit tests
2016-08-29 21:55:31 -07:00
Maxime Beauchemin
f17cfcbfa2 [filter box] making filter order matches the dropdown (#1007)
fixes https://github.com/airbnb/caravel/issues/1005
2016-08-26 15:22:43 -07:00
Maxime Beauchemin
7eceb140be [bugfix] slice description makes charts overflow (#993) (#1010) 2016-08-26 15:21:16 -07:00
Maxime Beauchemin
b93f9ec598 [line chart] adding option for circle markers (#1006) 2016-08-26 11:45:46 -07:00
Alanna Scott
36a6714e9e slice/explore header (#996)
* move slice header to partial, show datasource in slice title area if no slice is saved

* change partial name, and use same styling as slice title

* use jinja style guide spacing rule

* use [datasource] - untitled
2016-08-25 11:22:11 -07:00
Shashank Shekhar
30071eef09 Reduced number of ticks provided as hint to d3.ticks (#1012)
The number of the ticks suggested scales with 3 times as much
as the number of bins. This is unwanted since the number
of ticks is a hint to d3 as per (https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md#identity_ticks) The high number of suggesting ticks leads to a congested y-axis.
2016-08-25 08:18:22 -07:00
Alanna Scott
e85978a7ed add if enableAddSlice is truthy (#1015) 2016-08-25 00:03:46 -07:00
Alanna Scott
d1f43e3e28 [ui] a few unmerged rules (#1014)
* make btn default grey text on lighter bg

* get nav bar links working

* use same bg as airbnb body
2016-08-25 00:02:23 -07:00
Alanna Scott
de39923d06 [ui] design review changes - default/primary btn colors (#1008)
* change primary colors and inverse nav

* use primary small bans

* only use primary for query btn
2016-08-24 22:28:22 -07:00
Maxime Beauchemin
f800ff16c1 Making default labels visible (#999) 2016-08-23 14:03:10 -07:00
Alanna Scott
ef118dee6a move staging alert out of nav (#995) 2016-08-23 10:15:00 -07:00
Maxime Beauchemin
2bc1674237 [bugfix] slice description makes charts overflow (#993) 2016-08-22 20:27:03 -07:00
Maxime Beauchemin
9445549aff [tweak] allowing markup widget to overflow 2016-08-22 18:06:57 -07:00
Maxime Beauchemin
95eb928beb [bugfix] markup image is missing in examples 2016-08-22 18:03:08 -07:00
Maxime Beauchemin
a8fd23dfa4 Linting JSX files (#941)
`.jsx` linting is now in-scope for the `npm run lint` command, and
I linted the base files and some of the viz, there's still quite a bit
of work there, but that's a first pass on it.
2016-08-22 13:21:30 -07:00
Maxime Beauchemin
7f2805a3c5 [bugfix] fixing && linting the histogram viz (#987) 2016-08-19 18:03:27 -07:00
Maxime Beauchemin
a8715294b0 [bugfix] dashboard list doesn't populate in explore->saveas (#979) 2016-08-19 16:50:05 -07:00
Maxime Beauchemin
c7467f544c Documenting making your own build (#990) 2016-08-19 15:27:35 -07:00
Alanna Scott
30ef8eba37 [ui] hack bootswatch/cosmo theme to get better tabs for sql-lab (and other things) (#975)
* start hacking cosmo theme and fixing small ui bugs on pages.

* straighten up welcome page

* fix tab styling

* remove paper theme files

* add tables to docs

* make alerts lighter
2016-08-18 23:49:31 -07:00
Maxime Beauchemin
23a5463208 Hack around the "last migration doesn't stamp" Alembic bug (#967)
* Hack around the "last migration doesn't stamp" Alembic bug

This makes MySQL, Sqlite and Postgres work with a special hard coded
rule. I'm hoping Alembic fixes the root cause eventually.

* Running db upgrade twice in tests
2016-08-17 17:10:07 -07:00
Maxime Beauchemin
84213ab8cd [line] growth vs factor option for 'Period Ratio' (#970)
* [line] growth vs factor option for 'Period Ratio'

* i18n
2016-08-17 12:26:10 -07:00
Alanna Scott
379cf6cbd9 [ui] tweaks and improvements (#965)
* update panel headings, titles

* remove panel heading collapser

* style slice meta controls

* fix favstar on dashboard

* add space between heat map and list
2016-08-17 08:05:47 -07:00
Riccardo Magliocchetti
a029eaa451 docs: add a faq about mapbox api key (#968)
Also add it to sample config

Fix #952
2016-08-17 08:04:39 -07:00
Maxime Beauchemin
ac512ef731 [hotfix] SQL endpoint JQUERY error
Fixes #935
2016-08-16 22:29:11 -07:00
vera-liu
80974958bd vera_liu-deleted_dashboard_title_column_in_welcome_page (#951) 2016-08-16 21:52:57 -07:00
Riccardo Magliocchetti
cc058e5c9e viz: cache datetime.now() in query_obj (#955)
It shouldn't be a big deal, and it's pretty cheap on linux, but
still :)
2016-08-16 21:40:27 -07:00
Riccardo Magliocchetti
061d4f1ac7 Refine gamma experience (#883)
* gamma: filter the sqla tables the user has access to

Refs #359

* gamma: filter slices available for dashboards in DashboardModelView

Refs #359

* gamma: limit owners to dashboard to self

As we don't want to leak other users to unpriviliged users

Refs #359
2016-08-16 21:37:55 -07:00
Adam Jones
88f4260777 Change default location for db and logs to ~/.caravel Fix #915 (#947) 2016-08-16 21:35:31 -07:00
Adam Jones
66c2b84cb4 Add time grains for mssql. (#956) 2016-08-16 21:23:03 -07:00
Shashank Shekhar
348c09624f add amino to inthewild (#964) 2016-08-16 21:16:59 -07:00
Luca Albertalli
3e551e40a7 Update INTHEWILD.md (#960) 2016-08-16 11:46:00 -07:00
Maxime Beauchemin
c474581138 Fixing the rendering of the separator widget (#936) 2016-08-16 09:47:47 -07:00
Maxime Beauchemin
5646aa03d2 fixing the build 2016-08-15 23:47:33 -07:00
Maxime Beauchemin
6b5d6b4156 [typo] in setup.py 2016-08-15 23:38:05 -07:00
Maxime Beauchemin
4e1af9a2ca Adding codeclimate-test-reporter to dev-reqs.txt 2016-08-15 23:30:51 -07:00
Maxime Beauchemin
7d1bec11f9 Setting up python to allow 90 2016-08-14 17:24:59 -07:00
Maxime Beauchemin
c60476eadd Attempting to setup coverage 2016-08-14 17:20:35 -07:00
Maxime Beauchemin
d79220fb71 .codeclimate.yml on the right spot 2016-08-14 17:14:34 -07:00
Maxime Beauchemin
a8131dda7a Attempting coverage integration with code climate 2016-08-14 17:08:11 -07:00
Maxime Beauchemin
10011d572a Adding Code Climate conf file 2016-08-14 16:57:46 -07:00
Krish Munot
d7d10d2847 rectify GitHub's name (#942) 2016-08-14 15:48:46 -07:00
Maxime Beauchemin
aa01283774 [hotfix] adding react-select to package.json 2016-08-13 23:28:52 -07:00
Maxime Beauchemin
1b9458dcf0 Adding config element for alert message in navbar (#938) 2016-08-12 23:40:24 -07:00
Maxime Beauchemin
e243a14c64 Refactor around how visualizations/*.js are required (#913)
* Refactor around how visualizations/*.js are required

* Reactifying FilterBox further

* Fixing the auto-refresh on filtering events

* Fixing preselected filters
2016-08-11 21:39:10 -07:00
Gustavo Brian
198226a39f Fix date serialization (#873)
* [panoramix] -> [dashed]

* merge from caravel/master

* Updated from airbnb

* Cleaning

* Rebase with upstream/master

* merge from caravel/master

* Updated from airbnb

* Cleaning

* Manual rebase

* Last pending change to rebase

* Convert date to datetime before serialization.
Approach choosen: transform data before serialize and keep just one way to serialize

* Unit test created

* stupid error :(

* remove uneeded code and rename test

* Avoid double type checking
Test updated
note: isinstance(<datetime>, <date>) == True, check order changed

* Increase coverage

* Fix assertRaises
2016-08-10 23:13:59 -07:00
Chris Williams
2bfb9cc7dd pass the standalone request arg in the /caravel/slices/<slice_id>/ endpoint redirect (#876)
* pass the  request arg in the /caravel/slices/<slice_id>/ endpoint.

remove unused import.

* test that a single slice redirects rather than testing them all. update standalone redirect logic for Javascript 'false' instead of Python False
2016-08-10 23:11:53 -07:00
Riccardo Magliocchetti
71bdabe1a1 dashboard: don't enable buttons that would fail (#881)
With gamma users saving the dashboard model would fail if they
are not owner of the dashboard.
So if that's not the case just disable the "Add a new slice to
the dashboard" and "Save the current positioning and CSS".

Refs #359
2016-08-10 23:10:59 -07:00
Maxime Beauchemin
9b3b1f69df Fixes explore view when no slice is defined (#924) 2016-08-10 22:59:35 -07:00
Maxime Beauchemin
3f21a898c9 Fixing some aspects of the theme (#923) 2016-08-10 22:40:59 -07:00
Maxime Beauchemin
bcbe08bd5c Fixing some aspects of the theme (#907) 2016-08-10 21:14:24 -07:00
Maxime Beauchemin
4247cabb17 [david-dm] Adding badge for tracking js deps versions 2016-08-10 20:32:23 -07:00
Maxime Beauchemin
9a2c7740f0 [bugfix] nvd3's API changed and broke the range filter (#903)
* [bugfix] nvd3's API changed and broke the range filter

* Removing if that is not needed
2016-08-10 17:47:05 -07:00
Maxime Beauchemin
efdfa81f21 [bugfix] controls info bubble don't show up (#904) 2016-08-10 15:04:50 -07:00
Shashank Shekhar
15ee6d82e3 Histogram (#888)
* Add Histogram as a visualization

The css and js file use the histogram code from https://bl.ocks.org/mbostock/3048450.
THe viz.py extends from BaseViz to create chart data only for one histogram

* using d3.layout.histogram

* CSS updated

The new css has been used from the d3 chart http://bl.ocks.org/mbostock/1933560

* bars are visible

* added semicolons

* histogram from http://bl.ocks.org/mbostock/1933560

It takes as input no of bins. The histogram cycles through
a set of colors for different lengths of the bar. It places a
y axis coordinate on top or on the upper end of the bar
whichever is suitable.

* update style changes
2016-08-10 15:04:19 -07:00
Maxime Beauchemin
d15c557cd6 [bugfix] missing spinner in explore view (#914) 2016-08-10 15:02:13 -07:00
Maxime Beauchemin
08d682501e [webpack] set up proper dev/prod environment (#916) 2016-08-10 15:02:01 -07:00
Riccardo Magliocchetti
baf22c3c60 docs: make it clear that some config keys really need to be changed (#912) 2016-08-10 08:15:19 -07:00
Riccardo Magliocchetti
5a937f1d0b docs: simplify cryptography installation on Windows (#909)
As suggested here https://github.com/airbnb/caravel/issues/717#issuecomment-232741865
and confirmed there
https://cryptography.io/en/latest/installation/#on-windows
cryptography now ships a statically linked openssl.

Still am not sure we want to make installation on Windows easier :)
2016-08-10 08:14:42 -07:00
Bogdan
d6bb8c6935 Add per database permissions for the SQL Lab. (#885) 2016-08-09 17:53:23 -07:00
Riccardo Magliocchetti
b48101ca51 docs: recommend python3 and virtualenv (#901)
* docs: make it clear that python3 is the recommended version

* docs: recommend installing inside a virtualenv

And add a virtualenv primer.
2016-08-09 15:56:36 -07:00
Bogdan
572c6ee85e Update linting instructions. (#896)
flake8 changes tests lints only the test dir.
2016-08-09 09:54:59 -07:00
Alanna Scott
b0a1f07818 ui update (#879)
* caravel ui update

* make headings bold on /explore

* bump back pagination color
2016-08-08 10:55:03 -07:00
Kang Tu
cb23362a5b handle UUID type field (some database like postgres support UUID type field) (#889) 2016-08-07 22:04:36 -07:00
Riccardo Magliocchetti
7c810dbd20 Miscellaneous style fixes spotted by landscape (#874) 2016-08-04 15:30:33 -07:00
Maxime Beauchemin
82a8e6316f [bugfix] refresh dashboard widget button doesn't work (#878) 2016-08-03 15:44:10 -07:00
Maxime Beauchemin
aaef338539 Altering theme for more subtle alerts / labels / buttons (#798)
* Altering theme for more subtle alerts / labels / buttons

* Taking comments into account

* changed button-primary to more sober grey instead of brand-primary
* remove carousel from theme demo page and other useless items

* Forcing links to be gray
2016-08-01 23:09:25 -07:00
Riccardo Magliocchetti
e7ce38b486 Smarter redirect on slice creation (#691)
After ea8a7ec1ba creating a slice
started redirecting to druid datasource from sqlalchemy tables.
That's quite painful for sqlalchemy tables users.
Instead of hardcoding a choice just query the db, if we don't
have any druid datasource fallback to sqlalchemy tables.
Bonus points we remove hacky javascript and make the message
translatable.

While at it fix druid client test to not hardcode datasource id.
2016-08-01 23:06:19 -07:00
Karl LIN
862042bb49 add redis cache hint to docs (#861) 2016-08-01 23:04:26 -07:00
George Ke
cbca740f9f use css transforms for dashboard grid cells (#848) 2016-08-01 23:02:54 -07:00
Riccardo Magliocchetti
e36bc2477a Use flask_babel in CONTRIBUTING (#869)
Instead of deprecated flask.ext.babelpkg
2016-08-01 23:02:41 -07:00
Riccardo Magliocchetti
55afda3a7e viz: use sort_values(inplace=True) instead of sort (#870) 2016-08-01 23:02:16 -07:00
Maxime Beauchemin
ee9141a31a New endpoint that receives SQL and returns data as JSON (#842)
* New endpoint that receives SQL and returns data as JSON

* Addressing comments
2016-07-29 22:39:33 -07:00
Hari Prasath R
299e31fdff Added order_by_cols to as_list. Fix to issue #821 (#823) 2016-07-28 11:51:21 -07:00
George Ke
f9427b9bfb Fix mapbox radius calculations being off when changing latitude (#824) 2016-07-28 11:50:39 -07:00
plumbeo
88726773f1 Add an option to allow users to choose to what IP address the web server must bind to. Default to 0.0.0.0 (all IP addresses). (#826) 2016-07-28 11:49:43 -07:00
x4base
29e3dd404d Let the user decide the label type of pie charts (#819) 2016-07-28 11:39:29 -07:00
Alanna Scott
1101de5ae4 [js linting] use airbnb eslint settings (#796)
* add airbnb eslint settings and lint all the code

* fix linting erros
2016-07-27 16:57:05 -07:00
x4base
f43e5f18d5 Support showing the values on top of the bars (#777) 2016-07-25 20:38:26 -07:00
Riccardo Magliocchetti
2aea1943d6 Add Maieutical Labs / cloudschooling.it to Caravel users (#816) 2016-07-25 20:33:19 -07:00
Maxime Beauchemin
7dd5b6716e Fixing serializing the lazy string (#818) 2016-07-25 20:15:57 -07:00
George Ke
2425b8f614 [presto] Smaller granularity units; fallback support for H:m:s (#731)
* [presto] Smaller granularity units; fallback support for H:m:s

* break lines >90

* cast to DATETIME instead
2016-07-22 12:10:05 -07:00
Maxime Beauchemin
d11dd83c94 [docs] faq entry about server timeouts 2016-07-22 19:05:59 +00:00
George Ke
6731a287b5 Filter add/remove fix (#779) 2016-07-22 09:47:04 -07:00
aljones
cf785b4d03 fix datasources foreign key size (#791) 2016-07-22 09:46:41 -07:00
Riccardo Magliocchetti
8b694ddd7a Fix caching in python3 (#806)
* caravel: fix visualization cache for python3

python3 wants bytes and not strings:

2016-07-22 10:36:09,474:INFO:root:Caching for the next 28800 seconds
2016-07-22 10:36:09,475:WARNING:root:Could not cache key 1eeb45f32960f0df0ad99a125bdaf199
2016-07-22 10:36:09,475:ERROR:root:'str' does not support the buffer interface
Traceback (most recent call last):
  File "/home/rm/caraveltest/venv/lib/python3.4/site-packages/caravel/viz.py", line 306, in get_json
    zlib.compress(self.json_dumps(payload)),
TypeError: 'str' does not support the buffer interface

Tested with memcached and pylibmc client library.

* docs: add note about using a proper memcached client library
2016-07-22 09:45:51 -07:00
Riccardo Magliocchetti
187149caeb Update documentation url (#805)
Fix #789
2016-07-22 08:31:43 -07:00
yxjames
19f5371787 In subquery use alias to do 'order by' (#795)
* in subqry add orderby metric to select

* add comment
2016-07-21 17:53:25 -07:00
Maxime Beauchemin
9cdd289081 Set BoxPlotViz to is_timeseries=False (#802)
Fixes #786
2016-07-21 17:52:29 -07:00
Maxime Beauchemin
e813726afb Giving hingts that we support SparkSQL (#803) 2016-07-21 17:51:30 -07:00
Alanna Scott
a704d4ddee remove z-index from slice div and .nvtooltip (#793) 2016-07-21 12:30:40 -07:00
Maxime Beauchemin
fa0497de5e [eslint] changing to always-multiline on comma-dangle (#794) 2016-07-20 21:32:20 -07:00
Maxime Beauchemin
7bba9f73d0 [bugfix] fix dual alert messages where the second one is empty 2016-07-20 13:46:10 -07:00
Alanna Scott
83d5ad216a update contributing.md with js testing details (#781) 2016-07-19 16:14:04 -07:00
smilin-desperado
7306b9caaa Fix small typo in message strings (#778) 2016-07-19 17:30:06 -04:00
Alanna Scott
2b237f483f update-webpack-config to match babelrc (#776) 2016-07-15 10:36:22 -07:00
Alanna Scott
24e85f52b4 get npm test working (#762) 2016-07-14 19:50:47 -07:00
Alanna Scott
1fed498e33 [webpack] revert change to output extension (#760)
* revert change to output extension

* make dashboard use .js
2016-07-14 18:19:52 -07:00
Maxime Beauchemin
f034f2701e Allowing to define a default format string per-metric (#750) 2016-07-13 23:45:05 -04:00
Maxime Beauchemin
8312f1c2aa Adding an option to make separators in dashboard (#699) 2016-07-13 23:40:52 -04:00
Maxime Beauchemin
3522bf9b09 Fixes #721 - [mktime out of range] (#756) 2016-07-13 23:39:26 -04:00
vimxiang
8a69235220 fix development env req (#761)
when i install for development, get an error:
error: Flask-SQLAlchemy 2.1 is installed but Flask-SQLAlchemy==2.0 is required by set(['flask-appbuilder'])
2016-07-13 23:29:41 -04:00
Maxime Beauchemin
b295436bff [bugfix] refresh button on dashboard widget dones't work 2016-07-13 15:07:16 -04:00
x4base
8cfe9e96b8 Preselect filters (#752)
* Preselect filters in filter boxes according to the get parameters

* Use the JSX version in dashboard.html

* Use default parameters in ES6 and fix the indent
2016-07-13 11:23:43 -04:00
alanmcruickshank
65efe53bfc Adding in Second and Minute time grains for MySQL (#696)
* Added documentation of the health check endpoint

* Adding Minute and Second time grains for MySQL

* Fixed Migration script so that it doesn't break when new fields added to the models for Dashboards or Slices (using declarative base)

* Revert "Fixed Migration script so that it doesn't break when new fields added to the models for Dashboards or Slices (using declarative base)"

This reverts commit 0b6dd696d6.

* Code Cleanliness
2016-07-13 11:20:40 -04:00
x4base
09c95fb28a Fix the mapbox for Druid (#725) 2016-07-13 10:58:59 -04:00
Riccardo Magliocchetti
212284cbd4 viz: make sunburst work again (#728)
By using a different method for renaming the metric columns
Thanks to @simobasso for the help!

Fix #673
2016-07-13 10:42:00 -04:00
Maxime Beauchemin
082645d312 [bugfix] html points to dashboard.entry.js instead of jsx (#749) 2016-07-13 00:03:38 -04:00
Alanna Scott
18b8e6fa58 make show columns explicit, don't show password column (#748) 2016-07-11 18:15:33 -07:00
Maxime Beauchemin
9d7c05a015 [hotfix] fix py3 compatibility broken by basestring 2016-07-11 17:36:12 -04:00
x4base
3c92ba9bd5 Fix bugs in the world map and deal with edge cases (#722)
* Fix bugs in the world map and deal with edge cases

* Ignore countries labeled XXX
2016-07-10 19:38:12 -07:00
George Ke
00970d6b99 Fix erroneous options for default slices (#730) 2016-07-10 19:37:02 -07:00
Van Tien
979782d1cf Fix path for windows platfrom (#742)
* At platform specific path for Windows

* At platform specific path for Windows
2016-07-10 19:20:21 -07:00
George Ke
04f3e3bc8f "Add Slices" modal on dashboard page (#678)
* Add slice modal

* use datatables, filter by slice creator

* tests & landscaping

* code review + react-bootstrap-table + modularity
2016-07-07 21:40:33 -07:00
Alanna Scott
afff78868f remove console.log (#729) 2016-07-07 19:04:36 -07:00
Alanna Scott
8020464602 [explore] convert query and save btns to react (#690)
* start to convert query and save btns to react

* more explore.jsx to explore/
2016-07-07 18:39:43 -07:00
Maxime Beauchemin
8135c240dc Revert "Binding key Q to the running the query in explore view" (#713) 2016-07-01 16:36:06 -07:00
Maxime Beauchemin
19983147a3 Binding key Q to the running the query in explore view (#685) 2016-07-01 15:28:40 -07:00
x4base
d5b22dd86e Filter empty strings or nulls, and add more operators (#704)
* Filter empty strings or nulls, and add more operators

* Encapsulate strings for translation
2016-07-01 14:45:04 -07:00
Maxime Beauchemin
917bc984eb Make DruidDatasource.version_higher support funky version strings (#706) 2016-07-01 14:44:25 -07:00
x4base
1a952a4961 Dashboards can only be deleted by their owners (#701) 2016-07-01 14:33:07 -07:00
lucky2you
ee00aa6522 some Chinese simplified translation work (#710) 2016-07-01 14:31:22 -07:00
Maxime Beauchemin
2e0e6e3342 [quickfix] support isNaN aggregates in Table viz 2016-06-30 17:34:58 -07:00
Maxime Beauchemin
d4641e4457 [docs] FAQ how do I create my own viz? 2016-06-30 17:33:51 -07:00
Maxime Beauchemin
8b95d17b7b [quickfix] embeded view missing some dependencies 2016-06-30 10:34:53 -07:00
Maxime Beauchemin
f407bd45fd [quickfix] fix broken CRUD column header links 2016-06-29 22:20:25 -07:00
Maxime Beauchemin
fa65888590 Screenshot makeover in README 2016-06-29 11:00:44 -07:00
Maxime Beauchemin
dbb9356d7e Changelog updates for 0.10.0 2016-06-28 16:57:44 -07:00
yxjames
1ac2fccd2a fix small issue of dttm PR (#688) 2016-06-28 16:51:26 -07:00
Maxime Beauchemin
57bffe099f v0.10.0 2016-06-28 16:42:33 -07:00
Maxime Beauchemin
a016d181d7 [hotfix] mistakes slipped in 2016-06-28 12:58:09 -07:00
Maxime Beauchemin
759c8d5377 [hotfix] fixing minor control issues
* View Query button is disabled
* Missing tooltip on View Query
2016-06-28 12:29:51 -07:00
Maxime Beauchemin
bd68378d9c Saving slices and adding them to dashboards directly from explore view (#680)
* Saving slices from explore view

* Addressing comments
2016-06-28 10:31:36 -07:00
yxjames
7a7f61a296 datetime format and database expression on column level (#652)
* time format minor features added

* add description for datetime format input

* db version bug walkaround

* removed unecessary comments and fixed minor bug

* fixed code style

* minor fix

* fixed missing time format column in DruidDatasource

* Update models.py

Minor style fix

* Revert "Update models.py"

This reverts commit 6897c388e0.

* removed timestamp_format from druid and removed try catch in migration

* Using spaces, not tabs

* get the most updated migration and add the migration on the head of it

* remove vscode setting file

* use colunm based dttm_format

* modify dttm_converter

* modify datetime viz

* added comments and documents

* fixed some description and removed unnecessary import

* fix migration head

* minor style

* minor style

* deleted empty lines

* delete print statement

* add epoch converter

* error fixed

* fixed epoch parsing issue

* delete unnecessary lines

* fixed typo

* fix minor error

* fix styling issues

* fix styling error

* fixed typo

* support epoch_ms and did some refactoring

* fixed styling error

* fixed styling error

* add one more dataset to test dttm_format and db_expr

* add more slices

* styling

* specified String() lenght
2016-06-27 21:33:44 -07:00
Maxime Beauchemin
3e742c74bb [hotfix] many tooltips were not quoted properly 2016-06-27 20:44:32 -07:00
Maxime Beauchemin
6a34b729e9 Adapting Babel to new FAB version, regenerating translations (#684) 2016-06-27 20:10:40 -07:00
Maxime Beauchemin
4191b75966 Adding padding to markup widget 2016-06-26 13:13:16 -07:00
Maxime Beauchemin
d5b8414fde Showing only dashboards on welcome page (#676) 2016-06-24 18:42:55 -07:00
George Ke
57ebb2bacf Map visualization (#650)
* simple mapbox viz

use react-map-gl

superclustering of long/lat points

Added hook for map style, huge performance boost from bounding box fix, added count text on clusters

variable gradient size based on metric count

Ability to aggregate over any point property

This needed a change in the supercluster npm module, a PR was placed here:
https://github.com/mapbox/supercluster/pull/12

Aggregator function option in explore, tweaked visual defaults

better radius size management

clustering radius, point metric/unit options

scale cluster labels that don't fit, non-numeric labels for points

Minor fixes, label field affects points, text changes

serve mapbox apikey for slice

global opacity, viewport saves (hacky), bug in point labels

fixing mapbox-gl dependency

mapbox_api_key in config

expose row_limit, fix minor bugs

Add renderWhileDragging flag, groupby. Only show numerical columns for point radius

Implicitly group by lng/lat columns and error when label doesn't match groupby

'Fix' radius in miles problem, still some jankiness

derived fields cannot be typed as of now -> reverting numerical number change

better grouping error checking, expose count(*) for labelling

Custom colour for clusters/points + smart text colouring

Fixed bad positioning and overflow in explore view + small bugs + added thumbnail

* landscaping & eslint & use izip

* landscapin'

* address js code review
2016-06-24 14:16:51 -07:00
Maxime Beauchemin
914f23432f Make text in sunburst path more readable (#675) 2016-06-24 12:28:13 -07:00
Maxime Beauchemin
967b2ffeb0 Only creating perms for restricted metrics (#655)
* Only creating perms for restricted metrics

* Adding post_update hooks for is_restricted
2016-06-24 08:47:43 -07:00
Maxime Beauchemin
131372740e Adding orderby to Table 'not grouped by' and fixing metrics ordering (#669) 2016-06-23 22:43:52 -07:00
Maxime Beauchemin
51024b5f8a Reintroducing showControls as an option (#672) 2016-06-23 22:43:40 -07:00
Maxime Beauchemin
141dc12e44 Adding quarter time grain for Presto 2016-06-23 16:59:10 -07:00
Maxime Beauchemin
e230d9db4a Don't force formatting when using "Period Ratio" (#668)
At the moment, when using the "Period Ratio" option, a percentage
formatting is forced on the Y Axis. This code pre-dates the `Y Axis
Format` option.

People may want to see a growth rate, in which case the current `.3p`
isn't what they want, or they may want only 2 digits of precision or
whatever else. This PR allows that.
2016-06-23 15:28:42 -07:00
Maxime Beauchemin
4a8e62b439 Fixing the examples's dashboard positioning (#667) 2016-06-23 15:28:23 -07:00
Maxime Beauchemin
f949b88ebd A cleaner right side of the navbar with a Github link (#666) 2016-06-23 15:28:07 -07:00
x4base
ab71ee4f93 Make the headers of tables and pivot tables fixed (#651) 2016-06-22 16:16:27 -07:00
Maxime Beauchemin
8ebe074954 [bugfix] filter widgets to apply on applicable Slices (#658)
Also fixed a white on white issue on hover
2016-06-22 16:14:07 -07:00
Maxime Beauchemin
f25e37579d [quickfix] showControls on area chart 2016-06-22 14:21:09 -07:00
Maxime Beauchemin
3ef79bbaf3 [quickfix] removing controls in Area chart to leave more room for the legend 2016-06-22 14:00:35 -07:00
J Phani Mahesh
73601e4acb docs: correct name of local config module in comments (#653) 2016-06-22 07:55:09 -07:00
Maxime Beauchemin
a9fd2271dd [hotfix] caching and list ordering related bugs 2016-06-21 23:44:22 +00:00
Maxime Beauchemin
7c2d485de0 [quickfix] removing year detail in smart_date 2016-06-21 11:20:11 -07:00
kkalyan
30da408ace Druid Intervals Issues prevents metadata pull (#526)
* Druid Intervals Issues

* fetching druid version

* fixes

* space

* version fix

* landscape issues
2016-06-21 10:03:56 -07:00
x4base
485234bc78 Add having filters (#553)
Support the dimSelector having filters
2016-06-21 09:43:10 -07:00
x4base
13095eb550 Show right messages as soon as possible (#632)
* Flashed messaged should be flushed in every page

* Show error messages in AJAX style

* Introduce the decorator "api"

* Move toggleCheckbox() to the right place and trigger it in jQuery style
2016-06-21 09:42:54 -07:00
x4base
40e1787948 Improve the error message in the slices (#555)
* Improve the error message in the slices

Let slice.error() accept msg and xhr

* Check error first in nvd3_vis.js
2016-06-21 09:42:44 -07:00
Maxime Beauchemin
7e8053abef Adding list of table names to show Dashboard view (#648) 2016-06-21 09:41:48 -07:00
Lech Jankovski
ff44e46d7b Add impyla to recommended packages for Impala (#649) 2016-06-21 09:41:24 -07:00
Maxime Beauchemin
d71a67cdad Bumping FAB and cryptography to current version (#647) 2016-06-20 15:31:15 -07:00
George Ke
54e4be1d13 [hotfix] lint error should fail travis build 2016-06-20 15:17:04 -07:00
George Ke
fb0750710e [hotfix] missing semicolon breaking build 2016-06-20 14:14:20 -07:00
alanmcruickshank
5618df78f8 Added documentation of the health check endpoint (#644) 2016-06-20 09:24:49 -07:00
Chris Williams
668ede1133 expose /slice/<slice_id>/ endpoint to redirect to a slice's url (#633)
* expose /slice/<slice_id>/ endpoint to redirect to a slice's url

* remove residual print statement

* add unit test for caravel/slices/id endpoint
2016-06-20 09:18:03 -07:00
Maxime Beauchemin
deb197a1d8 Adding contribution to total option to Bar chart (#641) 2016-06-20 09:16:51 -07:00
Maxime Beauchemin
55c549d86f Adding option for reduceXTicks (#640) 2016-06-17 12:31:20 -07:00
Maxime Beauchemin
78eb1e6a54 [hotfix] fixing bug around looking up security access 2016-06-17 11:34:54 -07:00
yxjames
4c8523efc0 Prevent potential db upgrading problem (#628)
* fix general db upgrade problem

* add comments
2016-06-17 08:14:26 -07:00
Riccardo Magliocchetti
4400c70514 Make time grains translatable (#622)
* Make time grains translatable

Fix #616

* Refresh translations
2016-06-17 08:12:15 -07:00
Maxime Beauchemin
3105c9f9ae Improvments to NVD3 charts (axis labels & min bar width) (#629) 2016-06-17 08:11:53 -07:00
Maxime Beauchemin
04388a7b9b [docs] telling people to use gunicorn server 2016-06-16 09:16:10 -07:00
x4base
db30f20341 Only initiate permissions of valid metrics (#630) 2016-06-16 08:55:11 -07:00
x4base
65d9feb0a9 Check ownership before a slice is deleted (#624) 2016-06-15 14:27:36 -07:00
Rocky Qi
77c5c9400a Update sql.js to fix a invalid error msg (#621)
when using sql.html page to run sql directly, for any SELECT * sql, when click run! button for the second time,
the page will show error msg like: DataTables warning: table id={id} - Cannot reinitialise DataTable.
this can be fix by:https://datatables.net/manual/tech-notes/3#retrieve
2016-06-15 14:27:12 -07:00
Peiji Chen
5de8740a38 minor correction on the right npm path (#618)
* minor correction to the right npm path

* minor correction to the right npm path
2016-06-15 14:24:25 -07:00
x4base
ea8a7ec1ba Redirect to druid datasource page when the user wants to add slice. Also, provide a link to the table page (#625) 2016-06-15 14:23:25 -07:00
alanmcruickshank
b38590a0bb Added Hour time grain for MySQL (#615) 2016-06-15 14:22:27 -07:00
x4base
ee2d3330aa Prevent the cannot-overwrite error message from being removed before it can be seen (#626) 2016-06-15 14:19:50 -07:00
Maxime Beauchemin
aa2b8b42d0 Updating CHANGELOG 2016-06-15 10:13:48 -07:00
Maxime Beauchemin
91e272546a v0.9.1 2016-06-15 09:46:29 -07:00
Maxime Beauchemin
d90a2c861a Pinning all dependencies to specific versions (#627) 2016-06-15 09:29:37 -07:00
Maxime Beauchemin
a117498991 Adding some CRUD field descriptions to clarify things 2016-06-15 08:50:50 -07:00
Maxime Beauchemin
e29d71d0ff [hotfix] passing payload to Slice.done everywhere 2016-06-13 22:58:04 -07:00
Maxime Beauchemin
bacbd909d1 [hotfix] parallel coordinates grey background on hover 2016-06-13 22:44:57 -07:00
Maxime Beauchemin
77d8ccba87 Compress before caching, store more in each memcache key (#614) 2016-06-13 21:59:03 -07:00
Junxian Wu
347c39b8e9 Better support for Druid cardinality estimation mertics (#613)
* added rocognition of thetasketch and HLL metrics

* make sure the name agreed with SQL convention
2016-06-13 20:49:51 -07:00
Maxime Beauchemin
bc58c5d031 [hotfix] delete cache key when set fails 2016-06-13 18:01:55 -07:00
Maxime Beauchemin
267c0191a8 Make sure cache.set never fails hard (#611) 2016-06-13 13:26:05 -07:00
Maxime Beauchemin
9ed8c32f76 Cranking up FAB to latest (1.7.1) version (#609) 2016-06-13 09:25:22 -07:00
Maxime Beauchemin
1a4c7afbef Fixing a potential FK error when doing bulk updates (#606)
I hit this upgrade issue in production where the FK for user wasn't
allowing null. Perhaps it is specific to our environment but I'd rather
fix this.
2016-06-12 21:39:06 -07:00
Maxime Beauchemin
c58fd63efc [hotfix] caching indicator was missing on dashboard view 2016-06-12 21:38:32 -07:00
Maxime Beauchemin
fa13b77cfa Adding postgres to the build matrix (#604) 2016-06-12 11:01:16 -07:00
Maxime Beauchemin
c490138afe Fixing json issues (#602) 2016-06-11 20:39:25 -07:00
Maxime Beauchemin
327fceefb7 [hotfix] fixes issue around multidict 2016-06-11 08:02:56 -07:00
Marigold
1631137da1 fix missing multiple values for the same parameter name (#565) 2016-06-11 07:55:01 -07:00
Junxian Wu
4661b0210d Popover to generate iframe html tag when standalone button is clicked (#575)
* fixed

* basic implementation of the iframe embed popover

* remove unecessary comments

* remove public embed iframe

* remove debug print line and public access

* remove uncessary extra line and use better text explain

* maintain the style of airbnb/master

* fixed style

* re-run the test locally. Made sure it passed
2016-06-11 07:48:30 -07:00
Riccardo Magliocchetti
a8136bb9f5 Update translations files (#595) 2016-06-11 07:47:42 -07:00
x4base
4c6026fdda Add access control over metrics (#584)
* Add the new field "is_restricted" to SqlMetric and DruidMetric

* Add the access control on metrics

* Add the more descriptions on is_restricted

* Update docs/security.rst

* Update docs/security.rst
2016-06-10 15:49:33 -07:00
Maxime Beauchemin
55baab413a [hotfix] fixing the build (#594) 2016-06-09 22:58:20 -07:00
Maxime Beauchemin
2f60801059 [hotfix] fixing the build 2016-06-09 21:12:44 -07:00
Maxime Beauchemin
2644dd1984 Adding a test for welcome page 2016-06-09 18:11:24 -07:00
Maxime Beauchemin
c35e0e831c [hotfix] csv and json link are off (#592)
* Fixing bugs

* [hotfix] csv and json link are off
2016-06-09 18:06:20 -07:00
Maxime Beauchemin
60ed3e4050 TOX / Travis build matrix (#593)
* Building on many dbs

* Fixing some translation related error msg
2016-06-09 18:05:58 -07:00
Riccardo Magliocchetti
dd662eaca3 caravel: mark more strings for translations in viz (#586) 2016-06-09 16:46:27 -07:00
Riccardo Magliocchetti
e3da785321 caravel: reduce usage of choicify in forms (#591)
So that more string can be translated

Fix #583
2016-06-09 16:45:45 -07:00
Jeremi Joslin
f4c92da4e6 Make sure the APP_ICON config is used in the template (#590) 2016-06-09 16:45:03 -07:00
Riccardo Magliocchetti
eb208b921c config: there's no such thing as WEBSERVER_THREADS (#587)
The config option is CARAVEL_WORKERS. Bump example to 16 processes
to keep backward compatibility with the current default.
2016-06-09 16:44:13 -07:00
Riccardo Magliocchetti
8a579e2a2a Mark more strings for translations (#581)
* caravel: mark viz strings for translations

* caravel: mark templates string for translation

* caravel: make forms strings translatable

* Update translations
2016-06-08 17:38:43 -07:00
Riccardo Magliocchetti
cdb573e793 caravel: add missing test requirements in setup.py (#582) 2016-06-08 13:50:43 -07:00
Riccardo Magliocchetti
ad5507c5f4 views: translate labels and not names (#567)
So that we can install caravel on postgresql:
```
2016-06-03 17:58:05,386:ERROR:flask_appbuilder.base:
Add Permission on Menu Error: (psycopg2.ProgrammingError) can't adapt type '_LazyString'
[SQL: 'SELECT ab_view_menu.id AS ab_view_menu_id, ab_view_menu.name AS
ab_view_menu_name \nFROM ab_view_menu \nWHERE ab_view_menu.name = %(name_1)s \n
LIMIT %(param_1)s'] [parameters: {'param_1': 1, 'name_1': l'Sources'}]
```

Other that translating names should be a recipe for disaster if
you switch language.

Fix #558
2016-06-07 17:43:51 -07:00
George Ke
24a68f5c48 Two hotfixes (#574)
* fix for dashboards created without slices

* [hotfix] check_ownership param needed for adding slice to dashboard
2016-06-07 11:07:25 -07:00
Maxime Beauchemin
0d800fa302 Fixing bugs where params aren't reflected on nvd3* (#564) 2016-06-07 08:10:49 -07:00
Maxime Beauchemin
dc33506bfa Removing deprecated refs to flask.ext.* (#566) 2016-06-05 21:37:03 -07:00
Maxime Beauchemin
89f9efd3a3 Fixing the dashboard's look after resizing the window (#556) 2016-06-03 11:53:37 -07:00
George Ke
52c2b2348a allow for multiple columns in NOT GROUP BY (#560) 2016-06-03 11:34:29 -07:00
Maxime Beauchemin
b5fe9dbe33 Adding a security section to the docs (#561) 2016-06-03 11:33:11 -07:00
Riccardo Magliocchetti
5bc50210ad utils: generalize utility to find find_constraint_name (#557)
See https://github.com/airbnb/caravel/pull/531
2016-06-03 09:47:51 -07:00
Riccardo Magliocchetti
fe402465b1 caravel: catch only ImportError when loading config (#559)
As you may want to see the exception raised on at leasts SyntaxError
2016-06-03 09:46:03 -07:00
Maxime Beauchemin
3ee9a68c09 Only owners can update their objects (#507) 2016-06-02 19:17:34 -07:00
Maxime Beauchemin
29170512ab Adapting default CSS templates to the new grid layout 2016-06-02 16:40:55 -07:00
Maxime Beauchemin
b5614a433e Fixing 2 bugs that happen when fields are removed from table (#551) 2016-06-02 12:39:21 -07:00
Giacomo Tagliabue
5f005d67e3 Add CORS support (#478)
* Add optional CORS

* make CORS an extra dependency

* add documentation
2016-06-02 12:34:36 -07:00
George Ke
c78d3682ac Reactify dashboard grid (#523)
* Use react-grid-layout instead of gridster

* visualizations show and resize

* display slice name and description; links work

* positioning of widgets to match gridster, rowHeight matches

* Change margins, rowHeight, unpositioned viz, and expandedSlices to match gridster

* Saving dashboard, deleting slices, formatting on slices (chart control and resize handle), expanded slices fixed.

* responsiveness + use es6 classes

* Minor ui fixes + linting

* CSS transforms on slices messes up nvd3 tooltip positioning.
Turn off CSS transforms for the time being, with a cost of painting speed.

Issue is currently being looked at on the nvd3 repo
PR: https://github.com/novus/nvd3/pull/1674

* Remove breakpoint listener, fires when it shouldn't (i.e. too often)

* resize is no longer buggy, minor cleanup

* gridster class, const, landscape error

* one source of data for data to front end from python
2016-06-02 12:31:05 -07:00
Luca Albertalli
fe6628b0a4 Fix #529 2 - "This Session's transaction has been rolled back" (#531)
* Created migration to fix the bug

* Working also on MySQL

* Added support for Vertica Grains (#515)

* Fix #529 1 "This Session's transaction has been rolled back" (#530)

* Fixing the specific issue

* Added an additional fix for a similar error in #529

Background:
- When an object is modified by SQLAlchemy, it is invalidated so need to be fetched again from the DB
- If there's an exception during a transaction, SQLAlchemy performs a rollback and mark the connection as dirty.

Bug:
- When handling exceptions, the exception handler tries to access the name of the cluster in the main object. Since the name has been invalidated due to a write, SQLAlchemy tries to fetch it on a 'dirty' connection and spits out an error. Solution:
- Fetch the information for handling the exception before starting the process.

* Modified the migration function to to automatically detect the the foreign keys based on the signature.
It supports also sqlite using batch migrations

* i18n: Fix typo in Druid cluster broker port label (#512)

* Update models.py (#541)

removing duplicated `user_id` def
2016-06-02 12:27:59 -07:00
George Ke
cb384d051b Fix for Not Grouped By on Table View (#544)
* fix for default metric in table visualization

* better location for form override; covers all parameters

* remove dead code, use items instead of iteritems for python 3
2016-06-01 22:59:06 -07:00
George Ke
849063c797 fix world_map appending to old world map when updating (#549) 2016-06-01 22:47:22 -07:00
Luca Albertalli
087c47a37e Fix #529 1 "This Session's transaction has been rolled back" (#530)
* Fixing the specific issue

* Added an additional fix for a similar error in #529

Background:
- When an object is modified by SQLAlchemy, it is invalidated so need to be fetched again from the DB
- If there's an exception during a transaction, SQLAlchemy performs a rollback and mark the connection as dirty.

Bug:
- When handling exceptions, the exception handler tries to access the name of the cluster in the main object. Since the name has been invalidated due to a write, SQLAlchemy tries to fetch it on a 'dirty' connection and spits out an error. Solution:
- Fetch the information for handling the exception before starting the process.
2016-05-31 21:16:32 -07:00
Riccardo Magliocchetti
b193539fa4 i18n: Fix typo in Druid cluster broker port label (#512) 2016-05-31 21:10:28 -07:00
Luca Albertalli
ae7fb012a9 Added support for Vertica Grains (#515) 2016-05-31 21:09:04 -07:00
Jiayu Liu
409233d4fc Update models.py (#541)
removing duplicated `user_id` def
2016-05-31 21:07:14 -07:00
Maxime Beauchemin
7d27692828 [hotfix] forcing newer pydruid version 2016-05-24 12:35:55 -07:00
Maxime Beauchemin
dee4c34411 [hotfix] adapting to pydruid 0.2.3 2016-05-24 18:35:10 +00:00
Maxime Beauchemin
eb3bfb5c56 Cranking FAB to 1.6.2 (#505) 2016-05-23 13:08:50 -07:00
Maxime Beauchemin
57990bfd83 Implementing druid's regex filters (#501)
* Implementing druid's regex filters

* Debugging

* Debuggin'
2016-05-23 13:06:35 -07:00
Xuefeng Zhu
29f5ace436 complete Chinese translation (#503) 2016-05-23 11:46:46 -07:00
Maxime Beauchemin
0fcab30652 More translation instrumentation and some french to test (#502) 2016-05-23 11:46:33 -07:00
Maxime Beauchemin
c53874c8ab [hotfix] treemap was broken 2016-05-20 14:05:08 -07:00
Maxime Beauchemin
e77d50bc61 Adding a doc page for Druid 2016-05-20 12:11:38 -07:00
Maxime Beauchemin
f0c6a98027 Gunicorn tweaks, unlimited limit-request-line & limit-request-field_size (#500)
This is mostly to enable long text in the Markdown widget
Related:
https://github.com/benoitc/gunicorn/issues/376
2016-05-20 11:35:41 -07:00
Maxime Beauchemin
58d78beeaa [bugfix] 'Y Axis Zero' would force 1 in Y axis (#497) 2016-05-20 11:10:46 -07:00
Riccardo Magliocchetti
e1a3854f2a utils: add special serializer for numpy.int64 (#492)
It looks like COUNT(*) returns a numpy.int64 value that the
default JSONEncoder does not handle.

While at if we get a type we are not handling make it easier to
debug the issue by throwing a TypeError exception with useful
data.

Fix #486
2016-05-20 11:10:29 -07:00
Florent BENOIT
7630d73002 fix typo (#484) 2016-05-20 11:05:28 -07:00
Riccardo Magliocchetti
3cfc58e3a2 Add italian translation (#491) 2016-05-20 11:05:08 -07:00
Josh Walters
3ee102b79f Made '__' prefix into suffix. (#496) 2016-05-20 11:03:49 -07:00
Maxime Beauchemin
f5180d8724 [hotfix] fix name change on test dashboard triggers error 2016-05-19 08:51:13 -07:00
Maxime Beauchemin
4738b01125 [hotfix] setting default value for druid_time_origin 2016-05-16 23:08:46 -07:00
Maxime Beauchemin
d1f0276408 Introducing Horizon charts (#472)
* Introducing Horizon charts

* JS Lintin
2016-05-16 22:49:12 -07:00
Maxime Beauchemin
1766f6edd6 [hotfix] making druid_time_origin stick 2016-05-16 22:48:32 -07:00
Andrii Sydorchuk
8a406b18f5 Add PUBLIC_ROLE_LIKE_GAMMA config flag (#473) 2016-05-16 21:24:43 -07:00
Maxime Beauchemin
2620aeca02 Fixing the heatmap calendar color ranges in the Welcome page (#474) 2016-05-16 21:22:38 -07:00
George Ke
5c0e30ed70 Added Calendar Heatmap (#475)
* Added sqlite Grains

* Calendar heatmap visualization

* Linting

* Explicit metric setting was breaking tests

* Python linting

* Code cleanup + review

* [fixing the build] a new version of eslint is more picky

* Linting

* Added sqlite Grains

* Calendar heatmap visualization

* Linting

* Linting

* Explicit metric setting was breaking tests

* Python linting

* Code cleanup + review
2016-05-16 17:59:38 -07:00
Maxime Beauchemin
607e1f941b Fixing the build 2 out of 2 2016-05-16 17:41:02 -07:00
Maxime Beauchemin
d30567959b [fixing the build] a new version of eslint is more picky 2016-05-16 16:33:08 -07:00
Maxime Beauchemin
83e0e58888 [hotfix] Druid explore/table dropdown doesn't change anything 2016-05-16 16:10:22 -07:00
x4base
5a870fe1c2 Mysql key length (#459)
* Use varchar(255) in MySQL

* Adjust the key lengths in old migration scripts
2016-05-12 10:27:38 -07:00
Maxime Beauchemin
d846cb3d73 Updating changelog 2016-05-12 08:51:59 -07:00
Maxime Beauchemin
a0099ad6d6 v0.9.0 2016-05-12 08:24:01 -07:00
Maxime Beauchemin
f28c2b2557 Cosmetric tweaks in the CRUD list view (#458) 2016-05-11 21:05:32 -07:00
Maxime Beauchemin
52bbb38188 Don't limit parallel coordinates table size (#455)
Before we limited the table size to 10 rows, now there's no limit and it
will overflow properly with a scrollbar.
2016-05-11 17:48:04 -07:00
Maxime Beauchemin
aa6e6bdf7a Allowing for templated urls in iFrame (#460)
* Allowing for templated urls in iFrame

This can allow for passing {{ width }} and {{ height }} as dynamic
attributes in the iFrame's URL.

The new method Slice.render_template method could do more eventually
exposing more variables to be used in dynamic strings.

* Passing function references

* js linting
2016-05-11 17:00:46 -07:00
Maxime Beauchemin
6c333d5010 Fixing the missing searchbox bug (#431)
* Fixing the missing searchbox bug

* Linting JS

* Layout tweaks
2016-05-10 11:49:32 -07:00
Maxime Beauchemin
673cce9e56 Attempting to fix #412 (#430)
* Attempting to fix #412

* More flushes
2016-05-10 11:19:30 -07:00
Maxime Beauchemin
d79089c587 Improving the parallel coordinate viz (#452)
* Improving the parallel coordinate viz

* Clear container on refresh
* Order of columns is kept
* Option to show/hide the series column in viz
* Color metric not shown by default

* JS linting
2016-05-10 09:39:33 -07:00
Maxime Beauchemin
c4e3020369 Conververting datetime based on database dialects (#446) 2016-05-10 09:29:29 -07:00
Maxime Beauchemin
77e9e6a5d7 Fixing issue #444 color function chokes on non-string param (#447) 2016-05-10 09:22:59 -07:00
Siddharth Gupta
a75d6bc52c add timestamp toggle in chart options (Table Viz) (#439)
* add timestamp toggle in chart options (Table Viz)

* refactor timestamp choices

* fix build error
2016-05-10 09:22:32 -07:00
x4base
c5fcbc0709 Support hyperUniqueCardinality type in post aggregation (#451) 2016-05-10 09:21:09 -07:00
Siddharth Gupta
2f64c42062 blacklist druid datasources to be refreshed because it takes forever to load all druid datasources metadata which is not even required (given the use cases user should be able to blacklist data sources which are not required to be refreshed). (#441) 2016-05-06 12:03:42 -07:00
Maxime Beauchemin
d304ee005a Bugfix in line chart where the series name is an empty string (#434) 2016-05-05 12:19:51 -07:00
Junshuo
82fa501dea Update Chinese translation (#438)
Change some Chinese translations to make them sound less robotic
2016-05-05 12:19:21 -07:00
x4base
bc7170769b Recursively get the dependency fields of post aggregators (#437) 2016-05-05 08:52:48 -07:00
andrewhn
6941f1de64 add unicode data to tests (#432)
* add unicode data to tests

* make tests pass on 2.7

* clean up data loading

- remove duplicate keys in slice_data
- reduce line length

* change manager option flag to -t, --load-test-data

* test --> load_test_data
2016-05-05 08:46:16 -07:00
Maxime Beauchemin
a3f549bb9e Some chinese translations as a Proof of Concept (#435) 2016-05-04 21:36:10 -07:00
Andrii Sydorchuk
0bedaed367 Make sure anonymous user with proper permissions can access data (#415)
* Make sure anonymous user with proper permissions can access data

* Review fixes: naming changes

* Review fixes: add more granular tests for public user dashboard access

* Review fixes: test that public user has access only to permitted data sets
2016-05-03 22:31:37 -07:00
x4base
1d0863abfe Rename the dummy variable _ to avoid conflict with lazy_gettext (#427)
Otherwise "local variable '_' referenced before assignment" will be raised
2016-05-03 22:28:30 -07:00
Maxime Beauchemin
a3a9ec926f [hotfix] piechart goes black on refresh (#424) 2016-05-03 12:49:52 -07:00
Giacomo Tagliabue
a8d0ae1361 Fix name of test script in contributing guide (#428) 2016-05-03 12:45:38 -07:00
Giacomo Tagliabue
9a08c45e59 add additional postgres and redshift time grains (#429) 2016-05-03 12:44:42 -07:00
Maxime Beauchemin
88c9516e20 Getting started on translations (#423) 2016-05-02 10:50:23 -07:00
Maxime Beauchemin
ec7dbed800 Blank theme (#419)
* A white theme

* Updating TODO

* Fixing tests
2016-05-02 10:04:29 -07:00
Maxime Beauchemin
26d273643b Allowing for druid post aggregations (#418)
* Druid post aggregations

* Fixing tests
2016-05-02 10:00:39 -07:00
Maxime Beauchemin
0ca3f5ec80 Improving SQLA query generation (#421)
* Improving SQLA query generation

* Fixing debug
2016-05-02 10:00:28 -07:00
Siddharth Gupta
d7ea47387f enable timerotateloghandler (configurable) (#311)
* attempt to enchance logging

* clean up logging

* clean up logging

* reset to logger

* clean up imports

* add comments in config.py

* remove redundant declaration of logging.config. Already exists in caravel/__init__.py

* replace RotatingFileHandler with TimedRotatingFileHandler

* revert back running web server in debug mode

* fix debug in bin/caravel

* resolve build errors - formatting

* need to test

* enable time rotateloghandler

* revert back print statements - add feature for rotatetimelog which is needed and make it configurable

* revert back to default in master

* fix build issues

* remove extra print statement

* change log location to default

* configure console log level and format
2016-05-01 07:59:08 -07:00
x4base
7b5b602e96 Use batch_alter_table to drop column in the migration script (#406) 2016-04-30 08:30:40 -07:00
Chris Williams
b78ec54650 Merge pull request #414 from airbnb/chris/fix-sunburst-level-trunc
[bugfix] allow repeated values across levels when building sunburst hierarchy
2016-04-29 10:59:29 -07:00
Maxime Beauchemin
77e4d4b2d4 Fixgin README link 2016-04-28 17:58:09 -07:00
Maxime Beauchemin
2198fd4e3d Adding badge for python versions 2016-04-28 17:55:01 -07:00
Maxime Beauchemin
bd47a29076 CHANGELOG update 2016-04-27 19:43:03 -07:00
Maxime Beauchemin
337c9d59ae v0.8.9 2016-04-27 19:38:36 -07:00
Maxime Beauchemin
54860a874c Adding reference to another docker image in READMEM 2016-04-27 18:12:24 -07:00
Maxime Beauchemin
89d1a77281 [hotfix] filter_immune_slices fix 2016-04-27 13:41:06 -07:00
Maxime Beauchemin
b634d03ac3 Show only Slices and Dashboards users have access to (#404)
* Introducing more security features

* Many to many owners for slices and dashboards
* Slices are filtered to only slices that the user has access to

* Adding unit tests
2016-04-26 16:44:51 -07:00
Maxime Beauchemin
ab64a26b5b fix img loading overlay in explore view (#403) 2016-04-26 16:14:40 -07:00
andrewhn
a2f2ad84da add navigation, zoom to treemap (#396)
* add navigation, zoom to treemap

* don't use stale form data
2016-04-26 11:51:01 -07:00
Maxime Beauchemin
c0fb9eeca4 [bugfix] big_number doesn't fadeout on hover 2016-04-26 09:29:40 -07:00
Maxime Beauchemin
42ac46c1e1 [bugfix] fix context confusion in Slice 2016-04-26 09:29:19 -07:00
Maxime Beauchemin
7b1075990c [hotfix] periodic refresh dashboard feature had broken caching 2016-04-25 15:28:14 -07:00
Maxime Beauchemin
37be01bc12 [hotfix] adding cache_key in payload 2016-04-25 12:41:30 -07:00
Maxime Beauchemin
7d90f26554 Fixing #388 (#391) 2016-04-21 21:10:09 -07:00
Maxime Beauchemin
f1e10d8d25 One more Druid test (#387) 2016-04-21 08:17:15 -07:00
x4base
b01d378475 Fix the label of the periodic-refresh button (#386) 2016-04-21 08:16:52 -07:00
Maxime Beauchemin
a5f33fecd8 Adding login call for Druid test 2016-04-20 20:13:41 -07:00
Maxime Beauchemin
f4177bfa94 More examples / tests (#385)
* More examples / tests

* Fixing the dashboards positioning
2016-04-20 17:36:37 -07:00
x4base
d8a2b621d8 Periodically update the slices in the dashboard (#374)
* Periodically update the slices in the dashboard

* Make the refresh interval changeable

* Add the button and the modal for the user to change the refresh interval

* Don't use callback for refreshing

* Randomize to prevent all widgets refreshing at the same time

* Show the loading icon as an overlay when the slices refresh
2016-04-20 17:35:07 -07:00
Maxime Beauchemin
9a33557112 Removing forgotten print statement 2016-04-20 15:28:02 -07:00
Kim Pham
efc6bf4eb8 Redirect application log to stderr, which is picked up by gunicorn. (#335) 2016-04-20 15:09:15 -07:00
Maxime Beauchemin
17e711fda2 Druid unit tests using Mock (#384)
* Initial Druid mock unit tests

* More unit tests

* Test for Druid query

* Adding a groupby test
2016-04-20 15:08:10 -07:00
Maxime Beauchemin
01a8c96820 Specifying python versions supported in setup.py (#382)
* Specifying python versions supported in setup.py

* Fixing py3
2016-04-20 12:27:17 -07:00
Chris Williams
d96b634ded split sunburst breadcrumb names on '_' to apply ' '-dependent wrapping more freqently (#376) 2016-04-19 14:42:36 -07:00
Siddharth Gupta
afcdcf06a1 Fixing overwrite and save slice permissions for a give role (#298)
* Fixing overwrite and save slice permissions for a give role

* fix function name - build failed

* fix function name and test user permissions

* disable the button in the UI

* fix build error - characters too long in 1 line

* try to disable button on the UI

* disable cursor in caravel css. You wont be able to click anymore if no access

* fix build issues

* fix build errors! god bless me

* disable main features in dashboard and slice

* fix build issues
2016-04-18 13:56:00 -07:00
Maxime Beauchemin
5597eb4cc4 Fix db upgrade script b4456560d4f3 (#370)
* Recreating db upgrade error first

* Wrapping alter table calls in try statements
2016-04-18 12:49:13 -07:00
Siddharth Gupta
3f0171b77b Configure Visualizations (#365)
* make viz types configurable

* make visualizations configurable

* deault every viz is true

* add blacklist viz_type

* fix build
2016-04-18 09:00:03 -07:00
Maxime Beauchemin
badcd8bfa1 Storing version number in only one place (#362) 2016-04-17 08:20:11 -07:00
Maxime Beauchemin
04f1b176c4 Fixes issue #364 2016-04-17 08:17:08 -07:00
Maxime Beauchemin
899fe19afb [WiP] Attempting to support Druid's granularity origin as a hidden url param (#194)
* Supporting Druid'd time origin feature

* Adding origin to form, making it free form
2016-04-15 17:00:44 -07:00
Maxime Beauchemin
f3168518e2 New url default for iframe viz 2016-04-15 16:50:16 -07:00
Maxime Beauchemin
04d769ff24 Some more error handling when rendering the explore view (#361) 2016-04-15 15:00:49 -07:00
Maxime Beauchemin
01c2c7baf8 Fixing unique constraint in SqlaTable model (#360) 2016-04-15 14:53:06 -07:00
482 changed files with 45067 additions and 9898 deletions

36
.codeclimate.yml Normal file
View File

@@ -0,0 +1,36 @@
engines:
csslint:
enabled: false
duplication:
enabled: false
eslint:
enabled: true
config:
config: superset/assets/.eslintrc
pep8:
enabled: true
fixme:
enabled: false
radon:
enabled: true
checks:
Complexity:
enabled: false
ratings:
paths:
- "**.py"
- "superset/assets/**.js"
- "superset/assets/**.jsx"
exclude_paths:
- ".*"
- "**.pyc"
- "**.gz"
- "env/"
- "tests/"
- "superset/ascii_art.py"
- "superset/assets/images/"
- "superset/assets/vendor/"
- "superset/assets/node_modules/"
- "superset/assets/javascripts/dist/"
- "superset/migrations"
- "docs/"

14
.gitignore vendored
View File

@@ -1,27 +1,33 @@
*.pyc
yarn-error.log
_modules
superset/assets/coverage/*
changelog.sh
babel
.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
*.sqllite
# Node.js, webpack artifacts
*.entry.js
*.js.map
node_modules
npm-debug.log
yarn.lock

View File

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

2
.pycodestyle Normal file
View File

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

View File

@@ -1,22 +1,38 @@
language: python
python:
- "2.7"
- "3.4"
addons:
code_climate:
repo_token: 5f3a06c425eef7be4b43627d7d07a3e46c45bdc07155217825ff7c49cb6a470c
apt:
sources:
- deadsnakes
packages:
- python3.5
cache:
directories:
- $HOME/.wheelhouse/
env:
global:
- TRAVIS_CACHE=$HOME/.travis_cache/
- TRAVIS_NODE_VERSION="5.11"
matrix:
- TOX_ENV=javascript
- TOX_ENV=py34-postgres
- TOX_ENV=py34-sqlite
- TOX_ENV=py27-mysql
- TOX_ENV=py27-sqlite
before_install:
- npm install -g npm@'>=2.7.1'
- npm install -g npm@'>=3.9.5'
before_script:
- 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 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 wheel -w $HOME/.wheelhouse -f $HOME/.wheelhouse .
- pip install --find-links=$HOME/.wheelhouse --no-index .
- pip install --find-links=$HOME/.wheelhouse --no-index -r dev-reqs.txt
- cd caravel/assets
- npm --version
- 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
- npm run lint
- npm run prod
- cd $TRAVIS_BUILD_DIR
script: bash run_tests.sh
after_success:
- coveralls
script: tox -e $TOX_ENV

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ You can contribute in many ways:
### Report Bugs
Report bugs through Github
Report bugs through GitHub
If you are reporting a bug, please include:
@@ -30,14 +30,14 @@ 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.
### Submit Feedback
The best way to send feedback is to file an issue on Github.
The best way to send feedback is to file an issue on GitHub.
If you are proposing a feature:
@@ -49,16 +49,16 @@ If you are proposing a feature:
## Latest Documentation
[API Documentation](http://pythonhosted.com/caravel)
Latest documentation and tutorial are available [here](http://airbnb.io/superset)
## 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
# 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,28 +68,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.
### Using npm to generate bundled files
@@ -102,23 +104,22 @@ echo prefix=~/.npm-packages >> ~/.npmrc
curl -L https://www.npmjs.com/install.sh | sh
```
The final step is to add
`~/.node/bin` to your `PATH` so commands you install globally are usable.
Add something like this to your `.bashrc` file.
The final step is to add `~/.npm-packages/bin` to your `PATH` so commands you install globally are usable.
Add something like this to your `.bashrc` file, then `source ~/.bashrc` to reflect the change.
```
export PATH="$HOME/.node/bin:$PATH"
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.
@@ -134,24 +135,57 @@ 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
```
## Testing
Tests can then be run with:
Python tests can be run with:
./run_unit_tests.sh
./run_tests.sh
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 /superset/superset/assets/javascripts
npm i
npm run test
## Linting
Lint the project with:
# for python changes
flake8 changes tests
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 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*
```
curl -L https://github.com/docker/machine/releases/download/v0.7.0/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine && chmod +x /usr/local/bin/docker-machine
brew install docker
docker-machine create --driver virtual box default
docker-machine env default
eval "$(docker-machine env default)"
docker pull codeclimate/codeclimate
brew tap codeclimate/formulae
brew install codeclimate
```
*Run the lint command:*
```
docker-machine start
eval "$(docker-machine env default)”
codeclimate analyze
```
More info can be found here: https://docs.codeclimate.com/docs/open-source-free
## API documentation
Generate the documentation with:
@@ -159,12 +193,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
by modifying the Less variables or files in ```assets/stylesheets/less/```.
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]
@@ -179,7 +213,60 @@ meets these guidelines:
as part of the same PR. Doc string are often sufficient, make
sure to follow the sphinx compatible standards.
3. The pull request should work for Python 2.6, 2.7, and ideally python 3.3.
`from __future__ import ` will be required in every `.py` file soon.
``from __future__ import`` will be required in every `.py` file soon.
4. Code will be reviewed by re running the unittests, flake8 and syntax
should be as rigorous as the core Python project.
5. Please rebase and resolve all conflicts before submitting.
## Translations
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 `superset_config.py`. Having more than one
options here will add a language selection dropdown on the right side of the
navigation bar.
LANGUAGES = {
'en': {'flag': 'us', 'name': 'English'},
'fr': {'flag': 'fr', 'name': 'French'},
'zh': {'flag': 'cn', 'name': 'Chinese'},
}
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 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 superset/translations/
You can then translate the strings gathered in files located under
`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 superset/translations/
## Adding new datasources
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.
2. Create db migration files for the new models
3. Specify this variable to add the datasource model and from which module it is from in config.py:
For example:
`ADDITIONAL_MODULE_DS_MAP = {'superset.my_models': ['MyDatasource', 'MyOtherDatasource']}`
This means it'll register MyDatasource and MyOtherDatasource in superset.my_models module in the source registry.

View File

@@ -1,10 +1,14 @@
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
----------
- [Airbnb](https://github.com/airbnb)
- [GfK Data Lab] (http://datalab.gfk.com)
- [Maieutical Labs] (https://cloudschooling.it)
- [Shopkick] (https://www.shopkick.com)
- [Amino] (https://amino.com)
- [Faasos] (http://faasos.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,8 +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-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 *

View File

@@ -1,36 +1,51 @@
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)
[![Code Health](https://landscape.io/github/airbnb/caravel/master/landscape.svg?style=flat)](https://landscape.io/github/airbnb/caravel/master)
[![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/)
[![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
**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]
Video - Introduction to Caravel
---------------------------------
[![Caravel - ](http://img.youtube.com/vi/3Txm_nj_R7M/0.jpg)](http://www.youtube.com/watch?v=3Txm_nj_R7M)
Screenshots & Gifs
------------------
Screenshots
------------
![img](http://i.imgur.com/JRbTnTx.png)
![img](http://i.imgur.com/4wRtxwb.png)
**View Dashboards**
![superset-dashboard](https://cloud.githubusercontent.com/assets/130878/20371438/a703a2a0-ac19-11e6-80c4-00a47c2eb644.gif)
Caravel
<br/>
**View/Edit a Slice**
![superset-explore-slice](https://cloud.githubusercontent.com/assets/130878/20372732/410392f4-ac22-11e6-9c6d-3ef512e81212.gif)
<br/>
**Query and Visualize with SQL Lab**
![superset-sql-lab-visualization](https://cloud.githubusercontent.com/assets/130878/20372911/7c3b3358-ac23-11e6-8f24-923ef1b35715.gif)
<br/>
![superset-dashboard-misc](https://cloud.githubusercontent.com/assets/130878/20234704/0f40778c-a835-11e6-9556-983a62ea061b.png)
![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
@@ -43,14 +58,15 @@ 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
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).
@@ -71,41 +87,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/MAFZTtU.png)
![img](http://i.imgur.com/xcy1QjN.png)
![img](http://i.imgur.com/RWqA8ly.png)
![img](http://i.imgur.com/D2kZL7q.png)
![img](http://i.imgur.com/0UPTK61.png)
![img](http://i.imgur.com/ahHoCuS.png)
![superset-security-menu](https://cloud.githubusercontent.com/assets/130878/20234707/0f565886-a835-11e6-9277-b4f5f4aa2fcc.png)
![superset-slice-bubble](https://cloud.githubusercontent.com/assets/130878/20234708/0f57f3d0-a835-11e6-8268-fcefe8f868c8.png)
![superset-slice-map](https://cloud.githubusercontent.com/assets/130878/20234709/0f5a5a44-a835-11e6-987a-1b6f8ac9922b.png)
![superset-slice-multiline](https://cloud.githubusercontent.com/assets/130878/20234710/0f632d68-a835-11e6-98d1-542dcb618193.png)
![superset-slice-sankey](https://cloud.githubusercontent.com/assets/130878/20234711/0f639136-a835-11e6-8721-fe5e48dab8e7.png)
![superset-slice-view](https://cloud.githubusercontent.com/assets/130878/20234712/0f63c4c6-a835-11e6-8595-6091a6428fa9.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](https://hub.docker.com/r/kochalex/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)

10
TODO.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,14 +7,12 @@ 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`
* **Default slice:** choose a default slice for the dataset instead of
default endpoint
* **refresh freq**: specifying the refresh frequency of a dashboard and
specific slices within it, some randomization would be nice
* **Widget sets / chart grids:** a way to have all charts support making
a series of charts and putting them in a grid. The same way that you
can groupby for series, you could chart by. The form field set would be
@@ -27,18 +25,14 @@ List of TODO items for Caravel
some visualizations as annotations. An example of a layer might be
"holidays" or "site outages", ...
* **Slack integration** - TBD
* **Sexy Viz Selector:** the visualization selector should be a nice large
modal with nice thumbnails for each one of the viz
* **Comments:** allow for people to comment on slices and dashes
## Easy-ish fix
* Build matrix to include mysql using tox
* Kill switch for Druid in docs
* CREATE VIEW button from SQL editor
* Test button for when editing SQL expression
* Slider form element
* datasource in explore mode could be a dropdown
* [druid] Allow for post aggregations (ratios!)
* in/notin filters autocomplete (druid)

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]

4
babel/babel.cfg Normal file
View File

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

1809
babel/messages.pot Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +0,0 @@
"""Package's main module!"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import logging
import os
from flask import Flask, redirect
from flask.ext.appbuilder import SQLA, AppBuilder, IndexView
from flask.ext.appbuilder.baseviews import expose
from flask.ext.cache import Cache
from flask.ext.migrate import Migrate
VERSION = '0.8.8'
APP_DIR = os.path.dirname(__file__)
CONFIG_MODULE = os.environ.get('CARAVEL_CONFIG', 'caravel.config')
# Logging configuration
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(name)s:%(message)s')
logging.getLogger().setLevel(logging.DEBUG)
app = Flask(__name__)
app.config.from_object(CONFIG_MODULE)
db = SQLA(app)
cache = Cache(app, config=app.config.get('CACHE_CONFIG'))
migrate = Migrate(app, db, directory=APP_DIR + "/migrations")
class MyIndexView(IndexView):
@expose('/')
def index(self):
return redirect('/caravel/welcome')
appbuilder = AppBuilder(
app, db.session,
base_template='caravel/base.html',
indexview=MyIndexView,
security_manager_class=app.config.get("CUSTOM_SECURITY_MANAGER"))
sm = appbuilder.sm
get_session = appbuilder.get_session
from caravel import config, views # noqa

View File

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

View File

@@ -1,3 +0,0 @@
node_modules/*
vendor/*
javascripts/dist/*

View File

@@ -1,234 +0,0 @@
{
"root": true,
"globals": {
"Symbol": false,
"Map": false,
"Set": false,
"Reflect": false,
},
"env": {
"es6": false,
"browser": true,
"node": true,
},
"parserOptions": {
"ecmaVersion": 5,
"sourceType": "module"
},
"rules": {
"array-bracket-spacing": [2, "never", {
"singleValue": false,
"objectsInArrays": false,
"arraysInArrays": false
}],
"array-callback-return": [2],
"block-spacing": [2, "always"],
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"callback-return": [2, ["callback"]],
"camelcase": [0],
"comma-dangle": [2, "never"],
"comma-spacing": [2],
"comma-style": [2, "last"],
"curly": [2, "all"],
"eqeqeq": 2,
"func-names": [0],
"id-length": [2, { "min": 1, "max": 25, "properties": "never" }],
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
"keyword-spacing": [2, {
"before": true,
"after": true,
"overrides": {
"return": { "after": true },
"throw": { "after": true },
"case": { "after": true }
}
}],
"linebreak-style": [2, "unix"],
"lines-around-comment": [2, {
"beforeBlockComment": false,
"afterBlockComment": false,
"beforeLineComment": false,
"allowBlockStart": true,
"allowBlockEnd": true
}],
"max-depth": [2, 5],
"max-len": [0, 80, 4],
"max-nested-callbacks": [1, 3],
"max-params": [1, 4],
"new-parens": [2],
"newline-after-var": [0],
"no-bitwise": [0],
"no-cond-assign": [2],
"no-console": [1, { allow: ["warn", "error"] }],
"no-const-assign": [2],
"no-constant-condition": [2],
"no-control-regex": [2],
"no-debugger": [2],
"no-delete-var": [2],
"no-dupe-args": [2],
"no-dupe-class-members": [2],
"no-dupe-keys": [2],
"no-duplicate-case": [2],
"no-else-return": [0],
"no-empty": [2],
"no-eq-null": [0],
"no-eval": [2],
"no-ex-assign": [2],
"no-extend-native": [2],
"no-extra-bind": [2],
"no-extra-boolean-cast": [2],
"no-extra-label": [2],
"no-extra-parens": [0], // needed for clearer #math eg (a - b) / c
"no-extra-semi": [2],
"no-fallthrough": [2],
"no-floating-decimal": [2],
"no-func-assign": [2],
"no-implied-eval": [2],
"no-implicit-coercion": [2, {
"boolean": false,
"number": true,
"string": true
}],
"no-implicit-globals": [2],
"no-inline-comments": [0],
"no-invalid-regexp": [2],
"no-irregular-whitespace": [2],
"no-iterator": [2],
"no-label-var": [2],
"no-labels": [2, { "allowLoop": false, "allowSwitch": false }],
"no-lone-blocks": [2],
"no-lonely-if": [2],
"no-loop-func": [2],
"no-magic-numbers": [0], // doesn't work well with vis cosmetic constant
"no-mixed-requires": [1, false],
"no-mixed-spaces-and-tabs": [2, false],
"no-multi-spaces": [2, {
"exceptions": {
"ImportDeclaration": true,
"Property": true,
"VariableDeclarator": true
}
}],
"no-multi-str": [2],
"no-multiple-empty-lines": [2, { "max": 1, "maxEOF": 1 }],
"no-native-reassign": [2],
"no-negated-condition": [2],
"no-negated-in-lhs": [2],
"no-nested-ternary": [0],
"no-new": [2],
"no-new-func": [2],
"no-new-object": [2],
"no-new-require": [0],
"no-new-symbol": [2],
"no-new-wrappers": [2],
"no-obj-calls": [2],
"no-octal": [2],
"no-octal-escape": [2],
"no-path-concat": [0],
"no-process-env": [0],
"no-process-exit": [2],
"no-proto": [2],
"no-redeclare": [2],
"no-regex-spaces": [2],
"no-restricted-modules": [0],
"no-restricted-imports": [0],
"no-restricted-syntax": [2,
"DebuggerStatement",
"LabeledStatement",
"WithStatement"
],
"no-return-assign": [2, "always"],
"no-script-url": [2],
"no-self-assign": [2],
"no-self-compare": [0],
"no-sequences": [2],
"no-shadow-restricted-names": [2],
"no-spaced-func": [2],
"no-sparse-arrays": [2],
"no-sync": [0],
"no-ternary": [0],
"no-this-before-super": [2],
"no-throw-literal": [2],
"no-trailing-spaces": [2, { "skipBlankLines": false }],
"no-undef": [2, { "typeof": true }],
"no-undef-init": [2],
"no-undefined": [0],
"no-underscore-dangle": [0], // __data__ sometimes
"no-unexpected-multiline": [2],
"no-unmodified-loop-condition": [2],
"no-unneeded-ternary": [2],
"no-unreachable": [2],
"no-unused-expressions": [2],
"no-unused-labels": [2],
"no-unused-vars": [2, {
"vars": "all",
"args": "none", // (d, i) pattern d3 func makes difficult to enforce
"varsIgnorePattern": "jQuery"
}],
"no-use-before-define": [0],
"no-useless-call": [2],
"no-useless-concat": [2],
"no-useless-constructor": [2],
"no-void": [0],
"no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
"no-with": [2],
"no-whitespace-before-property": [2],
"object-curly-spacing": [2, "always"],
"object-shorthand": [2, "never"],
"one-var": [0],
"one-var-declaration-per-line": [2, "initializations"],
"operator-assignment": [0, "always"],
"padded-blocks": [0],
"prefer-arrow-callback": [0],
"prefer-const": [0],
"prefer-reflect": [0],
"prefer-rest-params": [0],
"prefer-spread": [0],
"prefer-template": [0],
"quote-props": [2, "as-needed", { "keywords": true }],
"radix": [2],
"require-yield": [2],
"semi": [2],
"semi-spacing": [2, { "before": false, "after": true }],
"sort-vars": [0],
"sort-imports": [0],
"space-before-function-paren": [2, { "anonymous": "always", "named": "never" }],
"space-before-blocks": [2, { "functions": "always", "keywords": "always" }],
"space-in-brackets": [0, "never", {
"singleValue": true,
"arraysInArrays": false,
"arraysInObjects": false,
"objectsInArrays": true,
"objectsInObjects": true,
"propertyName": false
}],
},
// Temporarily not enforced
"new-cap": [2], // @TODO more tricky for the moment
"newline-per-chained-call": [2, { "ignoreChainWithDepth": 6 }],
"no-param-reassign": [0], // turn on once default args supported
"no-shadow": [2, { // @TODO more tricky for the moment with eg 'data'
"builtinGlobals": false,
"hoist": "functions",
"allow": ["i", "d"]
}],
"space-in-parens": [2, "never"],
"space-infix-ops": [2],
"space-unary-ops": [2, { "words": true, "nonwords": false }],
"spaced-comment": [2, "always", { "markers": ["!"] }],
"spaced-line-comment": [0, "always"],
"strict": [2, "global"],
"template-curly-spacing": [2, "never"],
"use-isnan": [2],
"valid-jsdoc": [0],
"valid-typeof": [2],
"vars-on-top": [0],
"wrap-iife": [2],
"wrap-regex": [2],
"yield-star-spacing": [2, { "before": false, "after": true }],
"yoda": [2, "never", { "exceptRange": true, "onlyEquality": false }]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 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

View File

@@ -1 +0,0 @@
require('../stylesheets/less/index.less');

View File

@@ -1,258 +0,0 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('./modules/caravel.js');
var d3 = require('d3');
var showModal = require('./modules/utils.js').showModal;
require('bootstrap');
var ace = require('brace');
require('brace/mode/css');
require('brace/theme/crimson_editor');
require('./caravel-select2.js');
require('../node_modules/gridster/dist/jquery.gridster.min.css');
require('../node_modules/gridster/dist/jquery.gridster.min.js');
var Dashboard = function (dashboardData) {
var dashboard = $.extend(dashboardData, {
filters: {},
init: function () {
this.initDashboardView();
px.initFavStars();
var sliceObjects = [],
dash = this;
dashboard.slices.forEach(function (data) {
if (data.error) {
var html = '<div class="alert alert-danger">' + data.error + '</div>';
$("#slice_" + data.slice_id).find('.token').html(html);
} else {
var slice = px.Slice(data, dash);
$("#slice_" + data.slice_id).find('a.refresh').click(function () {
slice.render(true);
});
sliceObjects.push(slice);
slice.render();
}
});
this.slices = sliceObjects;
},
setFilter: function (slice_id, col, vals) {
this.addFilter(slice_id, col, vals, false);
},
addFilter: function (slice_id, col, vals, merge) {
if (merge === undefined) {
merge = true;
}
if (!(slice_id in this.filters)) {
this.filters[slice_id] = {};
}
if (!(col in this.filters[slice_id]) || !merge) {
this.filters[slice_id][col] = vals;
} else {
this.filters[slice_id][col] = d3.merge([this.filters[slice_id][col], vals]);
}
this.refreshExcept(slice_id);
},
readFilters: function () {
// Returns a list of human readable active filters
return JSON.stringify(this.filters, null, 4);
},
refreshExcept: function (slice_id) {
var immune = this.metadata.filter_immune_slice || [];
this.slices.forEach(function (slice) {
if (slice.data.slice_id !== slice_id && immune.indexOf(slice.data.slice_id) === -1) {
slice.render();
}
});
},
clearFilters: function (slice_id) {
delete this.filters[slice_id];
this.refreshExcept(slice_id);
},
removeFilter: function (slice_id, col, vals) {
if (slice_id in this.filters) {
if (col in this.filters[slice_id]) {
var a = [];
this.filters[slice_id][col].forEach(function (v) {
if (vals.indexOf(v) < 0) {
a.push(v);
}
});
this.filters[slice_id][col] = a;
}
}
this.refreshExcept(slice_id);
},
getSlice: function (slice_id) {
slice_id = parseInt(slice_id, 10);
for (var i=0; i < this.slices.length; i++) {
if (this.slices[i].data.slice_id === slice_id) {
return this.slices[i];
}
}
},
initDashboardView: function () {
dashboard = this;
var gridster = $(".gridster ul").gridster({
autogrow_cols: true,
widget_margins: [10, 10],
widget_base_dimensions: [95, 95],
draggable: {
handle: '.drag'
},
resize: {
enabled: true,
stop: function (e, ui, element) {
dashboard.getSlice($(element).attr('slice_id')).resize();
}
},
serialize_params: function (_w, wgd) {
return {
slice_id: $(_w).attr('slice_id'),
col: wgd.col,
row: wgd.row,
size_x: wgd.size_x,
size_y: wgd.size_y
};
}
}).data('gridster');
// Displaying widget controls on hover
$('.chart-header').hover(
function () {
$(this).find('.chart-controls').fadeIn(300);
},
function () {
$(this).find('.chart-controls').fadeOut(300);
}
);
$("div.gridster").css('visibility', 'visible');
$("#savedash").click(function () {
var expanded_slices = {};
$.each($(".slice_info"), function (i, d) {
var widget = $(this).parents('.widget');
var slice_description = widget.find('.slice_description');
if (slice_description.is(":visible")) {
expanded_slices[$(d).attr('slice_id')] = true;
}
});
var data = {
positions: gridster.serialize(),
css: editor.getValue(),
expanded_slices: expanded_slices
};
$.ajax({
type: "POST",
url: '/caravel/save_dash/' + dashboard.id + '/',
data: {
data: JSON.stringify(data)
},
success: function () {
showModal({
title: "Success",
body: "This dashboard was saved successfully."
});
},
error: function (error) {
showModal({
title: "Error",
body: "Sorry, there was an error saving this dashboard:<br />" + error
});
console.warn("Save dashboard error", error);
}
});
});
var editor = ace.edit("dash_css");
editor.$blockScrolling = Infinity;
editor.setTheme("ace/theme/crimson_editor");
editor.setOptions({
minLines: 16,
maxLines: Infinity,
useWorker: false
});
editor.getSession().setMode("ace/mode/css");
$(".select2").select2({
dropdownAutoWidth: true
});
$("#css_template").on("change", function () {
var css = $(this).find('option:selected').data('css');
editor.setValue(css);
$('#dash_css').val(css);
injectCss("dashboard-template", css);
});
$('#filters').click(function () {
showModal({
title: "<span class='fa fa-info-circle'></span> Current Global Filters",
body: "The following global filters are currently applied:<br/>" + dashboard.readFilters()
});
});
$('#refresh_dash').click(function () {
dashboard.slices.forEach(function (slice) {
slice.render(true);
});
});
$("a.remove-chart").click(function () {
var li = $(this).parents("li");
gridster.remove_widget(li);
});
$("li.widget").click(function (e) {
var $this = $(this);
var $target = $(e.target);
if ($target.hasClass("slice_info")) {
$this.find(".slice_description").slideToggle(0, function () {
$this.find('.refresh').click();
});
} else if ($target.hasClass("controls-toggle")) {
$this.find(".chart-controls").toggle();
}
});
editor.on("change", function () {
var css = editor.getValue();
$('#dash_css').val(css);
injectCss("dashboard-template", css);
});
var css = $('.dashboard').data('css');
injectCss("dashboard-template", css);
// Injects the passed css string into a style sheet with the specified className
// If a stylesheet doesn't exist with the passed className, one will be injected into <head>
function injectCss(className, css) {
var head = document.head || document.getElementsByTagName('head')[0];
var style = document.querySelector('.' + className);
if (!style) {
if (className.split(' ').length > 1) {
throw new Error("This method only supports selections with a single class name.");
}
style = document.createElement('style');
style.className = className;
style.type = 'text/css';
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.innerHTML = css;
}
}
}
});
dashboard.init();
return dashboard;
};
$(document).ready(function () {
Dashboard($('.dashboard').data('dashboard'));
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
});

View File

@@ -1,365 +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
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('./modules/caravel.js');
var showModal = require('./modules/utils.js').showModal;
require('jquery-ui');
$.widget.bridge('uitooltip', $.ui.tooltip); // Shutting down jq-ui tooltips
require('bootstrap');
require('./caravel-select2.js');
require('../node_modules/bootstrap-toggle/js/bootstrap-toggle.min.js');
// css
require('../vendor/pygments.css');
require('../node_modules/bootstrap-toggle/css/bootstrap-toggle.min.css');
var slice;
function prepForm() {
var i = 1;
// Assigning the right id to form elements in filters
$("#filters > div").each(function () {
$(this).attr("id", function () {
return "flt_" + i;
});
$(this).find("#flt_col_0")
.attr("id", function () {
return "flt_col_" + i;
})
.attr("name", function () {
return "flt_col_" + i;
});
$(this).find("#flt_op_0")
.attr("id", function () {
return "flt_op_" + i;
})
.attr("name", function () {
return "flt_op_" + i;
});
$(this).find("#flt_eq_0")
.attr("id", function () {
return "flt_eq_" + i;
})
.attr("name", function () {
return "flt_eq_" + i;
});
i++;
});
}
function query(force, pushState) {
if (force === undefined) {
force = false;
}
if (pushState !== false) {
history.pushState({}, document.title, slice.querystring());
}
$('.query-and-save button').attr('disabled', 'disabled');
$('.btn-group.results span,a').attr('disabled', 'disabled');
$('div.alert').remove();
$('#is_cached').hide();
prepForm();
slice.render(force);
}
function initExploreView() {
function get_collapsed_fieldsets() {
var collapsed_fieldsets = $("#collapsed_fieldsets").val();
if (collapsed_fieldsets !== undefined && collapsed_fieldsets !== "") {
collapsed_fieldsets = collapsed_fieldsets.split('||');
} else {
collapsed_fieldsets = [];
}
return collapsed_fieldsets;
}
function toggle_fieldset(legend, animation) {
var parent = legend.parent();
var fieldset = parent.find(".legend_label").text();
var collapsed_fieldsets = get_collapsed_fieldsets();
var 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 = collapsed_fieldsets.indexOf(fieldset);
if (index !== -1) {
collapsed_fieldsets.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 = collapsed_fieldsets.indexOf(fieldset);
if (index === -1 && fieldset !== "" && fieldset !== undefined) {
collapsed_fieldsets.push(fieldset);
}
}
$("#collapsed_fieldsets").val(collapsed_fieldsets.join("||"));
}
px.initFavStars();
$('form .panel-heading').click(function () {
toggle_fieldset($(this), true);
$(this).css('cursor', 'pointer');
});
function copyURLToClipboard(url) {
var textArea = document.createElement("textarea");
textArea.style.position = 'fixed';
textArea.style.left = '-1000px';
textArea.value = url;
document.body.appendChild(textArea);
textArea.select();
try {
var successful = document.execCommand('copy');
if (!successful) {
throw new Error("Not successful");
}
} catch (err) {
window.alert("Sorry, your browser does not support copying. Use Ctrl / Cmd + C!");
}
document.body.removeChild(textArea);
return successful;
}
$('#shortner').click(function () {
$.ajax({
type: "POST",
url: '/r/shortner/',
data: {
data: '/' + window.location.pathname + slice.querystring()
},
success: function (data) {
var close = '<a style="cursor: pointer;"><i class="fa fa-close" id="close_shortner"></i></a>';
var copy = '<a style="cursor: pointer;"><i class="fa fa-clipboard" title="Copy to clipboard" id="copy_url"></i></a>';
var spaces = '&nbsp;&nbsp;&nbsp;';
var popover = data + spaces + copy + spaces + close;
var $shortner = $('#shortner')
.popover({
content: popover,
placement: 'left',
html: true,
trigger: 'manual'
})
.popover('show');
$('#copy_url').tooltip().click(function () {
var success = copyURLToClipboard(data);
if (success) {
$(this).attr("data-original-title", "Copied!").tooltip('fixTitle').tooltip('show');
window.setTimeout(destroyPopover, 1200);
}
});
$('#close_shortner').click(destroyPopover);
function destroyPopover() {
$shortner.popover('destroy');
}
},
error: function (error) {
showModal({
title: "Error",
body: "Sorry, an error occurred during this operation:<br/>" + error
});
console.warn("Short URL error", error);
}
});
});
$("#viz_type").change(function () {
$("#query").submit();
});
$("#datasource_id").change(function () {
var url = $(this).find('option:selected').attr('url');
window.location = url;
});
var collapsed_fieldsets = get_collapsed_fieldsets();
for (var i = 0; i < collapsed_fieldsets.length; i++) {
toggle_fieldset($('legend:contains("' + collapsed_fieldsets[i] + '")'), false);
}
function formatViz(viz) {
var url = '/static/assets/images/viz_thumbnails/' + viz.id + '.png';
var no_img = '/static/assets/images/noimg.png';
return $(
'<img class="viz-thumb-option" src="' + url + '" onerror="this.src=\'' + no_img + '\';">' +
'<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 set_filters() {
for (var i = 1; i < 10; i++) {
var eq = px.getParam("flt_eq_" + i);
if (eq !== '') {
add_filter(i);
}
}
}
set_filters();
function add_filter(i) {
var cp = $("#flt0").clone();
$(cp).appendTo("#filters");
$(cp).show();
if (i !== undefined) {
$(cp).find("#flt_eq_0").val(px.getParam("flt_eq_" + i));
$(cp).find("#flt_op_0").val(px.getParam("flt_op_" + i));
$(cp).find("#flt_col_0").val(px.getParam("flt_col_" + i));
}
$(cp).find('select').select2();
$(cp).find('.remove').click(function () {
$(this).parent().parent().remove();
});
}
$(window).bind("popstate", function (event) {
// Browser back button
var 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();
});
$("#plus").click(add_filter);
$("#btn_save").click(function () {
var slice_name = prompt("Name your slice!");
if (slice_name !== "" && slice_name !== null) {
$("#slice_name").val(slice_name);
prepForm();
$("#action").val("save");
$("#query").submit();
}
});
$("#btn_overwrite").click(function () {
var flag = confirm("Overwrite slice [" + $("#slice_name").val() + "] !?");
if (flag) {
$("#action").val("overwrite");
prepForm();
$("#query").submit();
}
});
$(".query").click(function () {
query(true);
});
function create_choices(term, data) {
var filtered = $(data).filter(function () {
return this.text.localeCompare(term) === 0;
});
if (filtered.length === 0) {
return {
id: term,
text: term
};
}
}
function initSelectionToValue(element, callback) {
callback({
id: element.val(),
text: element.val()
});
}
$(".select2_freeform").each(function () {
var parent = $(this).parent();
var name = $(this).attr('name');
var l = [];
var selected = '';
for (var 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: create_choices,
initSelection: initSelectionToValue,
dropdownAutoWidth: true,
multiple: false,
data: l
});
$(this).remove();
});
}
$(document).ready(function () {
initExploreView();
// Dynamically register this visualization
var visType = window.viz_type.value;
px.registerViz(visType);
var data = $('.slice').data('slice');
slice = px.Slice(data);
//
$('.slice').data('slice', slice);
// call vis render method, which issues ajax
query(false, false);
// make checkbox inputs display as toggles
$(':checkbox')
.addClass('pull-right')
.attr("data-onstyle", "default")
.bootstrapToggle({
size: 'mini'
});
$('div.toggle').addClass('pull-right');
slice.bindResizeToWindowResize();
});

View File

@@ -1,18 +0,0 @@
var $ = require('jquery');
var jQuery = $;
import React from 'react';
import { render } from 'react-dom';
import { Jumbotron } from 'react-bootstrap';
class App extends React.Component {
render () {
return (
<Jumbotron>
<h1>Caravel</h1>
<p>Extensible visualization tool for exploring data from any database.</p>
</Jumbotron>
);
}
}
render(<App />, document.getElementById('app'));

View File

@@ -1,386 +0,0 @@
var $ = require('jquery');
var jQuery = $;
var d3 = require('d3');
// vis sources
var sourceMap = {
area: 'nvd3_vis.js',
bar: 'nvd3_vis.js',
bubble: 'nvd3_vis.js',
big_number: 'big_number.js',
big_number_total: 'big_number.js',
compare: 'nvd3_vis.js',
dist_bar: 'nvd3_vis.js',
directed_force: 'directed_force.js',
filter_box: 'filter_box.js',
heatmap: 'heatmap.js',
iframe: 'iframe.js',
line: 'nvd3_vis.js',
markup: 'markup.js',
para: 'parallel_coordinates.js',
pie: 'nvd3_vis.js',
box_plot: 'nvd3_vis.js',
pivot_table: 'pivot_table.js',
sankey: 'sankey.js',
sunburst: 'sunburst.js',
table: 'table.js',
word_cloud: 'word_cloud.js',
world_map: 'world_map.js',
treemap: 'treemap.js'
};
var color = function () {
// Color related utility functions go in this object
var bnbColors = [
//rausch hackb kazan babu lima beach barol
'#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c',
'#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a',
'#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e'
];
var spectrums = {
blue_white_yellow: ['#00d1c1', 'white', '#ffb400'],
fire: ['white', 'yellow', 'red', 'black'],
white_black: ['white', 'black'],
black_white: ['black', 'white']
};
var colorBnb = function () {
// Color factory
var seen = {};
return function (s) {
if (!s) { return; }
// next line is for caravel series that should have the same color
s = s.replace('---', '');
if (seen[s] === undefined) {
seen[s] = Object.keys(seen).length;
}
return this.bnbColors[seen[s] % this.bnbColors.length];
};
};
var colorScalerFactory = function (colors, data, accessor) {
// Returns a linear scaler our of an array of color
if (!Array.isArray(colors)) {
colors = spectrums[colors];
}
var ext = [0, 1];
if (data !== undefined) {
ext = d3.extent(data, accessor);
}
var points = [];
var chunkSize = (ext[1] - ext[0]) / colors.length;
$.each(colors, function (i, c) {
points.push(i * chunkSize);
});
return d3.scale.linear().domain(points).range(colors);
};
return {
bnbColors: bnbColors,
category21: colorBnb(),
colorScalerFactory: colorScalerFactory
};
};
var px = (function () {
var visualizations = {};
var slice;
function getParam(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function UTC(dttm) {
return new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds());
}
var tickMultiFormat = d3.time.format.multi([
[".%L", function (d) {
return d.getMilliseconds();
}], // If there are millisections, show only them
[":%S", function (d) {
return d.getSeconds();
}], // If there are seconds, show only them
["%a %b %d, %I:%M %p", function (d) {
return d.getMinutes() !== 0;
}], // If there are non-zero minutes, show Date, Hour:Minute [AM/PM]
["%a %b %d, %I %p", function (d) {
return d.getHours() !== 0;
}], // If there are hours that are multiples of 3, show date and AM/PM
["%a %b %d, %Y", function (d) {
return d.getDate() !== 1;
}], // If not the first of the month, do "month day, year."
["%B %Y", function (d) {
return d.getMonth() !== 0 && d.getDate() === 1;
}], // If the first of the month, do "month day, year."
["%Y", function (d) {
return true;
}] // fall back on month, year
]);
function formatDate(dttm) {
var d = UTC(new Date(dttm));
//d = new Date(d.getTime() - 1 * 60 * 60 * 1000);
return tickMultiFormat(d);
}
function timeFormatFactory(d3timeFormat) {
var f = d3.time.format(d3timeFormat);
return function (dttm) {
var d = UTC(new Date(dttm));
return f(d);
};
}
function initFavStars() {
var baseUrl = '/caravel/favstar/';
// Init star behavihor for favorite
function show() {
if ($(this).hasClass('selected')) {
$(this).html('<i class="fa fa-star"></i>');
} else {
$(this).html('<i class="fa fa-star-o"></i>');
}
}
$('.favstar')
.attr('title', 'Click to favorite/unfavorite')
.each(show)
.each(function () {
var url = baseUrl + $(this).attr("class_name");
var star = this;
url += '/' + $(this).attr("obj_id") + '/';
$.getJSON(url + 'count/', function (data) {
if (data.count > 0) {
$(star)
.addClass('selected')
.each(show);
}
});
})
.click(function () {
$(this).toggleClass('selected');
var url = baseUrl + $(this).attr("class_name");
url += '/' + $(this).attr("obj_id") + '/';
if ($(this).hasClass('selected')) {
url += 'select/';
} else {
url += 'unselect/';
}
$.get(url);
$(this).each(show);
})
.tooltip();
}
var Slice = function (data, dashboard) {
var timer;
var token = $('#' + data.token);
var container_id = data.token + '_con';
var selector = '#' + container_id;
var container = $(selector);
var slice_id = data.slice_id;
var dttm = 0;
var stopwatch = function () {
dttm += 10;
var num = dttm / 1000;
$('#timer').text(num.toFixed(2) + " sec");
};
var qrystr = '';
var always = function (data) {
//Private f, runs after done and error
clearInterval(timer);
$('#timer').removeClass('btn-warning');
};
slice = {
data: data,
container: container,
container_id: container_id,
selector: selector,
querystring: function () {
var parser = document.createElement('a');
parser.href = data.json_endpoint;
if (dashboard !== undefined) {
var flts = encodeURIComponent(JSON.stringify(dashboard.filters));
qrystr = parser.search + "&extra_filters=" + flts;
} else if ($('#query').length === 0) {
qrystr = parser.search;
} else {
qrystr = '?' + $('#query').serialize();
}
return qrystr;
},
getWidgetHeader: function () {
return this.container.parents("li.widget").find(".chart-header");
},
jsonEndpoint: function () {
var parser = document.createElement('a');
parser.href = data.json_endpoint;
var endpoint = parser.pathname + this.querystring();
endpoint += "&json=true";
endpoint += "&force=" + this.force;
return endpoint;
},
done: function (data) {
clearInterval(timer);
token.find("img.loading").hide();
container.show();
var cachedSelector = null;
if (dashboard === undefined) {
cachedSelector = $('#is_cached');
if (data !== undefined && data.is_cached) {
cachedSelector
.attr('title', 'Served from data cached at ' + data.cached_dttm + '. Click to force-refresh')
.show()
.tooltip('fixTitle');
} else {
cachedSelector.hide();
}
} else {
var refresh = this.getWidgetHeader().find('.refresh');
if (data !== undefined && data.is_cached) {
refresh
.addClass('danger')
.attr(
'title',
'Served from data cached at ' + data.cached_dttm + '. Click to force-refresh')
.tooltip('fixTitle');
} else {
refresh
.removeClass('danger')
.attr(
'title',
'Click to force-refresh')
.tooltip('fixTitle');
}
}
if (data !== undefined) {
$("#query_container").html(data.query);
}
$('#timer').removeClass('btn-warning');
$('#timer').addClass('btn-success');
$('span.query').removeClass('disabled');
$('#json').click(function () {
window.location = data.json_endpoint;
});
$('#standalone').click(function () {
window.location = data.standalone_endpoint;
});
$('#csv').click(function () {
window.location = data.csv_endpoint;
});
$('.btn-group.results span,a').removeAttr('disabled');
$('.query-and-save button').removeAttr('disabled');
always(data);
},
error: function (msg) {
token.find("img.loading").hide();
var err = '<div class="alert alert-danger">' + msg + '</div>';
container.html(err);
container.show();
$('span.query').removeClass('disabled');
$('#timer').addClass('btn-danger');
$('.btn-group.results span,a').removeAttr('disabled');
$('.query-and-save button').removeAttr('disabled');
always(data);
},
width: function () {
return token.width();
},
height: function () {
var others = 0;
var widget = container.parents('.widget');
var slice_description = widget.find('.slice_description');
if (slice_description.is(":visible")) {
others += widget.find('.slice_description').height() + 25;
}
others += widget.find('.chart-header').height();
return widget.height() - others - 10;
},
bindResizeToWindowResize: function () {
var resizeTimer;
$(window).on('resize', function (e) {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
slice.resize();
}, 500);
});
},
render: function (force) {
if (force === undefined) {
force = false;
}
this.force = force;
token.find("img.loading").show();
container.hide();
container.html('');
container.css('height', slice.height());
dttm = 0;
timer = setInterval(stopwatch, 10);
$('#timer').removeClass('btn-danger btn-success');
$('#timer').addClass('btn-warning');
this.viz.render();
},
resize: function () {
token.find("img.loading").show();
container.hide();
container.css('height', slice.height());
container.html('');
this.viz.render();
this.viz.resize();
},
addFilter: function (col, vals) {
if (dashboard !== undefined) {
dashboard.addFilter(slice_id, col, vals);
}
},
setFilter: function (col, vals) {
if (dashboard !== undefined) {
dashboard.setFilter(slice_id, col, vals);
}
},
clearFilter: function () {
if (dashboard !== undefined) {
delete dashboard.clearFilter(slice_id);
}
},
removeFilter: function (col, vals) {
if (dashboard !== undefined) {
delete dashboard.removeFilter(slice_id, col, vals);
}
}
};
var visType = data.form_data.viz_type;
px.registerViz(visType);
slice.viz = visualizations[data.form_data.viz_type](slice);
return slice;
};
function registerViz(name) {
var visSource = sourceMap[name];
if (visSource) {
var visFactory = require('../../visualizations/' + visSource);
if (typeof visFactory === 'function') {
visualizations[name] = visFactory;
}
} else {
throw new Error("require(" + name + ") failed.");
}
}
// Export public functions
return {
registerViz: registerViz,
Slice: Slice,
formatDate: formatDate,
timeFormatFactory: timeFormatFactory,
color: color(),
getParam: getParam,
initFavStars: initFavStars
};
})();
module.exports = px;

View File

@@ -1,80 +0,0 @@
var $ = require('jquery');
var d3 = require('d3');
/*
Utility function that takes a d3 svg:text selection and a max width, and splits the
text's text across multiple tspan lines such that any given line does not exceed max width
If text does not span multiple lines AND adjustedY is passed, will set the text to the passed val
*/
function wrapSvgText(text, width, adjustedY) {
var lineHeight = 1; // ems
text.each(function () {
var text = d3.select(this),
words = text.text().split(/\s+/),
word,
line = [],
lineNumber = 0,
x = text.attr("x"),
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
var didWrap = false;
for (var i = 0; i < words.length; i++) {
word = words[i];
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop(); // remove word that pushes over the limit
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
didWrap = true;
}
}
if (!didWrap && typeof adjustedY !== "undefined") {
tspan.attr("y", adjustedY);
}
});
}
/**
* Sets the body and title content of a modal, and shows it. Assumes HTML for modal exists and that
* it handles closing (i.e., works with bootstrap)
*
* @param {object} options object of the form
* {
* title: {string},
* body: {string},
* modalSelector: {string, default: '.misc-modal' },
* titleSelector: {string, default: '.misc-modal .modal-title' },
* bodySelector: {string, default: '.misc-modal .modal-body' },
* }
*/
function showModal(options) {
options.modalSelector = options.modalSelector || ".misc-modal";
options.titleSelector = options.titleSelector || ".misc-modal .modal-title";
options.bodySelector = options.bodySelector || ".misc-modal .modal-body";
$(options.titleSelector).html(options.title || "");
$(options.bodySelector).html(options.body || "");
$(options.modalSelector).modal("show");
}
module.exports = {
wrapSvgText: wrapSvgText,
showModal: showModal
};

View File

@@ -1,106 +0,0 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var showModal = require('./modules/utils.js').showModal;
require('./caravel-select2.js');
require('datatables.net-bs');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
require('bootstrap');
var ace = require('brace');
require('brace/mode/sql');
require('brace/theme/crimson_editor');
require('../stylesheets/sql.css');
$(document).ready(function () {
function getParam(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
function initSqlEditorView() {
var database_id = $('#database_id').val();
var editor = ace.edit("sql");
editor.$blockScrolling = Infinity;
editor.getSession().setUseWrapMode(true);
$('#sql').hide();
editor.setTheme("ace/theme/crimson_editor");
editor.setOptions({
minLines: 16,
maxLines: Infinity
});
editor.getSession().setMode("ace/mode/sql");
editor.focus();
$("select").select2({
dropdownAutoWidth: true
});
function showTableMetadata() {
$(".metadata").load(
'/caravel/table/' + database_id + '/' + $("#dbtable").val() + '/');
}
$("#dbtable").on("change", showTableMetadata);
showTableMetadata();
$("#create_view").click(function () {
showModal({
title: "Error",
body: "Sorry, this feature is not yet implemented"
});
});
$(".sqlcontent").show();
function selectStarOnClick() {
$.ajax('/caravel/select_star/' + database_id + '/' + $("#dbtable").val() + '/')
.done(function (msg) {
editor.setValue(msg);
});
}
$("#select_star").click(selectStarOnClick);
editor.setValue(getParam('sql'));
$(window).bind("popstate", function (event) {
// Could do something more lightweight here, but we're not optimizing
// for the use of the back button anyways
editor.setValue(getParam('sql'));
$("#run").click();
});
$("#run").click(function () {
$('#results').hide(0);
$('#loading').show(0);
history.pushState({}, document.title, '?sql=' + encodeURIComponent(editor.getValue()));
$.ajax({
type: "POST",
url: '/caravel/runsql/',
data: {
data: JSON.stringify({
database_id: $('#database_id').val(),
sql: editor.getSession().getValue()
})
},
success: function (data) {
$('#loading').hide(0);
$('#results').show(0);
$('#results').html(data);
$('table.sql_results').DataTable({
paging: false,
searching: true,
aaSorting: []
});
},
error: function (err, err2) {
$('#loading').hide(0);
$('#results').show(0);
$('#results').html(err.responseText);
}
});
});
}
initSqlEditorView();
});

View File

@@ -1,13 +0,0 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var px = require('./modules/caravel.js');
require('bootstrap');
$(document).ready(function () {
var slice;
var data = $('.slice').data('slice');
slice = px.Slice(data);
slice.render();
slice.bindResizeToWindowResize();
});

View File

@@ -1,70 +0,0 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
require('../stylesheets/welcome.css');
require('bootstrap');
require('datatables.net-bs');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
require('../node_modules/cal-heatmap/cal-heatmap.css');
var CalHeatMap = require('cal-heatmap');
function modelViewTable(selector, modelView, orderCol, order) {
// Builds a dataTable from a flask appbuilder api endpoint
var url = '/' + modelView.toLowerCase() + '/api/read';
url += '?_oc_' + modelView + '=' + orderCol;
url += '&_od_' + modelView +'=' + order;
$.getJSON(url, function (data) {
var tableData = jQuery.map(data.result, function (el, i) {
var row = $.map(data.list_columns, function (col, i) {
return el[col];
});
return [row];
});
var cols = jQuery.map(data.list_columns, function (col, i) {
return { sTitle: data.label_columns[col] };
});
var panel = $(selector).parents('.panel');
panel.find("img.loading").remove();
$(selector).DataTable({
aaData: tableData,
aoColumns: cols,
bPaginate: true,
pageLength: 10,
bLengthChange: false,
aaSorting: [],
searching: true,
bInfo: false
});
// Hack to move the searchbox in the right spot
var search = panel.find(".dataTables_filter input");
search.addClass('form-control').detach();
search.appendTo(panel.find(".search"));
panel.find('.dataTables_filter').remove();
// Hack to display the page navigator properly
panel.find('.col-sm-5').remove();
var nav = panel.find('.col-sm-7');
nav.removeClass('col-sm-7');
nav.addClass('col-sm-12');
$(selector).slideDown();
$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
});
}
$(document).ready(function () {
var cal = new CalHeatMap();
cal.init({
start: new Date().setFullYear(new Date().getFullYear() - 1),
range: 13,
data: '/caravel/activity_per_day',
domain: "month",
subDomain: "day",
itemName: "action",
tooltip: true
});
modelViewTable('#dash_table', 'DashboardModelViewAsync', 'changed_on', 'desc');
modelViewTable('#slice_table', 'SliceAsync', 'changed_on', 'desc');
});

View File

@@ -1,78 +0,0 @@
{
"name": "caravel",
"version": "0.1.0",
"description": "Any database to any visualization",
"directories": {
"doc": "docs",
"test": "tests"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack -d --watch --colors",
"prod": "webpack -p --colors",
"lint": "npm run --silent lint:js",
"lint:js": "eslint --ignore-path=.eslintignore --ext .js ."
},
"repository": {
"type": "git",
"url": "git+https://github.com/airbnb/caravel.git"
},
"keywords": [
"big",
"data",
"exploratory",
"analysis",
"react",
"d3",
"airbnb",
"nerds",
"database",
"flask"
],
"author": "Airbnb",
"bugs": {
"url": "https://github.com/airbnb/caravel/issues"
},
"homepage": "https://github.com/airbnb/caravel#readme",
"dependencies": {
"babel-loader": "^6.2.1",
"babel-polyfill": "^6.3.14",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"bootstrap": "^3.3.6",
"bootstrap-datepicker": "^1.6.0",
"bootstrap-toggle": "^2.2.1",
"brace": "^0.7.0",
"cal-heatmap": "3.5.4",
"css-loader": "^0.23.1",
"d3": "^3.5.14",
"d3-cloud": "^1.2.1",
"d3-sankey": "^0.2.1",
"d3-tip": "^0.6.7",
"datamaps": "^0.4.4",
"datatables-bootstrap3-plugin": "^0.4.0",
"datatables.net-bs": "^1.10.11",
"exports-loader": "^0.6.3",
"font-awesome": "^4.5.0",
"gridster": "^0.5.6",
"imports-loader": "^0.6.5",
"jquery": "^2.2.1",
"jquery-ui": "^1.10.5",
"less": "^2.6.1",
"less-loader": "^2.2.2",
"nvd3": "1.8.2",
"react": "^0.14.7",
"react-bootstrap": "^0.28.3",
"react-dom": "^0.14.7",
"select2": "3.5",
"select2-bootstrap-css": "^1.4.6",
"style-loader": "^0.13.0",
"topojson": "^1.6.22",
"webpack": "^1.12.12"
},
"devDependencies": {
"eslint": "^2.2.0",
"file-loader": "^0.8.5",
"url-loader": "^0.5.7"
}
}

View File

@@ -1,616 +0,0 @@
// Paper 3.3.5
// Bootswatch
// -----------------------------------------------------
@web-font-path: "https://fonts.googleapis.com/css?family=Roboto:300,400,500,700";
.web-font(@path) {
@import url("@{path}");
}
.web-font(@web-font-path);
// Navbar =====================================================================
.navbar {
border: none;
.box-shadow(0 1px 2px rgba(0,0,0,.3));
&-brand {
font-size: 24px;
}
&-inverse {
.navbar-form {
input[type=text],
input[type=password] {
color: #fff;
.box-shadow(inset 0 -1px 0 @navbar-inverse-link-color);
.placeholder(@navbar-inverse-link-color);
&:focus {
.box-shadow(inset 0 -2px 0 #fff);
}
}
}
}
}
// Buttons ====================================================================
#btn(@class,@bg) {
.btn-@{class} {
background-size: 200%;
background-position: 50%;
&:focus {
background-color: @bg;
}
&:hover,
&:active:hover {
background-color: darken(@bg, 6%);
}
&:active {
background-color: darken(@bg, 12%);
#gradient > .radial(darken(@bg, 12%) 10%, @bg 11%);
background-size: 1000%;
.box-shadow(2px 2px 4px rgba(0,0,0,.4));
}
}
}
#btn(default,@btn-default-bg);
#btn(primary,@btn-primary-bg);
#btn(success,@btn-success-bg);
#btn(info,@btn-info-bg);
#btn(warning,@btn-warning-bg);
#btn(danger,@btn-danger-bg);
#btn(link,#fff);
.btn {
text-transform: uppercase;
border: none;
.box-shadow(1px 1px 4px rgba(0,0,0,.4));
.transition(all 0.4s);
&-link {
border-radius: @btn-border-radius-base;
.box-shadow(none);
color: @btn-default-color;
&:hover,
&:focus {
.box-shadow(none);
color: @btn-default-color;
text-decoration: none;
}
}
&-default {
&.disabled {
background-color: rgba(0, 0, 0, 0.1);
color: rgba(0, 0, 0, 0.4);
opacity: 1;
}
}
}
.btn-group {
.btn + .btn,
.btn + .btn-group,
.btn-group + .btn,
.btn-group + .btn-group {
margin-left: 0;
}
&-vertical {
> .btn + .btn,
> .btn + .btn-group,
> .btn-group + .btn,
> .btn-group + .btn-group {
margin-top: 0;
}
}
}
// Typography =================================================================
body {
-webkit-font-smoothing: antialiased;
letter-spacing: .1px;
}
p {
margin: 0 0 1em;
}
input,
button {
-webkit-font-smoothing: antialiased;
letter-spacing: .1px;
}
a {
.transition(all 0.2s);
}
// Tables =====================================================================
.table-hover {
> tbody > tr,
> tbody > tr > th,
> tbody > tr > td {
.transition(all 0.2s);
}
}
// Forms ======================================================================
label {
font-weight: normal;
}
textarea,
textarea.form-control,
input.form-control,
input[type=text],
input[type=password],
input[type=email],
input[type=number],
[type=text].form-control,
[type=password].form-control,
[type=email].form-control,
[type=tel].form-control,
[contenteditable].form-control {
padding: 0;
border: none;
border-radius: 0;
-webkit-appearance: none;
.box-shadow(inset 0 -1px 0 #ddd);
font-size: 16px;
&:focus {
.box-shadow(inset 0 -2px 0 @brand-primary);
}
&[disabled],
&[readonly] {
.box-shadow(none);
border-bottom: 1px dotted #ddd;
}
&.input {
&-sm {
font-size: @font-size-small;
}
&-lg {
font-size: @font-size-large;
}
}
}
select,
select.form-control {
border: 0;
border-radius: 0;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
padding-left: 0;
padding-right: 0\9; // remove padding for < ie9 since default arrow can't be removed
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEVmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmaP/QSjAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=);
background-size: 13px;
background-repeat: no-repeat;
background-position: right center;
.box-shadow(inset 0 -1px 0 #ddd);
font-size: 16px;
line-height: 1.5;
&::-ms-expand {
display: none;
}
&.input {
&-sm {
font-size: @font-size-small;
}
&-lg {
font-size: @font-size-large;
}
}
&:focus {
.box-shadow(inset 0 -2px 0 @brand-primary);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAMAAACelLz8AAAAJ1BMVEUhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISEhISF8S9ewAAAADHRSTlMAAgMJC0uWpKa6wMxMdjkoAAAANUlEQVR4AeXJyQEAERAAsNl7Hf3X6xt0QL6JpZWq30pdvdadme+0PMdzvHm8YThHcT1H7K0BtOMDniZhWOgAAAAASUVORK5CYII=);
}
&[multiple] {
background: none;
}
}
.radio,
.radio-inline,
.checkbox,
.checkbox-inline {
label {
padding-left: 25px;
}
input[type="radio"],
input[type="checkbox"] {
margin-left: -25px;
}
}
input[type="radio"],
.radio input[type="radio"],
.radio-inline input[type="radio"] {
position: relative;
margin-top: 6px;
margin-right: 4px;
vertical-align: top;
border: none;
background-color: transparent;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
&:focus {
outline: none;
}
&:before,
&:after {
content: "";
display: block;
width: 18px;
height: 18px;
border-radius: 50%;
.transition(240ms);
}
&:before {
position: absolute;
left: 0;
top: -3px;
background-color: @brand-primary;
.scale(0);
}
&:after {
position: relative;
top: -3px;
border: 2px solid @gray;
}
&:checked:before {
.scale(0.5);
}
&:disabled:checked:before {
background-color: @gray-light;
}
&:checked:after {
border-color: @brand-primary;
}
&:disabled:after,
&:disabled:checked:after {
border-color: @gray-light;
}
}
input[type="checkbox"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
position: relative;
border: none;
margin-bottom: -4px;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
&:focus {
outline: none;
}
&:focus:after {
border-color: @brand-primary;
}
&:after {
content: "";
display: block;
width: 18px;
height: 18px;
margin-top: -2px;
margin-right: 5px;
border: 2px solid @gray;
border-radius: 2px;
.transition(240ms);
}
&:checked:before {
content: "";
position: absolute;
top: 0;
left: 6px;
display: table;
width: 6px;
height: 12px;
border: 2px solid #fff;
border-top-width: 0;
border-left-width: 0;
.rotate(45deg);
}
&:checked:after {
background-color: @brand-primary;
border-color: @brand-primary;
}
&:disabled:after {
border-color: @gray-light;
}
&:disabled:checked:after {
background-color: @gray-light;
border-color: transparent;
}
}
.has-warning {
input:not([type=checkbox]),
.form-control,
input.form-control[readonly],
input[type=text][readonly],
[type=text].form-control[readonly],
input:not([type=checkbox]):focus,
.form-control:focus {
border-bottom: none;
.box-shadow(inset 0 -2px 0 @brand-warning);
}
}
.has-error {
input:not([type=checkbox]),
.form-control,
input.form-control[readonly],
input[type=text][readonly],
[type=text].form-control[readonly],
input:not([type=checkbox]):focus,
.form-control:focus {
border-bottom: none;
.box-shadow(inset 0 -2px 0 @brand-danger);
}
}
.has-success {
input:not([type=checkbox]),
.form-control,
input.form-control[readonly],
input[type=text][readonly],
[type=text].form-control[readonly],
input:not([type=checkbox]):focus,
.form-control:focus {
border-bottom: none;
.box-shadow(inset 0 -2px 0 @brand-success);
}
}
// Remove the Bootstrap feedback styles for input addons
.input-group-addon {
.has-warning &, .has-error &, .has-success & {
color: @input-color;
border-color: @input-group-addon-border-color;
background-color: @input-group-addon-bg;
}
}
// Navs =======================================================================
.nav-tabs {
> li > a,
> li > a:focus {
margin-right: 0;
background-color: transparent;
border: none;
color: @navbar-default-link-color;
.box-shadow(inset 0 -1px 0 #ddd);
.transition(all 0.2s);
&:hover {
background-color: transparent;
.box-shadow(inset 0 -2px 0 @brand-primary);
color: @brand-primary;
}
}
& > li.active > a,
& > li.active > a:focus {
border: none;
.box-shadow(inset 0 -2px 0 @brand-primary);
color: @brand-primary;
&:hover {
border: none;
color: @brand-primary;
}
}
& > li.disabled > a {
.box-shadow(inset 0 -1px 0 #ddd);
}
&.nav-justified {
& > li > a,
& > li > a:hover,
& > li > a:focus,
& > .active > a,
& > .active > a:hover,
& > .active > a:focus {
border: none;
}
}
.dropdown-menu {
margin-top: 0;
}
}
.dropdown-menu {
margin-top: 0;
border: none;
.box-shadow(0 1px 4px rgba(0,0,0,.3));
}
// Indicators =================================================================
.alert {
border: none;
color: #fff;
&-success {
background-color: @brand-success;
}
&-info {
background-color: @brand-info;
}
&-warning {
background-color: @brand-warning;
}
&-danger {
background-color: @brand-danger;
}
a:not(.close),
.alert-link {
color: #fff;
font-weight: bold;
}
.close {
color: #fff;
}
}
.badge {
padding: 4px 6px 4px;
}
.progress {
position: relative;
z-index: 1;
height: 6px;
border-radius: 0;
.box-shadow(none);
&-bar {
.box-shadow(none);
&:last-child {
border-radius: 0 3px 3px 0;
}
&:last-child {
&:before {
display: block;
content: "";
position: absolute;
width: 100%;
height: 100%;
left: 0;
right: 0;
z-index: -1;
background-color: lighten(@progress-bar-bg, 35%);
}
}
&-success:last-child.progress-bar:before {
background-color: lighten(@brand-success, 35%);
}
&-info:last-child.progress-bar:before {
background-color: lighten(@brand-info, 45%);
}
&-warning:last-child.progress-bar:before {
background-color: lighten(@brand-warning, 35%);
}
&-danger:last-child.progress-bar:before {
background-color: lighten(@brand-danger, 25%);
}
}
}
// Progress bars ==============================================================
// Containers =================================================================
.close {
font-size: 34px;
font-weight: 300;
line-height: 24px;
opacity: 0.6;
.transition(all 0.2s);
&:hover {
opacity: 1;
}
}
.list-group {
&-item {
padding: 15px;
}
&-item-text {
color: @gray-light;
}
}
.well {
border-radius: 0;
.box-shadow(none);
}
.panel {
border: none;
border-radius: 2px;
.box-shadow(0 1px 4px rgba(0,0,0,.3));
&-heading {
border-bottom: none;
}
&-footer {
border-top: none;
}
}
.popover {
border: none;
.box-shadow(0 1px 4px rgba(0,0,0,.3));
}
.carousel {
&-caption {
h1, h2, h3, h4, h5, h6 {
color: inherit;
}
}
}

View File

@@ -1,183 +0,0 @@
// JS
var d3 = window.d3 || require('d3');
// CSS
require('./big_number.css');
var px = require('../javascripts/modules/caravel.js');
function bigNumberVis(slice) {
var 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);
return '';
}
var fd = payload.form_data;
var json = payload.data;
var color_range = [-1, 1];
var f = d3.format(fd.y_axis_format);
var fp = d3.format('+.1%');
var width = slice.width();
var height = slice.height();
div.selectAll("*").remove();
var svg = div.append('svg');
svg.attr("width", width);
svg.attr("height", height);
var data = json.data;
var compare_suffix = ' ' + json.compare_suffix;
var v_compare = null;
var v = null;
if (fd.viz_type === 'big_number') {
v = data[data.length - 1][1];
} else {
v = data[0][0];
}
if (json.compare_lag > 0) {
var pos = data.length - (json.compare_lag + 1);
if (pos >= 0) {
v_compare = (v / data[pos][1]) - 1;
}
}
var date_ext = d3.extent(data, function (d) {
return d[0];
});
var value_ext = d3.extent(data, function (d) {
return d[1];
});
var margin = 20;
var scale_x = d3.time.scale.utc().domain(date_ext).range([margin, width - margin]);
var scale_y = d3.scale.linear().domain(value_ext).range([height - (margin), margin]);
var colorRange = [d3.hsl(0, 1, 0.3), d3.hsl(120, 1, 0.3)];
var scale_color = d3.scale
.linear().domain(color_range)
.interpolate(d3.interpolateHsl)
.range(colorRange).clamp(true);
var line = d3.svg.line()
.x(function (d) {
return scale_x(d[0]);
})
.y(function (d) {
return scale_y(d[1]);
})
.interpolate("basis");
var g = svg.append('g');
var y = height / 2;
//Printing big number
g.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)
.attr('fill', 'white');
if (fd.viz_type === 'big_number') {
//Drawing trend line
g.append('path')
.attr('d', function (d) {
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 (v_compare !== null) {
y = (height / 8) * 3;
}
//Printing big number subheader text
if (json.subheader !== null) {
g.append('text')
.attr('x', width / 2)
.attr('y', y + d3.min([height, width]) / 4.5)
.text(json.subheader)
.attr('id', 'subheader_text')
.style('font-size', d3.min([height, width]) / 16)
.style('text-anchor', 'middle')
.attr('fill', c)
.attr('stroke', c);
}
var c = scale_color(v_compare);
//Printing compare %
if (v_compare !== null) {
g.append('text')
.attr('x', width / 2)
.attr('y', (height / 16) * 12)
.text(fp(v_compare) + compare_suffix)
.style('font-size', d3.min([height, width]) / 8)
.style('text-anchor', 'middle')
.attr('fill', c)
.attr('stroke', c);
}
var g_axis = svg.append('g').attr('class', 'axis').attr('opacity', 0);
g = g_axis.append('g');
var x_axis = d3.svg.axis()
.scale(scale_x)
.orient('bottom')
.ticks(4)
.tickFormat(px.formatDate);
g.call(x_axis);
g.attr('transform', 'translate(0,' + (height - margin) + ')');
g = g_axis.append('g').attr('transform', 'translate(' + (width - margin) + ',0)');
var y_axis = d3.svg.axis()
.scale(scale_y)
.orient('left')
.tickFormat(d3.format(fd.y_axis_format))
.tickValues(value_ext);
g.call(y_axis);
g.selectAll('text')
.style('text-anchor', 'end')
.attr('y', '-7')
.attr('x', '-4');
g.selectAll("text")
.style('font-size', '10px');
div.on('mouseover', function (d) {
var div = d3.select(this);
div.select('path').transition().duration(500).attr('opacity', 1)
.style('stroke-width', '2px');
div.select('g.digits').transition().duration(500).attr('opacity', 0.1);
div.select('g.axis').transition().duration(500).attr('opacity', 1);
})
.on('mouseout', function (d) {
var div = d3.select(this);
div.select('path').transition().duration(500).attr('opacity', 0.5)
.style('stroke-width', '5px');
div.select('g.digits').transition().duration(500).attr('opacity', 1);
div.select('g.axis').transition().duration(500).attr('opacity', 0);
});
}
slice.done(payload);
});
}
return {
render: render,
resize: render
};
}
module.exports = bigNumberVis;

View File

@@ -1,175 +0,0 @@
// JS
var d3 = window.d3 || require('d3');
// CSS
require('./directed_force.css');
/* Modified from http://bl.ocks.org/d3noob/5141278 */
function directedForceVis(slice) {
var div = d3.select(slice.selector);
var link_length = slice.data.form_data.link_length || 200;
var charge = slice.data.form_data.charge || -500;
var render = function () {
var width = slice.width();
var height = slice.height() - 25;
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText);
return '';
}
var links = json.data;
var 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);
var target_name = link.target.name;
var source_name = link.source.name;
if (nodes[target_name].total === undefined) {
nodes[target_name].total = link.value;
}
if (nodes[source_name].total === undefined) {
nodes[source_name].total = 0;
}
if (nodes[target_name].max === undefined) {
nodes[target_name].max = 0;
}
if (link.value > nodes[target_name].max) {
nodes[target_name].max = link.value;
}
if (nodes[target_name].min === undefined) {
nodes[target_name].min = 0;
}
if (link.value > nodes[target_name].min) {
nodes[target_name].min = link.value;
}
nodes[target_name].total += link.value;
});
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(link_length)
.charge(charge)
.on("tick", tick)
.start();
var 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");
var edgeScale = d3.scale.linear()
.range([0.1, 0.5]);
// add the links and the arrows
var 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
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.on("mouseenter", function (d) {
d3.select(this)
.select("circle")
.transition()
.style('stroke-width', 5);
d3.select(this)
.select("text")
.transition()
.style('font-size', 25);
})
.on("mouseleave", function (d) {
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
var ext = d3.extent(d3.values(nodes), function (d) {
return Math.sqrt(d.total);
});
var 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;
});
// add the curvy lines
function tick() {
path.attr("d", function (d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
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 + ")";
});
}
slice.done(json);
});
};
return {
render: render,
resize: render
};
}
module.exports = directedForceVis;

View File

@@ -1,8 +0,0 @@
.select2-highlighted > .filter_box {
background-color: transparent;
border: 1px caravel black;
}
.dashboard .filter_box .slice_container > div {
padding-top: 0;
}

View File

@@ -1,82 +0,0 @@
// JS
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var d3 = window.d3 || require('d3');
// CSS
require('./filter_box.css');
require('../javascripts/caravel-select2.js');
function filterBox(slice) {
var filtersObj = {};
var d3token = d3.select(slice.selector);
var fltChanged = function () {
var val = $(this).val();
var vals = [];
if (val !== '') {
vals = val.split(',');
}
slice.setFilter($(this).attr('name'), vals);
};
var refresh = function () {
d3token.selectAll("*").remove();
var container = d3token
.append('div')
.classed('padded', true);
$.getJSON(slice.jsonEndpoint(), function (payload) {
var maxes = {};
for (var filter in payload.data) {
var data = payload.data[filter];
maxes[filter] = d3.max(data, function (d) {
return d.metric;
});
var id = 'fltbox__' + filter;
var div = container.append('div');
div.append("label").text(filter);
div.append('div')
.attr('name', filter)
.classed('form-control', true)
.attr('multiple', '')
.attr('id', id);
filtersObj[filter] = $('#' + id).select2({
placeholder: "Select [" + filter + ']',
containment: 'parent',
dropdownAutoWidth: true,
data: data,
multiple: true,
formatResult: select2Formatter
})
.on('change', fltChanged);
}
slice.done(payload);
function select2Formatter(result, container /*, query, escapeMarkup*/) {
var perc = Math.round((result.metric / maxes[result.filter]) * 100);
var style = 'padding: 2px 5px;';
style += "background-image: ";
style += "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%";
$(container).attr('style', 'padding: 0px; background: white;');
$(container).addClass('filter_box');
return '<div style="' + style + '"><span>' + result.text + '</span></div>';
}
})
.fail(function (xhr) {
slice.error(xhr.responseText);
});
};
return {
render: refresh,
resize: refresh
};
}
module.exports = filterBox;

View File

@@ -1,234 +0,0 @@
// JS
var $ = window.$ || require('jquery');
var px = window.px || require('../javascripts/modules/caravel.js');
var d3 = require('d3');
d3.tip = require('d3-tip'); //using window.d3 doesn't capture events properly bc of multiple instances
// CSS
require('./heatmap.css');
// Inspired from http://bl.ocks.org/mbostock/3074470
// https://jsfiddle.net/cyril123/h0reyumq/
function heatmapVis(slice) {
function refresh() {
var margin = {
top: 10,
right: 10,
bottom: 35,
left: 35
};
d3.json(slice.jsonEndpoint(), function (error, payload) {
var matrix = {};
if (error) {
slice.error(error.responseText);
return '';
}
var fd = payload.form_data;
var data = payload.data;
// Dynamically adjusts based on max x / y category lengths
function adjustMargins(data, margins) {
var pixelsPerCharX = 4.5; // approx, depends on font size
var pixelsPerCharY = 6.8; // approx, depends on font size
var longestX = 1;
var longestY = 1;
var datum;
for (var 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);
}
margins.left = Math.ceil(Math.max(margins.left, pixelsPerCharY * longestY));
margins.bottom = Math.ceil(Math.max(margins.bottom, pixelsPerCharX * longestX));
}
function ordScale(k, rangeBands, reverse) {
if (reverse === undefined) {
reverse = false;
}
var 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));
} else {
return d3.scale.ordinal().domain(domain).rangeBands(rangeBands);
}
}
adjustMargins(data, margin);
var width = slice.width();
var height = slice.height();
var hmWidth = width - (margin.left + margin.right);
var hmHeight = height - (margin.bottom + margin.top);
var fp = d3.format('.3p');
var xScale = ordScale('x');
var yScale = ordScale('y', undefined, true);
var xRbScale = ordScale('x', [0, hmWidth]);
var yRbScale = ordScale('y', [hmHeight, 0]);
var X = 0,
Y = 1;
var heatmapDim = [xRbScale.domain().length, yRbScale.domain().length];
var color = px.color.colorScalerFactory(fd.linear_color_scheme);
var scale = [
d3.scale.linear()
.domain([0, heatmapDim[X]])
.range([0, hmWidth]),
d3.scale.linear()
.domain([0, heatmapDim[Y]])
.range([0, hmHeight])
];
var container = d3.select(slice.selector);
var 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");
var svg = container.append("svg")
.attr("width", width)
.attr("height", height)
.style("left", "0px")
.style("top", "0px")
.style("position", "absolute");
var 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);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset(function () {
var k = d3.mouse(this);
var x = k[0] - (hmWidth / 2);
return [k[1] - 20, x];
})
.html(function (d) {
var s = "";
var k = d3.mouse(this);
var m = Math.floor(scale[0].invert(k[0]));
var n = Math.floor(scale[1].invert(k[1]));
if (m in matrix && n in matrix[m]) {
var 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);
var xAxis = d3.svg.axis()
.scale(xRbScale)
.tickValues(xRbScale.domain().filter(
function (d, i) {
return !(i % (parseInt(fd.xscale_interval, 10)));
}))
.orient("bottom");
var 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);
var context = canvas.node().getContext("2d");
context.imageSmoothingEnabled = false;
createImageObj();
// Compute the pixel colors; scaled by CSS.
function createImageObj() {
var imageObj = new Image();
var image = context.createImageData(heatmapDim[0], heatmapDim[1]);
var pixs = {};
$.each(data, function (i, d) {
var c = d3.rgb(color(d.perc));
var x = xScale(d.x);
var 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;
}
});
var p = -1;
for (var i = 0; i < heatmapDim[0] * heatmapDim[1]; i++) {
var c = pixs[i];
var 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();
}
slice.done();
});
}
return {
render: refresh,
resize: refresh
};
}
module.exports = heatmapVis;

View File

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

View File

@@ -1,8 +0,0 @@
g.caravel path {
stroke-dasharray: 5, 5;
}
.nvtooltip tr.highlight td {
font-weight: bold;
font-size: 15px !important;
}

View File

@@ -1,229 +0,0 @@
// JS
var $ = window.$ || require('jquery');
var d3 = window.d3 || require('d3');
var px = window.px || require('../javascripts/modules/caravel.js');
var nv = require('nvd3');
// CSS
require('../node_modules/nvd3/build/nv.d3.min.css');
require('./nvd3_vis.css');
function nvd3Vis(slice) {
var chart;
var render = function () {
$.getJSON(slice.jsonEndpoint(), function (payload) {
var fd = payload.form_data;
var viz_type = fd.viz_type;
var f = d3.format('.3s');
var colorKey = 'key';
nv.addGraph(function () {
switch (viz_type) {
case 'line':
if (fd.show_brush) {
chart = nv.models.lineWithFocusChart();
chart.lines2.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(true)
.groupSpacing(0.1);
chart.xAxis
.showMaxMin(false)
.staggerLabels(true);
chart.stacked(fd.bar_stacked);
break;
case 'dist_bar':
chart = nv.models.multiBarChart()
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.reduceXTicks(false)
.rotateLabels(45)
.groupSpacing(0.1); //Distance between each group of bars.
chart.xAxis
.showMaxMin(false);
chart.stacked(fd.bar_stacked);
break;
case 'pie':
chart = nv.models.pieChart();
colorKey = 'x';
chart.valueFormat(f);
if (fd.donut) {
chart.donut(true);
chart.labelsOutside(true);
}
chart.labelsOutside(true);
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':
var row = function (col1, col2) {
return "<tr><td>" + col1 + "</td><td>" + col2 + "</td></tr>";
};
chart = nv.models.scatterChart();
chart.showDistX(true);
chart.showDistY(true);
chart.tooltip.contentGenerator(function (obj) {
var p = obj.point;
var 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.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" + viz_type);
}
if ("showLegend" in chart && typeof fd.show_legend !== 'undefined') {
chart.showLegend(fd.show_legend);
}
var height = slice.height();
height -= 15; // accounting for the staggered xAxis
chart.height(height);
slice.container.css('height', height + 'px');
if ((viz_type === "line" || viz_type === "area") && fd.rich_tooltip) {
chart.useInteractiveGuideline(true);
}
if (fd.y_axis_zero) {
chart.forceY([0, 1]);
} else if (fd.y_log_scale) {
chart.yScale(d3.scale.log());
}
if (fd.x_log_scale) {
chart.xScale(d3.scale.log());
}
var xAxisFormatter = null;
if (viz_type === 'bubble') {
xAxisFormatter = d3.format('.3s');
} else if (fd.x_axis_format === 'smart_date') {
xAxisFormatter = px.formatDate;
chart.xAxis.tickFormat(xAxisFormatter);
} else if (fd.x_axis_format !== undefined) {
xAxisFormatter = px.timeFormatFactory(fd.x_axis_format);
chart.xAxis.tickFormat(xAxisFormatter);
}
if (chart.hasOwnProperty("x2Axis")) {
chart.x2Axis.tickFormat(xAxisFormatter);
height += 30;
}
if (viz_type === 'bubble') {
chart.xAxis.tickFormat(d3.format('.3s'));
} else if (fd.x_axis_format === 'smart_date') {
chart.xAxis.tickFormat(px.formatDate);
} else if (fd.x_axis_format !== undefined) {
chart.xAxis.tickFormat(px.timeFormatFactory(fd.x_axis_format));
}
if (chart.yAxis !== undefined) {
chart.yAxis.tickFormat(d3.format('.3s'));
}
if (fd.contribution || fd.num_period_compare || viz_type === 'compare') {
chart.yAxis.tickFormat(d3.format('.3p'));
if (chart.y2Axis !== undefined) {
chart.y2Axis.tickFormat(d3.format('.3p'));
}
} else 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(function (d, i) {
return px.color.category21(d[colorKey]);
});
d3.select(slice.selector).html('');
d3.select(slice.selector).append("svg")
.datum(payload.data)
.transition().duration(500)
.attr('height', height)
.call(chart);
return chart;
});
slice.done(payload);
})
.fail(function (xhr) {
slice.error(xhr.responseText);
});
};
var update = function () {
if (chart && chart.update) {
chart.update();
}
};
return {
render: render,
resize: update
};
}
module.exports = nvd3Vis;

View File

@@ -1,92 +0,0 @@
// JS
var $ = window.$ || require('jquery');
var d3 = window.d3 || require('d3');
d3.parcoords = require('../vendor/parallel_coordinates/d3.parcoords.js');
d3.divgrid = require('../vendor/parallel_coordinates/divgrid.js');
// CSS
require('../vendor/parallel_coordinates/d3.parcoords.css');
function parallelCoordVis(slice) {
function refresh() {
$('#code').attr('rows', '15');
$.getJSON(slice.jsonEndpoint(), function (payload) {
var data = payload.data;
var fd = payload.form_data;
var ext = d3.extent(data, function (d) {
return d[fd.secondary_metric];
});
ext = [ext[0], (ext[1] - ext[0]) / 2, ext[1]];
var cScale = d3.scale.linear()
.domain(ext)
.range(['red', 'grey', 'blue'])
.interpolate(d3.interpolateLab);
var color = function (d) {
return cScale(d[fd.secondary_metric]);
};
var container = d3.select(slice.selector);
var eff_height = fd.show_datatable ? (slice.height() / 2) : slice.height();
container.append('div')
.attr('id', 'parcoords_' + slice.container_id)
.style('height', eff_height + 'px')
.classed("parcoords", true);
var parcoords = d3.parcoords()('#parcoords_' + slice.container_id)
.width(slice.width())
.color(color)
.alpha(0.5)
.composite("darken")
.height(eff_height)
.data(payload.data)
.render()
.createAxes()
.shadows()
.reorderable()
.brushMode("1D-axes");
if (fd.show_datatable) {
// create data table, row hover highlighting
var grid = d3.divgrid();
container.append("div")
.datum(data.slice(0, 10))
.attr('id', "grid")
.call(grid)
.classed("parcoords", true)
.selectAll(".row")
.on({
mouseover: function (d) {
parcoords.highlight([d]);
},
mouseout: parcoords.unhighlight
});
// update data table on brush event
parcoords.on("brush", function (d) {
d3.select("#grid")
.datum(d.slice(0, 10))
.call(grid)
.selectAll(".row")
.on({
mouseover: function (d) {
parcoords.highlight([d]);
},
mouseout: parcoords.unhighlight
});
});
}
slice.done();
})
.fail(function (xhr) {
slice.error(xhr.responseText);
});
}
return {
render: refresh,
resize: refresh
};
}
module.exports = parallelCoordVis;

View File

@@ -1,32 +0,0 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
require('datatables.net-bs');
require('./pivot_table.css');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
module.exports = function (slice) {
var container = slice.container;
var form_data = slice.data.form_data;
function refresh() {
$.getJSON(slice.jsonEndpoint(), function (json) {
container.html(json.data);
if (form_data.groupby.length === 1) {
var table = container.find('table').DataTable({
paging: false,
searching: false,
bInfo: false
});
table.column('-1').order('desc').draw();
}
slice.done(json);
}).fail(function (xhr) {
slice.error(xhr.responseText);
});
}
return {
render: refresh,
resize: refresh
};
};

View File

@@ -1,177 +0,0 @@
// CSS
require('./sankey.css');
// JS
var px = window.px || require('../javascripts/modules/caravel.js');
var d3 = window.d3 || require('d3');
d3.sankey = require('d3-sankey').sankey;
function sankeyVis(slice) {
var div = d3.select(slice.selector);
var render = function () {
var margin = {
top: 5,
right: 5,
bottom: 5,
left: 5
};
var width = slice.width() - margin.left - margin.right;
var height = slice.height() - margin.top - margin.bottom;
var formatNumber = d3.format(",.2f");
div.selectAll("*").remove();
var 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 + ")");
var tooltip = div.append("div")
.attr("class", "sankey-tooltip")
.style("opacity", 0);
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText);
return '';
}
var links = json.data;
var 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);
});
nodes = d3.values(nodes);
sankey
.nodes(nodes)
.links(links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
})
.on("mouseover", onmouseover)
.on("mouseout", onmouseout);
var 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 = px.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");
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);
}
function getTooltipHtml(d) {
var html;
if (d.sourceLinks) { // is node
html = d.name + " Value: <span class='emph'>" + formatNumber(d.value) + "</span>";
} else {
var val = formatNumber(d.value);
var sourcePercent = d3.round((d.value / d.source.value) * 100, 1);
var 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(d) {
tooltip.transition()
.duration(100)
.style("opacity", 0);
}
slice.done(json);
});
};
return {
render: render,
resize: render
};
}
module.exports = sankeyVis;

View File

@@ -1,375 +0,0 @@
var d3 = window.d3 || require('d3');
var px = require('../javascripts/modules/caravel.js');
var wrapSvgText = require('../javascripts/modules/utils.js').wrapSvgText;
require('./sunburst.css');
// Modified from http://bl.ocks.org/kerryrodden/7090426
function sunburstVis(slice) {
var container = d3.select(slice.selector);
var render = function () {
// vars with shared scope within this function
var margin = { top: 10, right: 5, bottom: 10, left: 5 };
var containerWidth = slice.width();
var containerHeight = slice.height();
var breadcrumbHeight = containerHeight * 0.085;
var visWidth = containerWidth - margin.left - margin.right;
var visHeight = containerHeight - margin.top - margin.bottom - breadcrumbHeight;
var radius = Math.min(visWidth, visHeight) / 2;
var colorByCategory = true; // color by category if primary/secondary metrics match
var maxBreadcrumbs, breadcrumbDims, // set based on data
totalSize, // total size of all segments; set after loading the data.
colorScale,
breadcrumbs, vis, arcs, gMiddleText; // dom handles
// Helper + path gen functions
var partition = d3.layout.partition()
.size([2 * Math.PI, radius * radius])
.value(function (d) { return d.m1; });
var arc = d3.svg.arc()
.startAngle(function (d) {
return d.x;
})
.endAngle(function (d) {
return d.x + d.dx;
})
.innerRadius(function (d) {
return Math.sqrt(d.y);
})
.outerRadius(function (d) {
return Math.sqrt(d.y + d.dy);
});
var formatNum = d3.format(".3s");
var formatPerc = d3.format(".3p");
container.select("svg").remove();
var svg = container.append("svg:svg")
.attr("width", containerWidth)
.attr("height", containerHeight);
d3.json(slice.jsonEndpoint(), function (error, rawData) {
if (error !== null) {
slice.error(error.responseText);
return '';
}
createBreadcrumbs(rawData);
createVisualization(rawData);
slice.done(rawData);
});
function createBreadcrumbs(rawData) {
var firstRowData = rawData.data[0];
maxBreadcrumbs = (firstRowData.length - 2) + 1; // -2 bc row contains 2x metrics, +extra for %label and buffer
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");
}
// Main function to draw and set up the visualization, once we have the data.
function createVisualization(rawData) {
var 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.
var nodes = partition.nodes(tree)
.filter(function (d) {
return (d.dx > 0.005); // 0.005 radians = 0.29 degrees
});
var ext;
if (rawData.form_data.metric !== rawData.form_data.secondary_metric) {
colorByCategory = false;
ext = d3.extent(nodes, function (d) {
return d.m2 / d.m1;
});
colorScale = d3.scale.linear()
.domain([ext[0], ext[0] + ((ext[1] - ext[0]) / 2), ext[1]])
.range(["#00D1C1", "white", "#FFB400"]);
}
var 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", function (d) {
return colorByCategory ? px.color.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;
}
// Fade all but the current sequence, and show it in the breadcrumb trail.
function mouseenter(d) {
var sequenceArray = getAncestors(d);
var parentOfD = sequenceArray[sequenceArray.length - 2] || null;
var absolutePercentage = (d.m1 / totalSize).toPrecision(3);
var conditionalPercentage = parentOfD ? (d.m1 / parentOfD.m1).toPrecision(3) : null;
var absolutePercString = formatPerc(absolutePercentage);
var conditionalPercString = parentOfD ? formatPerc(conditionalPercentage) : "";
var yOffsets = ["-25", "7", "35", "60"]; // 3 levels of text if inner-most level, 4 otherwise
var offsetIndex = 0;
// If metrics match, assume we are coloring by category
var 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(d) {
// Hide the breadcrumb trail
breadcrumbs.style("visibility", "hidden");
gMiddleText.selectAll("*").remove();
// Deactivate all segments during transition.
arcs.selectAll("path").on("mouseenter", null);
//gMiddleText.selectAll("*").remove();
// 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);
});
}
// 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) {
var path = [];
var 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) {
var 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) {
var g = breadcrumbs.selectAll("g")
.data(sequenceArray, function (d) {
return d.name + d.depth;
});
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", function (d) {
return colorByCategory ? px.color.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")
.attr("class", "step-label")
.text(function (d) { return d.name; })
.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);
}
function buildHierarchy(rows) {
var root = {
name: "root",
children: []
};
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var m1 = Number(row[row.length - 2]);
var m2 = Number(row[row.length - 1]);
var levels = row.slice(0, row.length - 2);
if (isNaN(m1)) { // e.g. if this is a header row
continue;
}
var currentNode = root;
for (var j = 0; j < levels.length; j++) {
var children = currentNode.children || [];
var nodeName = levels[j];
// If the next node has the name "0", it will
var isLeafNode = (j >= levels.length - 1) || levels[j+1] === 0;
var childNode;
if (!isLeafNode) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k].name === nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {
name: nodeName,
children: []
};
children.push(childNode);
}
currentNode = childNode;
} else if (nodeName !== 0) {
// Reached the end of the sequence; create a leaf node.
childNode = {
name: nodeName,
m1: m1,
m2: m2
};
children.push(childNode);
}
}
}
function recurse(node) {
if (node.children) {
var sums;
var m1 = 0;
var m2 = 0;
for (var 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;
}
};
return {
render: render,
resize: render
};
}
module.exports = sunburstVis;

View File

@@ -1,126 +0,0 @@
var $ = window.$ = require('jquery');
var jQuery = window.jQuery = $;
var d3 = require('d3');
require('./table.css');
require('datatables.net-bs');
require('../node_modules/datatables-bootstrap3-plugin/media/css/datatables-bootstrap3.css');
function tableVis(slice) {
var data = slice.data;
var form_data = data.form_data;
var f = d3.format('.3s');
var fC = d3.format('0,000');
function refresh() {
$.getJSON(slice.jsonEndpoint(), onSuccess).fail(onError);
function onError(xhr) {
slice.error(xhr.responseText);
}
function onSuccess(json) {
var data = json.data;
var metrics = json.form_data.metrics;
function col(c) {
var arr = [];
for (var i = 0; i < data.records.length; i++) {
arr.push(json.data.records[i][c]);
}
return arr;
}
var maxes = {};
for (var i = 0; i < metrics.length; i++) {
maxes[metrics[i]] = d3.max(col(metrics[i]));
}
var table = d3.select(slice.selector).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(function (row, i) {
return data.columns.map(function (c) {
return {
col: c,
val: row[c],
isMetric: metrics.indexOf(c) >= 0
};
});
}).enter()
.append('td')
.style('background-image', function (d) {
if (d.isMetric) {
var perc = Math.round((d.val / maxes[d.col]) * 100);
return "linear-gradient(to right, lightgrey, lightgrey " + perc + "%, rgba(0,0,0,0) " + perc + "%";
}
})
.attr('title', function (d) {
if (!isNaN(d.val)) {
return fC(d.val);
}
})
.attr('data-sort', function (d) {
if (d.isMetric) {
return d.val;
}
})
.on("click", function (d) {
if (!d.isMetric) {
var 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) {
if (!d.isMetric) {
return 'pointer';
}
})
.html(function (d) {
if (d.isMetric) {
return f(d.val);
} else {
return d.val;
}
});
var datatable = slice.container.find('.dataTable').DataTable({
paging: false,
searching: form_data.include_search,
bInfo: false
});
// Sorting table by main column
if (form_data.metrics.length > 0) {
var main_metric = form_data.metrics[0];
datatable.column(data.columns.indexOf(main_metric)).order('desc').draw();
}
slice.done(json);
slice.container.parents('.widget').find('.tooltip').remove();
}
}
return {
render: refresh,
resize: function () {}
};
}
module.exports = tableVis;

View File

@@ -1,15 +0,0 @@
.node {
border: solid 1px white;
font: 10px sans-serif;
line-height: 12px;
overflow: hidden;
position: absolute;
text-indent: 2px;
padding: 0px; /* form div giving top 1px */
box-sizing: content-box; /* otherwise inheriting border-box */
}
.treemap-container {
position: relative;
margin: auto;
}

View File

@@ -1,104 +0,0 @@
// JS
var d3 = window.d3 || require('d3');
var px = window.px || require('../javascripts/modules/caravel.js');
// CSS
require('./treemap.css');
/* Modified from https://bl.ocks.org/mbostock/4063582 */
function treemap(slice) {
var div = d3.select(slice.selector);
var _draw = function (data, eltWidth, eltHeight, includeTitle) {
var margin = { top: 0, right: 0, bottom: 0, left: 0 },
headerHeight = includeTitle ? 30 : 0,
width = eltWidth - margin.left - margin.right,
height = eltHeight - headerHeight - margin.top - margin.bottom;
var treemap = d3.layout.treemap()
.size([width, height])
.value(function (d) { return d.value; });
var root = div.append("div")
.classed("treemap-container", true);
var header = root.append("div")
.style("width", (width + margin.left + margin.right) + "px")
.style("height", headerHeight + "px");
var container = root.append("div")
.style("position", "relative")
.style("width", (width + margin.left + margin.right) + "px")
.style("height", (height + margin.top + margin.bottom) + "px")
.style("left", margin.left + "px")
.style("top", margin.top + "px");
var position = function (selection) {
selection.style("left", function (d) { return d.x + "px"; })
.style("top", function (d) { return d.y + "px"; })
.style("width", function (d) { return Math.max(0, d.dx - 1) + "px"; })
.style("height", function (d) { return Math.max(0, d.dy - 1) + "px"; });
};
container.datum(data).selectAll(".node")
.data(treemap.nodes)
.enter().append("div")
.attr("class", "node")
.call(position)
.style("background", function (d) {
return d.children ? px.color.category21(d.name) : null;
})
.style("color", function (d) {
// detect if our background is dark and we need a
// light text color or vice-versa
var bg = d.parent ? px.color.category21(d.parent.name) : null;
if (bg) {
return d3.hsl(bg).l < 0.35 ? '#d3d3d3' : '#111111';
}
})
.text(function (d) { return d.children ? null : d.name; });
if (includeTitle) {
// title to help with multiple metrics (if any)
header.append("span")
.style("font-size", "18px")
.style("font-weight", "bold")
.text(data.name);
}
};
var render = function () {
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText);
return '';
}
div.selectAll("*").remove();
var width = slice.width();
// facet muliple metrics (no sense in combining)
var height = slice.height() / json.data.length;
var includeTitles = json.data.length > 1;
for (var i = 0, l = json.data.length; i < l; i ++) {
_draw(json.data[i], width, height, includeTitles);
}
slice.done(json);
});
};
return {
render: render,
resize: render
};
}
module.exports = treemap;

View File

@@ -1,91 +0,0 @@
var px = window.px || require('../javascripts/modules/caravel.js');
var d3 = window.d3 || require('d3');
var cloudLayout = require('d3-cloud');
function wordCloudChart(slice) {
var chart = d3.select(slice.selector);
function refresh() {
d3.json(slice.jsonEndpoint(), function (error, json) {
if (error !== null) {
slice.error(error.responseText);
return '';
}
var data = json.data;
var range = [
json.form_data.size_from,
json.form_data.size_to
];
var rotation = json.form_data.rotation;
var f_rotation;
if (rotation === "square") {
f_rotation = function () {
return ~~(Math.random() * 2) * 90;
};
} else if (rotation === "flat") {
f_rotation = function () {
return 0;
};
} else {
f_rotation = function () {
return (~~(Math.random() * 6) - 3) * 30;
};
}
var size = [slice.width(), slice.height()];
var scale = d3.scale.linear()
.range(range)
.domain(d3.extent(data, function (d) {
return d.size;
}));
var layout = cloudLayout()
.size(size)
.words(data)
.padding(5)
.rotate(f_rotation)
.font("serif")
.fontSize(function (d) {
return scale(d.size);
})
.on("end", draw);
layout.start();
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", function (d) {
return d.size + "px";
})
.style("font-family", "Impact")
.style("fill", function (d) {
return px.color.category21(d.text);
})
.attr("text-anchor", "middle")
.attr("transform", function (d) {
return "translate(" + [d.x, d.y] + ") rotate(" + d.rotate + ")";
})
.text(function (d) {
return d.text;
});
}
slice.done(json);
});
}
return {
render: refresh,
resize: refresh
};
}
module.exports = wordCloudChart;

View File

@@ -1,110 +0,0 @@
// JS
var d3 = window.d3 || require('d3');
//var Datamap = require('../vendor/datamaps/datamaps.all.js');
var Datamap = require('datamaps');
// CSS
require('./world_map.css');
function worldMapChart(slice) {
var render = function () {
var container = slice.container;
var div = d3.select(slice.selector);
container.css('height', slice.height());
d3.json(slice.jsonEndpoint(), function (error, json) {
var fd = json.form_data;
if (error !== null) {
slice.error(error.responseText);
return '';
}
var ext = d3.extent(json.data, function (d) {
return d.m1;
});
var extRadius = d3.extent(json.data, function (d) {
return d.m2;
});
var radiusScale = d3.scale.linear()
.domain([extRadius[0], extRadius[1]])
.range([1, fd.max_bubble_size]);
json.data.forEach(function (d) {
d.radius = radiusScale(d.m2);
});
var colorScale = d3.scale.linear()
.domain([ext[0], ext[1]])
.range(["#FFF", "black"]);
var d = {};
for (var i = 0; i < json.data.length; i++) {
var country = json.data[i];
country.fillColor = colorScale(country.m1);
d[country.country] = country;
}
var f = d3.format('.3s');
container.show();
var map = new Datamap({
element: slice.container.get(0),
data: json.data,
fills: {
defaultFill: '#ddd'
},
geographyConfig: {
popupOnHover: true,
highlightOnHover: true,
borderWidth: 1,
borderColor: '#fff',
highlightBorderColor: '#fff',
highlightFillColor: '#005a63',
highlightBorderWidth: 1,
popupTemplate: function (geo, data) {
return '<div class="hoverinfo"><strong>' + data.name + '</strong><br>' + f(data.m1) + '</div>';
}
},
bubblesConfig: {
borderWidth: 1,
borderOpacity: 1,
borderColor: '#005a63',
popupOnHover: true,
radius: null,
popupTemplate: function (geo, data) {
return '<div class="hoverinfo"><strong>' + data.name + '</strong><br>' + f(data.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(d);
if (fd.show_bubbles) {
map.bubbles(json.data);
div.selectAll("circle.datamaps-bubble").style('fill', '#005a63');
}
slice.done(json);
});
};
return {
render: render,
resize: render
};
}
module.exports = worldMapChart;

View File

@@ -1,51 +0,0 @@
var path = require('path');
var APP_DIR = path.resolve(__dirname, './'); // input
var BUILD_DIR = path.resolve(__dirname, './javascripts/dist'); // output
var config = {
// for now generate one compiled js file per entry point / html page
entry: {
'css-theme': APP_DIR + '/javascripts/css-theme.js',
dashboard: APP_DIR + '/javascripts/dashboard.js',
explore: APP_DIR + '/javascripts/explore.js',
welcome: APP_DIR + '/javascripts/welcome.js',
sql: APP_DIR + '/javascripts/sql.js',
standalone: APP_DIR + '/javascripts/standalone.js'
},
output: {
path: BUILD_DIR,
filename: '[name].entry.js'
},
module: {
loaders: [
{
test: /\.jsx?/,
include: APP_DIR,
exclude: APP_DIR + '/node_modules',
loader: 'babel'
},
/* for require('*.css') */
{
test: /\.css$/,
include: APP_DIR,
loader: "style-loader!css-loader"
},
/* for css linking images */
{ test: /\.png$/, loader: "url-loader?limit=100000" },
{ test: /\.jpg$/, loader: "file-loader" },
{ test: /\.gif$/, loader: "file-loader" },
/* for font-awesome */
{ test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&minetype=application/font-woff" },
{ test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" },
/* for require('*.less') */
{
test: /\.less$/,
include: APP_DIR,
loader: "style!css!less"
}
]
},
plugins: []
};
module.exports = config;

View File

@@ -1,109 +0,0 @@
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import logging
from datetime import datetime
from subprocess import Popen
import textwrap
from flask.ext.migrate import MigrateCommand
from flask.ext.script import Manager
import caravel
from caravel import app, ascii_art, db, data, utils
config = app.config
manager = Manager(app)
manager.add_command('db', MigrateCommand)
@manager.option(
'-d', '--debug', action='store_true',
help="Start the web server in debug mode")
@manager.option(
'-p', '--port', default=config.get("CARAVEL_WEBSERVER_PORT"),
help="Specify the port on which to run the web server")
@manager.option(
'-w', '--workers', default=config.get("CARAVEL_WORKERS", 16),
help="Number of gunicorn web server workers to fire up")
@manager.option(
'-t', '--timeout', default=config.get("CARAVEL_WEBSERVER_TIMEOUT"),
help="Specify the timeout (seconds) for the gunicorn web server")
def runserver(debug, port, timeout, workers):
"""Starts a Caravel web server"""
debug = debug or config.get("DEBUG")
if debug:
app.run(
host='0.0.0.0',
port=int(port),
debug=True)
else:
cmd = (
"gunicorn "
"-w {workers} "
"--timeout {timeout} "
"-b 0.0.0.0:{port} "
"caravel:app").format(**locals())
print("Starting server with command: " + cmd)
Popen(cmd, shell=True).wait()
@manager.command
def init():
"""Inits the Caravel application"""
utils.init(caravel)
@manager.command
def version():
"""Prints the current version number"""
s = (
"\n{boat}\n\n"
"-----------------------\n"
"Caravel {version}\n"
"-----------------------\n").format(
boat=ascii_art.boat, version=caravel.VERSION)
print(s)
@manager.option(
'-s', '--sample', action='store_true',
help="Only load 1000 rows (faster, used for testing)")
def load_examples(sample):
"""Loads a set of Slices and Dashboards and a supporting dataset """
print("Loading examples into {}".format(db))
data.load_css_templates()
print("Loading energy related dataset")
data.load_energy()
print("Loading [World Bank's Health Nutrition and Population Stats]")
data.load_world_bank_health_n_pop()
print("Loading [Birth names]")
data.load_birth_names()
@manager.command
def refresh_druid():
"""Refresh all druid datasources"""
session = db.session()
from caravel import models
for cluster in session.query(models.DruidCluster).all():
try:
cluster.refresh_datasources()
except Exception as e:
print(
"Error while processing cluster '{}'\n{}".format(
cluster, str(e)))
logging.exception(e)
cluster.metadata_last_refreshed = datetime.now()
print(
"Refreshed metadata from cluster "
"[" + cluster.cluster_name + "]")
session.commit()
if __name__ == "__main__":
manager.run()

View File

@@ -1,127 +0,0 @@
"""The main config file for Caravel
All configuration in this file can be overridden by providing a local_config
in your PYTHONPATH as there is a ``from local_config import *``
at the end of this file.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os
from dateutil import tz
from flask_appbuilder.security.manager import AUTH_DB
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
# ---------------------------------------------------------
# Caravel specifix config
# ---------------------------------------------------------
ROW_LIMIT = 50000
WEBSERVER_THREADS = 8
CARAVEL_WEBSERVER_PORT = 8088
CARAVEL_WEBSERVER_TIMEOUT = 60
CUSTOM_SECURITY_MANAGER = None
# ---------------------------------------------------------
# Your App secret key
SECRET_KEY = '\2\1thisismyscretkey\1\2\e\y\y\h' # noqa
# The SQLAlchemy connection string.
SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/caravel.db'
# SQLALCHEMY_DATABASE_URI = 'mysql://myapp@localhost/myapp'
# SQLALCHEMY_DATABASE_URI = 'postgresql://root:password@localhost/myapp'
# Flask-WTF flag for CSRF
CSRF_ENABLED = True
# Whether to run the web server in debug mode or not
DEBUG = False
# Whether to show the stacktrace on 500 error
SHOW_STACKTRACE = True
# ------------------------------
# GLOBALS FOR APP Builder
# ------------------------------
# Uncomment to setup Your App name
APP_NAME = "Caravel"
# Uncomment to setup Setup an App icon
# APP_ICON = "/static/img/something.png"
# Druid query timezone
# tz.tzutc() : Using utc timezone
# tz.tzlocal() : Using local timezone
# other tz can be overridden by providing a local_config
DRUID_IS_ACTIVE = True
DRUID_TZ = tz.tzutc()
# ----------------------------------------------------
# AUTHENTICATION CONFIG
# ----------------------------------------------------
# The authentication type
# AUTH_OID : Is for OpenID
# AUTH_DB : Is for database (username/password()
# AUTH_LDAP : Is for LDAP
# AUTH_REMOTE_USER : Is for using REMOTE_USER from web server
AUTH_TYPE = AUTH_DB
# Uncomment to setup Full admin role name
# AUTH_ROLE_ADMIN = 'Admin'
# Uncomment to setup Public role name, no authentication needed
# AUTH_ROLE_PUBLIC = 'Public'
# Will allow user self registration
# AUTH_USER_REGISTRATION = True
# The default user self registration role
# AUTH_USER_REGISTRATION_ROLE = "Public"
# When using LDAP Auth, setup the ldap server
# AUTH_LDAP_SERVER = "ldap://ldapserver.new"
# Uncomment to setup OpenID providers example for OpenID authentication
# OPENID_PROVIDERS = [
# { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' },
# { 'name': 'AOL', 'url': 'http://openid.aol.com/<username>' },
# { 'name': 'Flickr', 'url': 'http://www.flickr.com/<username>' },
# { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]
# ---------------------------------------------------
# Babel config for translations
# ---------------------------------------------------
# Setup default language
BABEL_DEFAULT_LOCALE = 'en'
# Your application default translation path
BABEL_DEFAULT_FOLDER = 'translations'
# The allowed translation for you app
LANGUAGES = {
'en': {'flag': 'us', 'name': 'English'},
}
# ---------------------------------------------------
# Image and file configuration
# ---------------------------------------------------
# The file upload folder, when using models with files
UPLOAD_FOLDER = BASE_DIR + '/app/static/uploads/'
# The image upload folder, when using models with images
IMG_UPLOAD_FOLDER = BASE_DIR + '/app/static/uploads/'
# The image upload url, when using models with images
IMG_UPLOAD_URL = '/static/uploads/'
# Setup image size default is (300, 200, True)
# IMG_SIZE = (300, 200, True)
CACHE_DEFAULT_TIMEOUT = None
CACHE_CONFIG = {'CACHE_TYPE': 'null'}
try:
from caravel_config import * # noqa
except Exception:
pass

View File

@@ -1,730 +0,0 @@
"""Loads datasets, dashboards and slices in a new caravel instance"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import gzip
import json
import os
import textwrap
import pandas as pd
from sqlalchemy import String, DateTime, Float
from caravel import app, db, models, utils
# Shortcuts
DB = models.Database
Slice = models.Slice
TBL = models.SqlaTable
Dash = models.Dashboard
config = app.config
DATA_FOLDER = os.path.join(config.get("BASE_DIR"), 'data')
def get_or_create_db(session):
print("Creating database reference")
dbobj = session.query(DB).filter_by(database_name='main').first()
if not dbobj:
dbobj = DB(database_name="main")
print(config.get("SQLALCHEMY_DATABASE_URI"))
dbobj.sqlalchemy_uri = config.get("SQLALCHEMY_DATABASE_URI")
session.add(dbobj)
session.commit()
return dbobj
def merge_slice(slc):
o = db.session.query(Slice).filter_by(slice_name=slc.slice_name).first()
if o:
db.session.delete(o)
db.session.add(slc)
db.session.commit()
def get_slice_json(defaults, **kwargs):
d = defaults.copy()
d.update(kwargs)
return json.dumps(d, indent=4, sort_keys=True)
def load_energy():
"""Loads an energy related dataset to use with sankey and graphs"""
tbl_name = 'energy_usage'
with gzip.open(os.path.join(DATA_FOLDER, 'energy.json.gz')) as f:
pdf = pd.read_json(f)
pdf.to_sql(
tbl_name,
db.engine,
if_exists='replace',
chunksize=500,
dtype={
'source': String(255),
'target': String(255),
'value': Float(),
},
index=False)
print("Creating table [wb_health_population] reference")
tbl = db.session.query(TBL).filter_by(table_name=tbl_name).first()
if not tbl:
tbl = TBL(table_name=tbl_name)
tbl.description = "Energy consumption"
tbl.is_featured = True
tbl.database = get_or_create_db(db.session)
db.session.merge(tbl)
db.session.commit()
tbl.fetch_metadata()
merge_slice(
Slice(
slice_name="Energy Sankey",
viz_type='sankey',
datasource_type='table',
table=tbl,
params=textwrap.dedent("""\
{
"collapsed_fieldsets": "",
"datasource_id": "3",
"datasource_name": "energy_usage",
"datasource_type": "table",
"flt_col_0": "source",
"flt_eq_0": "",
"flt_op_0": "in",
"groupby": [
"source",
"target"
],
"having": "",
"metric": "sum__value",
"row_limit": "5000",
"slice_id": "",
"slice_name": "Energy Sankey",
"viz_type": "sankey",
"where": ""
}
"""))
)
def load_world_bank_health_n_pop():
"""Loads the world bank health dataset, slices and a dashboard"""
tbl_name = 'wb_health_population'
with gzip.open(os.path.join(DATA_FOLDER, 'countries.json.gz')) as f:
pdf = pd.read_json(f)
pdf.columns = [col.replace('.', '_') for col in pdf.columns]
pdf.year = pd.to_datetime(pdf.year)
pdf.to_sql(
tbl_name,
db.engine,
if_exists='replace',
chunksize=500,
dtype={
'year': DateTime(),
'country_code': String(3),
'country_name': String(255),
'region': String(255),
},
index=False)
print("Creating table [wb_health_population] reference")
tbl = db.session.query(TBL).filter_by(table_name=tbl_name).first()
if not tbl:
tbl = TBL(table_name=tbl_name)
tbl.description = utils.readfile(os.path.join(DATA_FOLDER, 'countries.md'))
tbl.main_dttm_col = 'year'
tbl.is_featured = True
tbl.database = get_or_create_db(db.session)
db.session.merge(tbl)
db.session.commit()
tbl.fetch_metadata()
defaults = {
"compare_lag": "10",
"compare_suffix": "o10Y",
"datasource_id": "1",
"datasource_name": "birth_names",
"datasource_type": "table",
"limit": "25",
"granularity": "year",
"groupby": [],
"metric": 'sum__SP_POP_TOTL',
"metrics": ["sum__SP_POP_TOTL"],
"row_limit": config.get("ROW_LIMIT"),
"since": "2014-01-01",
"until": "2014-01-01",
"where": "",
"markup_type": "markdown",
"country_fieldtype": "cca3",
"secondary_metric": "sum__SP_POP_TOTL",
"entity": "country_code",
"show_bubbles": "y",
}
print("Creating slices")
slices = [
Slice(
slice_name="Region Filter",
viz_type='filter_box',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type='filter_box',
groupby=['region', 'country_name'])),
Slice(
slice_name="World's Population",
viz_type='big_number',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
since='2000',
viz_type='big_number',
compare_lag="10",
metric='sum__SP_POP_TOTL',
compare_suffix="over 10Y")),
Slice(
slice_name="Most Populated Countries",
viz_type='table',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type='table',
metrics=["sum__SP_POP_TOTL"],
groupby=['country_name'])),
Slice(
slice_name="Growth Rate",
viz_type='line',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type='line',
since="1960-01-01",
metrics=["sum__SP_POP_TOTL"],
num_period_compare="10",
groupby=['country_name'])),
Slice(
slice_name="% Rural",
viz_type='world_map',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type='world_map',
metric="sum__SP_RUR_TOTL_ZS",
num_period_compare="10")),
Slice(
slice_name="Life Expexctancy VS Rural %",
viz_type='bubble',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type='bubble',
since="2011-01-01",
until="2011-01-01",
series="region",
limit="0",
entity="country_name",
x="sum__SP_RUR_TOTL_ZS",
y="sum__SP_DYN_LE00_IN",
size="sum__SP_POP_TOTL",
max_bubble_size="50",
flt_col_1="country_code",
flt_op_1="not in",
flt_eq_1="TCA,MNP,DMA,MHL,MCO,SXM,CYM,TUV,IMY,KNA,ASM,ADO,AMA,PLW",
num_period_compare="10",)),
Slice(
slice_name="Rural Breakdown",
viz_type='sunburst',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type='sunburst',
groupby=["region", "country_name"],
secondary_metric="sum__SP_RUR_TOTL",
since="2011-01-01",
until="2011-01-01",)),
Slice(
slice_name="World's Pop Growth",
viz_type='area',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
since="1960-01-01",
until="now",
viz_type='area',
groupby=["region"],)),
Slice(
slice_name="Box plot",
viz_type='box_plot',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
since="1960-01-01",
until="now",
whisker_options="Tukey",
viz_type='box_plot',
groupby=["region"],)),
Slice(
slice_name="Treemap",
viz_type='treemap',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
since="1960-01-01",
until="now",
viz_type='treemap',
metrics=["sum__SP_POP_TOTL"],
groupby=["region", "country_code"],)),
]
for slc in slices:
merge_slice(slc)
print("Creating a World's Health Bank dashboard")
dash_name = "World's Health Bank Dashboard"
dash = db.session.query(Dash).filter_by(dashboard_title=dash_name).first()
if not dash:
dash = Dash()
js = textwrap.dedent("""\
[
{
"size_y": 2,
"size_x": 3,
"col": 10,
"slice_id": "22",
"row": 1
},
{
"size_y": 3,
"size_x": 3,
"col": 10,
"slice_id": "23",
"row": 3
},
{
"size_y": 8,
"size_x": 3,
"col": 1,
"slice_id": "24",
"row": 1
},
{
"size_y": 3,
"size_x": 6,
"col": 4,
"slice_id": "25",
"row": 6
},
{
"size_y": 5,
"size_x": 6,
"col": 4,
"slice_id": "26",
"row": 1
},
{
"size_y": 4,
"size_x": 6,
"col": 7,
"slice_id": "27",
"row": 9
},
{
"size_y": 3,
"size_x": 3,
"col": 10,
"slice_id": "28",
"row": 6
},
{
"size_y": 4,
"size_x": 6,
"col": 1,
"slice_id": "29",
"row": 9
},
{
"size_y": 4,
"size_x": 5,
"col": 8,
"slice_id": "30",
"row": 13
},
{
"size_y": 4,
"size_x": 7,
"col": 1,
"slice_id": "31",
"row": 13
}
]
""")
l = json.loads(js)
for i, pos in enumerate(l):
pos['slice_id'] = str(slices[i].id)
dash.dashboard_title = dash_name
dash.position_json = json.dumps(l, indent=4)
dash.slug = "world_health"
dash.slices = slices
db.session.merge(dash)
db.session.commit()
def load_css_templates():
"""Loads 2 css templates to demonstrate the feature"""
print('Creating default CSS templates')
CSS = models.CssTemplate # noqa
obj = db.session.query(CSS).filter_by(template_name='Flat').first()
if not obj:
obj = CSS(template_name="Flat")
css = textwrap.dedent("""\
.gridster li.widget {
transition: background-color 0.5s ease;
background-color: #FAFAFA;
border: 1px solid #CCC;
box-shadow: none;
border-radius: 0px;
}
.gridster li.widget:hover {
border: 1px solid #000;
background-color: #EAEAEA;
}
.navbar {
transition: opacity 0.5s ease;
opacity: 0.05;
}
.navbar:hover {
opacity: 1;
}
.chart-header .header{
font-weight: normal;
font-size: 12px;
}
/*
var bnbColors = [
//rausch hackb kazan babu lima beach tirol
'#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c',
'#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a',
'#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e',
];
*/
""")
obj.css = css
db.session.merge(obj)
db.session.commit()
obj = (
db.session.query(CSS).filter_by(template_name='Courier Black').first())
if not obj:
obj = CSS(template_name="Courier Black")
css = textwrap.dedent("""\
.gridster li.widget {
transition: background-color 0.5s ease;
background-color: #EEE;
border: 2px solid #444;
border-radius: 15px;
box-shadow: none;
}
h2 {
color: white;
font-size: 52px;
}
.navbar {
box-shadow: none;
}
.gridster li.widget:hover {
border: 2px solid #000;
background-color: #EAEAEA;
}
.navbar {
transition: opacity 0.5s ease;
opacity: 0.05;
}
.navbar:hover {
opacity: 1;
}
.chart-header .header{
font-weight: normal;
font-size: 12px;
}
.nvd3 text {
font-size: 12px;
font-family: inherit;
}
body{
background: #000;
font-family: Courier, Monaco, monospace;;
}
/*
var bnbColors = [
//rausch hackb kazan babu lima beach tirol
'#ff5a5f', '#7b0051', '#007A87', '#00d1c1', '#8ce071', '#ffb400', '#b4a76c',
'#ff8083', '#cc0086', '#00a1b3', '#00ffeb', '#bbedab', '#ffd266', '#cbc29a',
'#ff3339', '#ff1ab1', '#005c66', '#00b3a5', '#55d12e', '#b37e00', '#988b4e',
];
*/
""")
obj.css = css
db.session.merge(obj)
db.session.commit()
def load_birth_names():
"""Loading birth name dataset from a zip file in the repo"""
with gzip.open(os.path.join(DATA_FOLDER, 'birth_names.json.gz')) as f:
pdf = pd.read_json(f)
pdf.ds = pd.to_datetime(pdf.ds, unit='ms')
pdf.to_sql(
'birth_names',
db.engine,
if_exists='replace',
chunksize=500,
dtype={
'ds': DateTime,
'gender': String(16),
'state': String(10),
'name': String(255),
},
index=False)
l = []
print("Done loading table!")
print("-" * 80)
print("Creating table reference")
obj = db.session.query(TBL).filter_by(table_name='birth_names').first()
if not obj:
obj = TBL(table_name='birth_names')
obj.main_dttm_col = 'ds'
obj.database = get_or_create_db(db.session)
obj.is_featured = True
db.session.merge(obj)
db.session.commit()
obj.fetch_metadata()
tbl = obj
defaults = {
"compare_lag": "10",
"compare_suffix": "o10Y",
"datasource_id": "1",
"datasource_name": "birth_names",
"datasource_type": "table",
"flt_op_1": "in",
"limit": "25",
"granularity": "ds",
"groupby": [],
"metric": 'sum__num',
"metrics": ["sum__num"],
"row_limit": config.get("ROW_LIMIT"),
"since": "100 years ago",
"until": "now",
"viz_type": "table",
"where": "",
"markup_type": "markdown",
}
print("Creating some slices")
slices = [
Slice(
slice_name="Girls",
viz_type='table',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
groupby=['name'],
flt_col_1='gender',
flt_eq_1="girl", row_limit=50)),
Slice(
slice_name="Boys",
viz_type='table',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
groupby=['name'],
flt_col_1='gender',
flt_eq_1="boy",
row_limit=50)),
Slice(
slice_name="Participants",
viz_type='big_number',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type="big_number", granularity="ds",
compare_lag="5", compare_suffix="over 5Y")),
Slice(
slice_name="Number of Girls",
viz_type='big_number_total',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type="big_number_total", granularity="ds",
flt_col_1='gender', flt_eq_1='girl',
subheader='total female participants')),
Slice(
slice_name="Genders",
viz_type='pie',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type="pie", groupby=['gender'])),
Slice(
slice_name="Genders by State",
viz_type='dist_bar',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
flt_eq_1="other", viz_type="dist_bar",
metrics=['sum__sum_girls', 'sum__sum_boys'],
groupby=['state'], flt_op_1='not in', flt_col_1='state')),
Slice(
slice_name="Trends",
viz_type='line',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type="line", groupby=['name'],
granularity='ds', rich_tooltip='y', show_legend='y')),
Slice(
slice_name="Title",
viz_type='markup',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type="markup", markup_type="html",
code="""\
<div style="text-align:center">
<h1>Birth Names Dashboard</h1>
<p>
The source dataset came from
<a href="https://github.com/hadley/babynames">[here]</a>
</p>
<img src="http://monblog.system-linux.net/image/tux/baby-tux_overlord59-tux.png">
</div>
""")),
Slice(
slice_name="Name Cloud",
viz_type='word_cloud',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type="word_cloud", size_from="10",
series='name', size_to="70", rotation="square",
limit='100')),
Slice(
slice_name="Pivot Table",
viz_type='pivot_table',
datasource_type='table',
table=tbl,
params=get_slice_json(
defaults,
viz_type="pivot_table", metrics=['sum__num'],
groupby=['name'], columns=['state'])),
]
for slc in slices:
merge_slice(slc)
print("Creating a dashboard")
dash = db.session.query(Dash).filter_by(dashboard_title="Births").first()
if not dash:
dash = Dash()
js = textwrap.dedent("""\
[
{
"size_y": 4,
"size_x": 2,
"col": 8,
"slice_id": "85",
"row": 7
},
{
"size_y": 4,
"size_x": 2,
"col": 10,
"slice_id": "86",
"row": 7
},
{
"size_y": 2,
"size_x": 2,
"col": 1,
"slice_id": "87",
"row": 1
},
{
"size_y": 2,
"size_x": 2,
"col": 3,
"slice_id": "88",
"row": 1
},
{
"size_y": 3,
"size_x": 7,
"col": 5,
"slice_id": "89",
"row": 4
},
{
"size_y": 4,
"size_x": 7,
"col": 1,
"slice_id": "90",
"row": 7
},
{
"size_y": 3,
"size_x": 3,
"col": 9,
"slice_id": "91",
"row": 1
},
{
"size_y": 3,
"size_x": 4,
"col": 5,
"slice_id": "92",
"row": 1
},
{
"size_y": 4,
"size_x": 4,
"col": 1,
"slice_id": "93",
"row": 3
}
]
""")
l = json.loads(js)
for i, pos in enumerate(l):
pos['slice_id'] = str(slices[i].id)
dash.dashboard_title = "Births"
dash.position_json = json.dumps(l, indent=4)
dash.slug = "births"
dash.slices = slices
db.session.merge(dash)
db.session.commit()

Binary file not shown.

View File

@@ -1,631 +0,0 @@
"""Contains the logic to create cohesive forms on the explore view"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from collections import OrderedDict
from copy import copy
from wtforms import (
Form, SelectMultipleField, SelectField, TextField, TextAreaField,
BooleanField, IntegerField, HiddenField)
from wtforms import validators, widgets
from caravel import app
config = app.config
class BetterBooleanField(BooleanField):
"""Fixes the html checkbox to distinguish absent from unchecked
(which doesn't distinguish False from NULL/missing )
If value is unchecked, this hidden <input> fills in False value
"""
def __call__(self, **kwargs):
html = super(BetterBooleanField, self).__call__(**kwargs)
html += u'<input type="hidden" name="{}" value="false">'.format(self.name)
return widgets.HTMLString(html)
class SelectMultipleSortableField(SelectMultipleField):
"""Works along with select2sortable to preserves the sort order"""
def iter_choices(self):
d = OrderedDict()
for value, label in self.choices:
selected = self.data is not None and self.coerce(value) in self.data
d[value] = (value, label, selected)
if self.data:
for value in self.data:
if value:
yield d.pop(value)
while d:
yield d.popitem(last=False)[1]
class FreeFormSelect(widgets.Select):
"""A WTF widget that allows for free form entry"""
def __call__(self, field, **kwargs):
kwargs.setdefault('id', field.id)
if self.multiple:
kwargs['multiple'] = True
html = ['<select %s>' % widgets.html_params(name=field.name, **kwargs)]
found = False
for val, label, selected in field.iter_choices():
html.append(self.render_option(val, label, selected))
if field.data and val == field.data:
found = True
if not found:
html.insert(1, self.render_option(field.data, field.data, True))
html.append('</select>')
return widgets.HTMLString(''.join(html))
class FreeFormSelectField(SelectField):
"""A WTF SelectField that allows for free form input"""
widget = FreeFormSelect()
def pre_validate(self, form):
return
class OmgWtForm(Form):
"""Caravelification of the WTForm Form object"""
fieldsets = {}
css_classes = dict()
def get_field(self, fieldname):
return getattr(self, fieldname)
def field_css_classes(self, fieldname):
print(fieldname, self.css_classes[fieldname])
if fieldname in self.css_classes:
return " ".join(self.css_classes[fieldname])
return ""
class FormFactory(object):
"""Used to create the forms in the explore view dynamically"""
series_limits = [0, 5, 10, 25, 50, 100, 500]
fieltype_class = {
SelectField: 'select2',
SelectMultipleField: 'select2',
FreeFormSelectField: 'select2_freeform',
SelectMultipleSortableField: 'select2Sortable',
}
def __init__(self, viz):
self.viz = viz
from caravel.viz import viz_types
viz = self.viz
datasource = viz.datasource
default_metric = datasource.metrics_combo[0][0]
gb_cols = datasource.groupby_column_names
default_groupby = gb_cols[0] if gb_cols else None
group_by_choices = [(s, s) for s in datasource.groupby_column_names]
# Pool of all the fields that can be used in Caravel
self.field_dict = {
'viz_type': SelectField(
'Viz',
default='table',
choices=[(k, v.verbose_name) for k, v in viz_types.items()],
description="The type of visualization to display"),
'metrics': SelectMultipleSortableField(
'Metrics', choices=datasource.metrics_combo,
default=[default_metric],
description="One or many metrics to display"),
'metric': SelectField(
'Metric', choices=datasource.metrics_combo,
default=default_metric,
description="Chose the metric"),
'stacked_style': SelectField(
'Chart Style', choices=self.choicify(
['stack', 'stream', 'expand']),
default='stack',
description=""),
'linear_color_scheme': SelectField(
'Color Scheme', choices=self.choicify([
'fire', 'blue_white_yellow', 'white_black',
'black_white']),
default='blue_white_yellow',
description=""),
'normalize_across': SelectField(
'Normalize Across', choices=self.choicify([
'heatmap', 'x', 'y']),
default='heatmap',
description=(
"Color will be rendered based on a ratio "
"of the cell against the sum of across this "
"criteria")),
'canvas_image_rendering': SelectField(
'Rendering', choices=(
('pixelated', 'pixelated (Sharp)'),
('auto', 'auto (Smooth)'),
),
default='pixelated',
description=(
"image-rendering CSS attribute of the canvas object that "
"defines how the browser scales up the image")),
'xscale_interval': SelectField(
'XScale Interval', choices=self.choicify(range(1, 50)),
default='1',
description=(
"Number of step to take between ticks when "
"printing the x scale")),
'yscale_interval': SelectField(
'YScale Interval', choices=self.choicify(range(1, 50)),
default='1',
description=(
"Number of step to take between ticks when "
"printing the y scale")),
'bar_stacked': BetterBooleanField(
'Stacked Bars',
default=False,
description=""),
'secondary_metric': SelectField(
'Color Metric', choices=datasource.metrics_combo,
default=default_metric,
description="A metric to use for color"),
'country_fieldtype': SelectField(
'Country Field Type',
default='cca2',
choices=(
('name', 'Full name'),
('cioc', 'code International Olympic Committee (cioc)'),
('cca2', 'code ISO 3166-1 alpha-2 (cca2)'),
('cca3', 'code ISO 3166-1 alpha-3 (cca3)'),
),
description=(
"The country code standard that Caravel should expect "
"to find in the [country] column")),
'groupby': SelectMultipleSortableField(
'Group by',
choices=self.choicify(datasource.groupby_column_names),
description="One or many fields to group by"),
'columns': SelectMultipleSortableField(
'Columns',
choices=self.choicify(datasource.groupby_column_names),
description="One or many fields to pivot as columns"),
'all_columns': SelectMultipleSortableField(
'Columns',
choices=self.choicify(datasource.column_names),
description="Columns to display"),
'all_columns_x': SelectField(
'X',
choices=self.choicify(datasource.column_names),
description="Columns to display"),
'all_columns_y': SelectField(
'Y',
choices=self.choicify(datasource.column_names),
description="Columns to display"),
'granularity': FreeFormSelectField(
'Time Granularity', default="one day",
choices=self.choicify([
'all',
'5 seconds',
'30 seconds',
'1 minute',
'5 minutes',
'1 hour',
'6 hour',
'1 day',
'7 days',
]),
description=(
"The time granularity for the visualization. Note that you "
"can type and use simple natural language as in '10 seconds', "
"'1 day' or '56 weeks'")),
'link_length': FreeFormSelectField(
'Link Length', default="200",
choices=self.choicify([
'10',
'25',
'50',
'75',
'100',
'150',
'200',
'250',
]),
description="Link length in the force layout"),
'charge': FreeFormSelectField(
'Charge', default="-500",
choices=self.choicify([
'-50',
'-75',
'-100',
'-150',
'-200',
'-250',
'-500',
'-1000',
'-2500',
'-5000',
]),
description="Charge in the force layout"),
'granularity_sqla': SelectField(
'Time Column',
default=datasource.main_dttm_col or datasource.any_dttm_col,
choices=self.choicify(datasource.dttm_cols),
description=(
"The time column for the visualization. Note that you "
"can define arbitrary expression that return a DATETIME "
"column in the table editor. Also note that the "
"filter bellow is applied against this column or "
"expression")),
'resample_rule': FreeFormSelectField(
'Resample Rule', default='',
choices=self.choicify(('1T', '1H', '1D', '7D', '1M', '1AS')),
description=("Pandas resample rule")),
'resample_how': FreeFormSelectField(
'Resample How', default='',
choices=self.choicify(('', 'mean', 'sum', 'median')),
description=("Pandas resample how")),
'resample_fillmethod': FreeFormSelectField(
'Resample Fill Method', default='',
choices=self.choicify(('', 'ffill', 'bfill')),
description=("Pandas resample fill method")),
'since': FreeFormSelectField(
'Since', default="7 days ago",
choices=self.choicify([
'1 hour ago',
'12 hours ago',
'1 day ago',
'7 days ago',
'28 days ago',
'90 days ago',
'1 year ago'
]),
description=(
"Timestamp from filter. This supports free form typing and "
"natural language as in '1 day ago', '28 days' or '3 years'")),
'until': FreeFormSelectField(
'Until', default="now",
choices=self.choicify([
'now',
'1 day ago',
'7 days ago',
'28 days ago',
'90 days ago',
'1 year ago'])
),
'max_bubble_size': FreeFormSelectField(
'Max Bubble Size', default="25",
choices=self.choicify([
'5',
'10',
'15',
'25',
'50',
'75',
'100',
])
),
'whisker_options': FreeFormSelectField(
'Whisker/outlier options', default="Tukey",
description=(
"Determines how whiskers and outliers are calculated."),
choices=self.choicify([
'Tukey',
'Min/max (no outliers)',
'2/98 percentiles',
'9/91 percentiles',
])
),
'row_limit':
FreeFormSelectField(
'Row limit',
default=config.get("ROW_LIMIT"),
choices=self.choicify(
[10, 50, 100, 250, 500, 1000, 5000, 10000, 50000])),
'limit':
FreeFormSelectField(
'Series limit',
choices=self.choicify(self.series_limits),
default=50,
description=(
"Limits the number of time series that get displayed")),
'rolling_type': SelectField(
'Rolling',
default='None',
choices=[(s, s) for s in ['None', 'mean', 'sum', 'std', 'cumsum']],
description=(
"Defines a rolling window function to apply, works along "
"with the [Periods] text box")),
'rolling_periods': IntegerField(
'Periods',
validators=[validators.optional()],
description=(
"Defines the size of the rolling window function, "
"relative to the time granularity selected")),
'series': SelectField(
'Series', choices=group_by_choices,
default=default_groupby,
description=(
"Defines the grouping of entities. "
"Each serie is shown as a specific color on the chart and "
"has a legend toggle")),
'entity': SelectField(
'Entity', choices=group_by_choices,
default=default_groupby,
description="This define the element to be plotted on the chart"),
'x': SelectField(
'X Axis', choices=datasource.metrics_combo,
default=default_metric,
description="Metric assigned to the [X] axis"),
'y': SelectField(
'Y Axis', choices=datasource.metrics_combo,
default=default_metric,
description="Metric assigned to the [Y] axis"),
'size': SelectField(
'Bubble Size',
default=default_metric,
choices=datasource.metrics_combo),
'url': TextField(
'URL', default='www.airbnb.com',),
'where': TextField(
'Custom WHERE clause', default='',
description=(
"The text in this box gets included in your query's WHERE "
"clause, as an AND to other criteria. You can include "
"complex expression, parenthesis and anything else "
"supported by the backend it is directed towards.")),
'having': TextField(
'Custom HAVING clause', default='',
description=(
"The text in this box gets included in your query's HAVING"
" clause, as an AND to other criteria. You can include "
"complex expression, parenthesis and anything else "
"supported by the backend it is directed towards.")),
'compare_lag': TextField(
'Comparison Period Lag',
description=(
"Based on granularity, number of time periods to "
"compare against")),
'compare_suffix': TextField(
'Comparison suffix',
description="Suffix to apply after the percentage display"),
'x_axis_format': FreeFormSelectField(
'X axis format',
default='smart_date',
choices=[
('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'),
],
description="D3 format syntax for y axis "
"https://github.com/mbostock/\n"
"d3/wiki/Formatting"),
'y_axis_format': FreeFormSelectField(
'Y axis format',
default='.3s',
choices=[
('.3s', '".3s" | 12.3k'),
('.3%', '".3%" | 1234543.210%'),
('.4r', '".4r" | 12350'),
('.3f', '".3f" | 12345.432'),
('+,', '"+," | +12,345.4321'),
('$,.2f', '"$,.2f" | $12,345.43'),
],
description="D3 format syntax for y axis "
"https://github.com/mbostock/\n"
"d3/wiki/Formatting"),
'markup_type': SelectField(
"Markup Type",
choices=self.choicify(['markdown', 'html']),
default="markdown",
description="Pick your favorite markup language"),
'rotation': SelectField(
"Rotation",
choices=[(s, s) for s in ['random', 'flat', 'square']],
default="random",
description="Rotation to apply to words in the cloud"),
'line_interpolation': SelectField(
"Line Style",
choices=self.choicify([
'linear', 'basis', 'cardinal', 'monotone',
'step-before', 'step-after']),
default='linear',
description="Line interpolation as defined by d3.js"),
'code': TextAreaField(
"Code", description="Put your code here", default=''),
'pandas_aggfunc': SelectField(
"Aggregation function",
choices=self.choicify([
'sum', 'mean', 'min', 'max', 'median', 'stdev', 'var']),
default='sum',
description=(
"Aggregate function to apply when pivoting and "
"computing the total rows and columns")),
'size_from': TextField(
"Font Size From",
default="20",
description="Font size for the smallest value in the list"),
'size_to': TextField(
"Font Size To",
default="150",
description="Font size for the biggest value in the list"),
'show_brush': BetterBooleanField(
"Range Filter", default=False,
description=(
"Whether to display the time range interactive selector")),
'show_datatable': BetterBooleanField(
"Data Table", default=False,
description="Whether to display the interactive data table"),
'include_search': BetterBooleanField(
"Search Box", default=False,
description=(
"Whether to include a client side search box")),
'show_bubbles': BetterBooleanField(
"Show Bubbles", default=False,
description=(
"Whether to display bubbles on top of countries")),
'show_legend': BetterBooleanField(
"Legend", default=True,
description="Whether to display the legend (toggles)"),
'x_axis_showminmax': BetterBooleanField(
"X bounds", default=True,
description=(
"Whether to display the min and max values of the X axis")),
'rich_tooltip': BetterBooleanField(
"Rich Tooltip", default=True,
description=(
"The rich tooltip shows a list of all series for that"
" point in time")),
'y_axis_zero': BetterBooleanField(
"Y Axis Zero", default=False,
description=(
"Force the Y axis to start at 0 instead of the minimum "
"value")),
'y_log_scale': BetterBooleanField(
"Y Log", default=False,
description="Use a log scale for the Y axis"),
'x_log_scale': BetterBooleanField(
"X Log", default=False,
description="Use a log scale for the X axis"),
'donut': BetterBooleanField(
"Donut", default=False,
description="Do you want a donut or a pie?"),
'contribution': BetterBooleanField(
"Contribution", default=False,
description="Compute the contribution to the total"),
'num_period_compare': IntegerField(
"Period Ratio", default=None,
validators=[validators.optional()],
description=(
"[integer] Number of period to compare against, "
"this is relative to the granularity selected")),
'time_compare': TextField(
"Time Shift",
default="",
description=(
"Overlay a timeseries from a "
"relative time period. Expects relative time delta "
"in natural language (example: 24 hours, 7 days, "
"56 weeks, 365 days")),
'subheader': TextField(
'Subheader',
description=(
"Description text that shows up below your Big "
"Number")),
}
@staticmethod
def choicify(l):
return [("{}".format(obj), "{}".format(obj)) for obj in l]
def get_form(self):
"""Returns a form object based on the viz/datasource/context"""
viz = self.viz
field_css_classes = {}
for name, obj in self.field_dict.items():
field_css_classes[name] = ['form-control']
s = self.fieltype_class.get(obj.field_class)
if s:
field_css_classes[name] += [s]
for field in ('show_brush', 'show_legend', 'rich_tooltip'):
field_css_classes[field] += ['input-sm']
class QueryForm(OmgWtForm):
"""The dynamic form object used for the explore view"""
fieldsets = copy(viz.fieldsets)
css_classes = field_css_classes
standalone = HiddenField()
async = HiddenField()
force = HiddenField()
extra_filters = HiddenField()
json = HiddenField()
slice_id = HiddenField()
slice_name = HiddenField()
previous_viz_type = HiddenField(default=viz.viz_type)
collapsed_fieldsets = HiddenField()
viz_type = self.field_dict.get('viz_type')
filter_cols = viz.datasource.filterable_column_names or ['']
for i in range(10):
setattr(QueryForm, 'flt_col_' + str(i), SelectField(
'Filter 1',
default=filter_cols[0],
choices=self.choicify(filter_cols)))
setattr(QueryForm, 'flt_op_' + str(i), SelectField(
'Filter 1',
default='in',
choices=self.choicify(['in', 'not in'])))
setattr(
QueryForm, 'flt_eq_' + str(i),
TextField("Super", default=''))
for field in viz.flat_form_fields():
setattr(QueryForm, field, self.field_dict[field])
def add_to_form(attrs):
for attr in attrs:
setattr(QueryForm, attr, self.field_dict[attr])
# datasource type specific form elements
if viz.datasource.__class__.__name__ == 'SqlaTable':
QueryForm.fieldsets += ({
'label': 'SQL',
'fields': ['where', 'having'],
'description': (
"This section exposes ways to include snippets of "
"SQL in your query"),
},)
add_to_form(('where', 'having'))
grains = viz.datasource.database.grains()
if not viz.datasource.any_dttm_col:
return QueryForm
if grains:
time_fields = ('granularity_sqla', 'time_grain_sqla')
self.field_dict['time_grain_sqla'] = SelectField(
'Time Grain',
choices=self.choicify((grain.name for grain in grains)),
default="Time Column",
description=(
"The time granularity for the visualization. This "
"applies a date transformation to alter "
"your time column and defines a new time granularity."
"The options here are defined on a per database "
"engine basis in the Caravel source code"))
add_to_form(time_fields)
field_css_classes['time_grain_sqla'] = ['form-control', 'select2']
field_css_classes['granularity_sqla'] = ['form-control', 'select2']
else:
time_fields = 'granularity_sqla'
add_to_form((time_fields, ))
else:
time_fields = 'granularity'
add_to_form(('granularity',))
field_css_classes['granularity'] = ['form-control', 'select2_freeform']
add_to_form(('since', 'until'))
QueryForm.fieldsets = ({
'label': 'Time',
'fields': (
time_fields,
('since', 'until'),
),
'description': "Time related form attributes",
},) + tuple(QueryForm.fieldsets)
return QueryForm

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +0,0 @@
{% set menu = appbuilder.menu %}
{% set languages = appbuilder.languages %}
<div class="navbar navbar-static-top {{menu.extra_classes}}" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" style="padding:7px;opacity:0.9;" href="{{appbuilder.get_url_for_index}}">
<img width="50" src="/static/assets/images/caravel_logo.png">
</a>
<span class="navbar-brand">
<a href="{{appbuilder.get_url_for_index}}">
{{ appbuilder.app_name }}
</a>
</span>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{% include 'appbuilder/navbar_menu.html' %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="http://airbnb.io/caravel"><i class="fa fa-book"></i> Documentation</a></li>
{% include 'appbuilder/navbar_right.html' %}
</ul>
</div>
</div>
</div>

View File

@@ -1,4 +0,0 @@
<script>
var msg = "Click on a table link to create a Slice";
window.location = "/r/msg/?url={{ '/tablemodelview/list/' }}&msg=" + msg;
</script>

View File

@@ -1,12 +0,0 @@
{% extends "appbuilder/baselayout.html" %}
{% block head_css %}
<link rel="icon" type="image/png" href="/static/assets/images/favicon.png">
<link rel="stylesheet" type="text/css" href="/static/assets/stylesheets/caravel.css" />
{{super()}}
{% endblock %}
{% block head_js %}
{{super()}}
<script src="/static/assets/javascripts/dist/css-theme.entry.js"></script>
{% endblock %}

View File

@@ -1,139 +0,0 @@
{% extends "caravel/basic.html" %}
{% block head_js %}
{{ super() }}
<script src="/static/assets/javascripts/dist/dashboard.entry.js"></script>
{% endblock %}
{% block title %}[dashboard] {{ dashboard.dashboard_title }}{% endblock %}
{% block body %}
<div class="dashboard container-fluid" data-dashboard="{{ dashboard.json_data }}" data-css="{{ dashboard.css }}">
<!-- Modal -->
<div class="modal fade" id="css_modal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content css">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="myModalLabel">Dashboard CSS</h4>
<h6><strong>Styling applies to this dashboard only</strong></h6>
</div>
<div class="modal-body">
<select id="css_template" class="select2" style="margin-bottom: 5px;">
<option value="" data-css="">CSS template</option>
{% for t in templates %}
<option value="{{ t.id }}" data-css="{{t.css}}">
{{ t.template_name }}
</option>
{% endfor %}
</select><br>
<textarea id="dash_css" rows="30" cols="60">{{ dashboard.css }}</textarea>
<input type="hidden" id="dashboard_id" value="{{ dashboard.id }}" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<div class="title">
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<h2>
<span class="favstar" class_name="Dashboard" obj_id="{{ dashboard.id }}"></span>
{{ dashboard.dashboard_title }}
</h2>
</div>
<div class="col-md-3">
<div class="btn-group pull-right" role="group" >
<button type="button" id="refresh_dash" class="btn btn-default" data-toggle="tooltip" title="Force refresh the whole dashboard">
<i class="fa fa-refresh"></i>
</button>
<button type="button" id="filters" class="btn btn-default" data-toggle="tooltip" title="View the list of active filters">
<i class="fa fa-filter"></i>
</button>
<button type="button" id="css" class="btn btn-default" data-toggle="modal" data-target="#css_modal">
<i class="fa fa-css3" data-toggle="tooltip" title="Edit the dashboard's CSS"></i>
</button>
<a id="editdash" class="btn btn-default" href="/dashboardmodelview/edit/{{ dashboard.id }}" title="Edit this dashboard's property" data-toggle="tooltip" >
<i class="fa fa-edit"></i>
</a>
<button type="button" id="savedash" class="btn btn-default" data-toggle="tooltip" title="Save the current positioning and CSS">
<i class="fa fa-save"></i>
</button>
</div>
</div>
</div>
</div>
<div class="gridster content_fluid" style="visibility: hidden;">
<ul>
{% for slice in dashboard.slices %}
{% set pos = pos_dict.get(slice.id, {}) %}
<li
id="slice_{{ slice.id }}"
slice_id="{{ slice.id }}"
class="widget {{ slice.viz_type }}"
data-row="{{ pos.row or 1 }}"
data-col="{{ pos.col or loop.index }}"
data-sizex="{{ pos.size_x or 4 }}"
data-sizey="{{ pos.size_y or 4 }}">
<div class="chart-header">
<div class="row">
<div class="col-md-12 text-center header">
{{ slice.slice_name }}
</div>
<div class="col-md-12 chart-controls">
<div class="pull-left">
<a title="Move chart" data-toggle="tooltip">
<i class="fa fa-arrows drag"></i>
</a>
<a class="refresh" title="Force refresh data" data-toggle="tooltip">
<i class="fa fa-repeat"></i>
</a>
</div>
<div class="pull-right">
{% if slice.description %}
<a title="Toggle chart description">
<i class="fa fa-info-circle slice_info" slice_id="{{ slice.id }}" title="{{ slice.description }}" data-toggle="tooltip"></i>
</a>
{% endif %}
<a href="{{ slice.edit_url }}" title="Edit chart" data-toggle="tooltip">
<i class="fa fa-pencil"></i>
</a>
<a href="{{ slice.slice_url }}" title="Explore chart" data-toggle="tooltip">
<i class="fa fa-share"></i>
</a>
<a class="remove-chart" title="Remove chart from dashboard" data-toggle="tooltip">
<i class="fa fa-close"></i>
</a>
</div>
</div>
</div>
</div>
<div
class="slice_description bs-callout bs-callout-default"
style="{{ 'display: none;' if "{}".format(slice.id) not in dashboard.metadata_dejson.expanded_slices }}">
{{ slice.description_markeddown | safe }}
</div>
<div class="row chart-container">
<input type="hidden" slice_id="{{ slice.id }}" value="false">
<div id="{{ slice.token }}" class="token col-md-12">
<img src="{{ url_for("static", filename="assets/images/loading.gif") }}" class="loading" alt="loading">
<div class="slice_container" id="{{ slice.token }}_con"></div>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@@ -1,228 +0,0 @@
{% extends "caravel/basic.html" %}
{% block title %}
{% if slice %}
[slice] {{ slice.slice_name }}
{% else %}
[explore] {{ viz.datasource.table_name }}
{% endif %}
{% endblock %}
{% block body %}
{% set datasource = viz.datasource %}
{% set form = viz.form %}
{% macro panofield(fieldname)%}
<div>
{% set field = form.get_field(fieldname)%}
<div>
{{ viz.get_form_override(fieldname, 'label') or field.label }}
{% if field.description %}
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
title="{{ viz.get_form_override(fieldname, 'description') or field.description }}"></i>
{% endif %}
{{ field(class_=form.field_css_classes(field.name)) }}
</div>
</div>
{% endmacro %}
<div class="datasource container-fluid">
<form id="query" method="GET" style="display: none;">
<div class="header">
<span title="Data Source" data-toggle="tooltip">
<select id="datasource_id" class="select2">
{% for ds in datasources %}
<option url="{{ ds.explore_url }}" {{ "selected" if ds.id == datasource.id }} value="{{ ds.id }}">{{ ds.full_name }}<i class="fa fa-info"></i></option>
{% endfor %}
</select>
</span>
<a href="{{ datasource.url }}" class="btn btn-default-outline" data-toggle="tooltip" title="Edit/configure datasource">
<i class="fa fa-edit"></i>&nbsp;
</a>
<span title="Visualization Type" data-toggle="tooltip">
{{ form.get_field("viz_type")(class_="select2-with-images") }}
</span>
{% if slice %}
<span class="btn btn-default notbtn" title="Slice" data-toggle="tooltip" data-placement="bottom">
<span class="favstar" class_name="Slice" obj_id="{{ slice.id }}"></span>
{{ slice.slice_name }}
<a class="" href="/slicemodelview/edit/{{ slice.id }}" data-toggle="tooltip" title="Edit">
{% if slice.description %}
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="bottom" title="{{ slice.description }}"></i>
{% endif %}
<i class="fa fa-edit"></i>
</a>
</span>
{% endif %}
<div class="pull-right">
<span id="is_cached" class="label label-default" title="Force refresh" data-toggle="tooltip">
cached
</span>
<div class="btn-group results" role="group">
<a role="button" tabindex="0" class="btn btn-default" id="shortner" data-toggle="popover" data-trigger="focus">
<i class="fa fa-link" data-toggle="tooltip" title="Short URL"></i>&nbsp;
</a>
<span class="btn btn-default" id="standalone" title="Standalone version, use to embed anywhere" data-toggle="tooltip">
<i class="fa fa-code"></i>&nbsp;
</span>
<span class="btn btn-default " id="json" title="Export to .json" data-toggle="tooltip">
<i class="fa fa-file-code-o"></i>
.json
</span>
<span class="btn btn-default " id="csv" title="Export to .csv format" data-toggle="tooltip">
<i class="fa fa-file-text-o"></i>.csv
</span>
<span class="btn btn-warning notbtn" id="timer">0 sec</span>
<span class="btn btn-info disabled query"
data-toggle="modal" data-target="#query_modal">query</span>
</div>
</div>
<hr/>
</div>
<div id="form_container" class="col-left-fixed">
<div class="row center-block">
<div class="btn-group query-and-save">
<button type="button" class="btn btn-primary query">
<i class="fa fa-bolt"></i>Query
</button>
{% if viz.form_data.slice_id %}
<button type="button" class="btn btn-default" id="btn_overwrite">
<i class="fa fa-save"></i>Overwrite
</button>
{% endif %}
<button type="button" class="btn btn-default" id="btn_save">
<i class="fa fa-plus-circle"></i>Save as
</button>
</div>
</div>
<br/>
{% for fieldset in form.fieldsets %}
<div class="panel panel-default">
{% if fieldset.label %}
<div class="panel-heading">
<span class="legend_label">{{ fieldset.label }}</span>
{% if fieldset.description %}
<i class="fa fa-info-circle" data-toggle="tooltip"
data-placement="bottom"
title="{{ fieldset.description }}"></i>
{% endif %}
<span class="collapser"> [-]</span>
</div>
{% endif %}
<div class="panel-body">
{% for fieldname in fieldset.fields %}
{% if not fieldname %}
<hr/>
{% elif fieldname is string %}
{{ panofield(fieldname) }}
{% else %}
<div class="row">
<div class="form-group">
{% for name in fieldname %}
<div class="col-xs-{{ (12 / fieldname|length) | int }}">
{% if name %}
{{ panofield(name) }}
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
<div class="panel panel-default">
<div class="panel-heading">
<span class="legend_label">Filters</span>
<i class="fa fa-info-circle" data-toggle="tooltip"
data-placement="bottom"
title="Filters are defined using comma delimited strings as in 'US,FR,Other'"></i>
<span class="collapser"> [-]</span>
</div>
<div class="panel-body">
<div id="flt0" style="display: none;">
<span class="">{{ form.flt_col_0(class_="form-control inc") }}</span>
<div class="row">
<span class="col col-sm-4">{{ form.flt_op_0(class_="form-control inc") }}</span>
<span class="col col-sm-6">{{ form.flt_eq_0(class_="form-control inc") }}</span>
<button type="button" class="btn btn-default btn-sm remove" aria-label="Delete filter">
<span class="fa fa-minus" aria-hidden="true"></span>
</button>
</div>
</div>
<div id="filters"></div>
<button type="button" id="plus" class="btn btn-default btn-sm" aria-label="Add a filter">
<span class="fa fa-plus" aria-hidden="true"></span>
<span>Add filter</span>
</button>
</div>
</div>
{{ form.slice_id() }}
{{ form.slice_name() }}
{{ form.collapsed_fieldsets() }}
<input type="hidden" name="action" id="action" value="">
<input type="hidden" name="datasource_name" value="{{ datasource.name }}">
<input type="hidden" name="datasource_id" value="{{ datasource.id }}">
<input type="hidden" name="datasource_type" value="{{ datasource.type }}">
<input type="hidden" name="previous_viz_type" value="{{ viz.viz_type or "table" }}">
</div>
<div class="col-offset">
{% block messages %}{% endblock %}
{% include 'appbuilder/flash.html' %}
<div
id="{{ viz.token }}"
class="widget viz slice {{ viz.viz_type }}"
data-slice="{{ viz.json_data }}"
style="height: 700px;">
<img src="{{ url_for("static", filename="assets/images/loading.gif") }}" class="loading" alt="loading">
<div id="{{ viz.token }}_con" class="slice_container" style="height: 100%; width: 100%"></div>
</div>
<div class="credits pull-right">{{ "credits: " + viz.credits |safe if viz. credits else "" }}</div>
</div>
<div class="modal fade" id="query_modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Query</h4>
</div>
<div class="modal-body">
<pre id="query_container"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="sourceinfo_modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Datasource Description</h4>
</div>
<div class="modal-body">
{{ datasource.description_markeddown | safe }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
{% block tail_js %}
{{ super() }}
<script src="/static/assets/javascripts/dist/explore.entry.js"></script>
{% endblock %}

View File

@@ -1,6 +0,0 @@
{% extends "caravel/basic.html" %}
{% block tail_js %}
{{ super() }}
<script src="/static/assets/javascripts/dist/index.entry.js"></script>
{% endblock %}

View File

@@ -1,50 +0,0 @@
{% extends "caravel/basic.html" %}
{% block body %}
<div class="container-fluid">
<div class="sqlcontent" style="display: none;">
<h3>db: [{{ db }}]</h3>
<div class="row interactions">
<div class="col-xs-7">
<input type="hidden" id="database_id" value="{{ db.id }}">
<button class="btn btn-primary" id="run">Run!</button>
<button class="btn btn-default" id="create_view">Create View</button>
</div>
<div class="col-xs-5">
<select id="dbtable">
{% for t in tables %}
<option value="{{ t }}"
{{ "selected" if t == table_name else '' }}>
{{ t }}
</option>
{% endfor %}
</select>
<button class="btn btn-default" id="select_star">SELECT *</button>
</div>
</div>
<div class="topsql row">
<div class="col-xs-7 fillheight">
<textarea id="sql" class="fillup"></textarea>
</div>
<div class="col-xs-5 fillheight">
<div class="metadata fillup bordered"></div>
</div>
</div>
<div id="interactive">
</div>
<div id="results_section">
<hr/>
<img id="loading" width="25" style="display: none;" src="/static/assets/images/loading.gif">
</div>
<div>
<div id="results" class="bordered" style="display: none;"></div>
</div>
</div>
</div>
{% endblock %}
{% block tail_js %}
{{ super() }}
<script src="/static/assets/javascripts/dist/sql.entry.js"></script>
{% endblock %}

View File

@@ -1,21 +0,0 @@
<html>
<head>
<script src="/static/assets/javascripts/dist/standalone.entry.js"></script>
<link rel="stylesheet" type="text/css" href="/static/assets/stylesheets/caravel.css" />
{% set CSS_THEME = appbuilder.get_app.config.get("CSS_THEME") %}
{% set height = request.args.get("height", 700) %}
{% if CSS_THEME %}
<link rel="stylesheet" type="text/css" href="{{ CSS_THEME }}" />
{% endif %}
</head>
<body>
<div
id="{{ viz.token }}"
class="widget viz slice {{ viz.viz_type }}"
data-slice="{{ viz.json_data }}"
style="height: {{ height }}px;">
<img src="{{ url_for("static", filename="img/loading.gif") }}" class="loading" alt="loading">
<div id="{{ viz.token }}_con" class="slice_container" style="height: 100%; width: 100%"></div>
</div>
</body>
</html>

View File

@@ -1,65 +0,0 @@
{% extends "caravel/basic.html" %}
{% block head_js %}
{{ super() }}
<script src="/static/assets/javascripts/dist/welcome.entry.js"></script>
{% endblock %}
{% block title %}Welcome!{% endblock %}
{% block body %}
<div class="container welcome">
<div class="header">
<h3>Welcome!</h3>
</div>
<hr/>
<div id="cal-heatmap"></div>
<hr/>
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-12">
<span class="pull-left">
<h5><i class="fa fa-dashboard"></i> Dashboards</h5>
</span>
<span class="search pull-right"></span>
<span class="pull-right">
<h5><i class="fa fa-search"></i></h5>
</span>
</div>
</div>
</div>
<div class="panel-body">
<img class="loading" src="/static/assets/images/loading.gif"/>
<table id="dash_table" class="table table-condensed" width="100%"></table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-12">
<span class="pull-left">
<h5><i class="fa fa-bar-chart"></i> Slices</h5>
</span>
<span class="search pull-right"></span>
<span class="pull-right">
<h5><i class="fa fa-search"></i></h5>
</span>
</div>
</div>
</div>
<div class="panel-body">
<img class="loading" src="/static/assets/images/loading.gif"/>
<table id="slice_table" class="table table-condensed" width="100%"></table>
</div>
</div>
</div>
</div>
</div>
<hr/>
{% endblock %}

View File

@@ -1,226 +0,0 @@
"""Utility functions used across Caravel"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import functools
import json
import logging
from datetime import datetime
import parsedatetime
from dateutil.parser import parse
from flask import Markup
from flask_appbuilder.security.sqla import models as ab_models
from markdown import markdown as md
from sqlalchemy.types import TypeDecorator, TEXT
class memoized(object): # noqa
"""Decorator that caches a function's return value each time it is called
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
try:
return self.cache[args]
except KeyError:
value = self.func(*args)
self.cache[args] = value
return value
except TypeError:
# uncachable -- for instance, passing a list as an argument.
# Better to not cache than to blow up entirely.
return self.func(*args)
def __repr__(self):
"""Return the function's docstring."""
return self.func.__doc__
def __get__(self, obj, objtype):
"""Support instance methods."""
return functools.partial(self.__call__, obj)
def list_minus(l, minus):
"""Returns l without what is in minus
>>> list_minus([1, 2, 3], [2])
[1, 3]
"""
return [o for o in l if o not in minus]
def parse_human_datetime(s):
"""
Returns ``datetime.datetime`` from human readable strings
>>> from datetime import date, timedelta
>>> from dateutil.relativedelta import relativedelta
>>> parse_human_datetime('2015-04-03')
datetime.datetime(2015, 4, 3, 0, 0)
>>> parse_human_datetime('2/3/1969')
datetime.datetime(1969, 2, 3, 0, 0)
>>> parse_human_datetime("now") <= datetime.now()
True
>>> parse_human_datetime("yesterday") <= datetime.now()
True
>>> date.today() - timedelta(1) == parse_human_datetime('yesterday').date()
True
>>> year_ago_1 = parse_human_datetime('one year ago').date()
>>> year_ago_2 = (datetime.now() - relativedelta(years=1) ).date()
>>> year_ago_1 == year_ago_2
True
"""
try:
dttm = parse(s)
except Exception:
try:
cal = parsedatetime.Calendar()
dttm = dttm_from_timtuple(cal.parse(s)[0])
except Exception as e:
logging.exception(e)
raise ValueError("Couldn't parse date string [{}]".format(s))
return dttm
def dttm_from_timtuple(d):
return datetime(
d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec)
def merge_perm(sm, permission_name, view_menu_name):
pv = sm.find_permission_view_menu(permission_name, view_menu_name)
if not pv:
sm.add_permission_view_menu(permission_name, view_menu_name)
def parse_human_timedelta(s):
"""
Returns ``datetime.datetime`` from natural language time deltas
>>> parse_human_datetime("now") <= datetime.now()
True
"""
cal = parsedatetime.Calendar()
dttm = dttm_from_timtuple(datetime.now().timetuple())
d = cal.parse(s, dttm)[0]
d = datetime(
d.tm_year, d.tm_mon, d.tm_mday, d.tm_hour, d.tm_min, d.tm_sec)
return d - dttm
class JSONEncodedDict(TypeDecorator):
"""Represents an immutable structure as a json-encoded string."""
impl = TEXT
def process_bind_param(self, value, dialect):
if value is not None:
value = json.dumps(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = json.loads(value)
return value
def init(caravel):
"""Inits the Caravel application with security roles and such"""
db = caravel.db
models = caravel.models
sm = caravel.appbuilder.sm
alpha = sm.add_role("Alpha")
admin = sm.add_role("Admin")
merge_perm(sm, 'all_datasource_access', 'all_datasource_access')
perms = db.session.query(ab_models.PermissionView).all()
for perm in perms:
if perm.permission.name == 'datasource_access':
continue
if perm.view_menu and perm.view_menu.name not in (
'UserDBModelView', 'RoleModelView', 'ResetPasswordView',
'Security'):
sm.add_permission_role(alpha, perm)
sm.add_permission_role(admin, perm)
gamma = sm.add_role("Gamma")
for perm in perms:
if(
perm.view_menu and perm.view_menu.name not in (
'ResetPasswordView',
'RoleModelView',
'UserDBModelView',
'Security') and
perm.permission.name not in (
'all_datasource_access',
'can_add',
'can_download',
'can_delete',
'can_edit',
'can_save',
'datasource_access',
'muldelete',
)):
sm.add_permission_role(gamma, perm)
session = db.session()
table_perms = [
table.perm for table in session.query(models.SqlaTable).all()]
table_perms += [
table.perm for table in session.query(models.DruidDatasource).all()]
for table_perm in table_perms:
merge_perm(sm, 'datasource_access', table_perm)
def datetime_f(dttm):
"""Formats datetime to take less room when it is recent"""
if dttm:
dttm = dttm.isoformat()
now_iso = datetime.now().isoformat()
if now_iso[:10] == dttm[:10]:
dttm = dttm[11:]
elif now_iso[:4] == dttm[:4]:
dttm = dttm[5:]
return "<nobr>{}</nobr>".format(dttm)
def json_iso_dttm_ser(obj):
"""
json serializer that deals with dates
>>> dttm = datetime(1970, 1, 1)
>>> json.dumps({'dttm': dttm}, default=json_iso_dttm_ser)
'{"dttm": "1970-01-01T00:00:00"}'
"""
if isinstance(obj, datetime):
obj = obj.isoformat()
return obj
def markdown(s, markup_wrap=False):
s = s or ''
s = md(s, [
'markdown.extensions.tables',
'markdown.extensions.fenced_code',
'markdown.extensions.codehilite',
])
if markup_wrap:
s = Markup(s)
return s
def readfile(filepath):
with open(filepath) as f:
content = f.read()
return content

View File

@@ -1,909 +0,0 @@
"""Flask web views for Caravel"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import json
import logging
import re
import time
import traceback
from datetime import datetime
import pandas as pd
import sqlalchemy as sqla
from flask import (
g, request, redirect, flash, Response, render_template, Markup)
from flask.ext.appbuilder import ModelView, CompactCRUDMixin, BaseView, expose
from flask.ext.appbuilder.actions import action
from flask.ext.appbuilder.models.sqla.interface import SQLAInterface
from flask.ext.appbuilder.security.decorators import has_access
from pydruid.client import doublesum
from sqlalchemy import create_engine
from sqlalchemy import select, text
from sqlalchemy.sql.expression import TextAsFrom
from werkzeug.routing import BaseConverter
from wtforms.validators import ValidationError
from caravel import appbuilder, db, models, viz, utils, app, sm, ascii_art
config = app.config
log_this = models.Log.log_this
def validate_json(form, field): # noqa
try:
json.loads(field.data)
except Exception as e:
logging.exception(e)
raise ValidationError("json isn't valid")
def generate_download_headers(extension):
filename = datetime.now().strftime("%Y%m%d_%H%M%S")
content_disp = "attachment; filename={}.{}".format(filename, extension)
headers = {
"Content-Disposition": content_disp,
}
return headers
class DeleteMixin(object):
@action(
"muldelete", "Delete", "Delete all Really?", "fa-trash", single=False)
def muldelete(self, items):
self.datamodel.delete_all(items)
self.update_redirect()
return redirect(self.get_redirect())
class CaravelModelView(ModelView):
page_size = 500
class TableColumnInlineView(CompactCRUDMixin, CaravelModelView): # noqa
datamodel = SQLAInterface(models.TableColumn)
can_delete = False
edit_columns = [
'column_name', 'description', 'groupby', 'filterable', 'table',
'count_distinct', 'sum', 'min', 'max', 'expression', 'is_dttm']
add_columns = edit_columns
list_columns = [
'column_name', 'type', 'groupby', 'filterable', 'count_distinct',
'sum', 'min', 'max', 'is_dttm']
page_size = 500
description_columns = {
'is_dttm': (
"Whether to make this column available as a "
"[Time Granularity] option, column has to be DATETIME or "
"DATETIME-like"),
'expression': utils.markdown(
"a valid SQL expression as supported by the underlying backend. "
"Example: `substr(name, 1, 1)`", True),
}
appbuilder.add_view_no_menu(TableColumnInlineView)
class DruidColumnInlineView(CompactCRUDMixin, CaravelModelView): # noqa
datamodel = SQLAInterface(models.DruidColumn)
edit_columns = [
'column_name', 'description', 'datasource', 'groupby',
'count_distinct', 'sum', 'min', 'max']
list_columns = [
'column_name', 'type', 'groupby', 'filterable', 'count_distinct',
'sum', 'min', 'max']
can_delete = False
page_size = 500
def post_update(self, col):
col.generate_metrics()
appbuilder.add_view_no_menu(DruidColumnInlineView)
class SqlMetricInlineView(CompactCRUDMixin, CaravelModelView): # noqa
datamodel = SQLAInterface(models.SqlMetric)
list_columns = ['metric_name', 'verbose_name', 'metric_type']
edit_columns = [
'metric_name', 'description', 'verbose_name', 'metric_type',
'expression', 'table']
description_columns = {
'expression': utils.markdown(
"a valid SQL expression as supported by the underlying backend. "
"Example: `count(DISTINCT userid)`", True),
}
add_columns = edit_columns
page_size = 500
appbuilder.add_view_no_menu(SqlMetricInlineView)
class DruidMetricInlineView(CompactCRUDMixin, CaravelModelView): # noqa
datamodel = SQLAInterface(models.DruidMetric)
list_columns = ['metric_name', 'verbose_name', 'metric_type']
edit_columns = [
'metric_name', 'description', 'verbose_name', 'metric_type',
'datasource', 'json']
add_columns = [
'metric_name', 'verbose_name', 'metric_type', 'datasource', 'json']
page_size = 500
validators_columns = {
'json': [validate_json],
}
appbuilder.add_view_no_menu(DruidMetricInlineView)
class DatabaseView(CaravelModelView, DeleteMixin): # noqa
datamodel = SQLAInterface(models.Database)
list_columns = ['database_name', 'sql_link', 'created_by_', 'changed_on']
order_columns = utils.list_minus(list_columns, ['created_by_'])
add_columns = [
'database_name', 'sqlalchemy_uri', 'cache_timeout', 'extra']
show_columns = add_columns
search_exclude_columns = ('password',)
edit_columns = add_columns
add_template = "caravel/models/database/add.html"
edit_template = "caravel/models/database/edit.html"
base_order = ('changed_on', 'desc')
description_columns = {
'sqlalchemy_uri': (
"Refer to the SqlAlchemy docs for more information on how "
"to structure your URI here: "
"http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html"),
'extra': utils.markdown(
"JSON string containing extra configuration elements. "
"The ``engine_params`` object gets unpacked into the "
"[sqlalchemy.create_engine]"
"(http://docs.sqlalchemy.org/en/latest/core/engines.html#"
"sqlalchemy.create_engine) call, while the ``metadata_params`` "
"gets unpacked into the [sqlalchemy.MetaData]"
"(http://docs.sqlalchemy.org/en/rel_1_0/core/metadata.html"
"#sqlalchemy.schema.MetaData) call. ", True),
}
def pre_add(self, db):
conn = sqla.engine.url.make_url(db.sqlalchemy_uri)
db.password = conn.password
conn.password = "X" * 10 if conn.password else None
db.sqlalchemy_uri = str(conn) # hides the password
def pre_update(self, db):
self.pre_add(db)
appbuilder.add_view(
DatabaseView,
"Databases",
icon="fa-database",
category="Sources",
category_icon='fa-database',)
class TableModelView(CaravelModelView, DeleteMixin): # noqa
datamodel = SQLAInterface(models.SqlaTable)
list_columns = [
'table_link', 'database', 'sql_link', 'is_featured',
'changed_by_', 'changed_on']
add_columns = [
'table_name', 'database', 'schema',
'default_endpoint', 'offset', 'cache_timeout']
edit_columns = [
'table_name', 'is_featured', 'database', 'schema', 'description', 'owner',
'main_dttm_col', 'default_endpoint', 'offset', 'cache_timeout']
related_views = [TableColumnInlineView, SqlMetricInlineView]
base_order = ('changed_on', 'desc')
description_columns = {
'offset': "Timezone offset (in hours) for this datasource",
'schema': (
"Schema, as used only in some databases like Postgres, Redshift "
"and DB2"),
'description': Markup(
"Supports <a href='https://daringfireball.net/projects/markdown/'>"
"markdown</a>"),
}
def post_add(self, table):
try:
table.fetch_metadata()
except Exception as e:
logging.exception(e)
flash(
"Table [{}] doesn't seem to exist, "
"couldn't fetch metadata".format(table.table_name),
"danger")
utils.merge_perm(sm, 'datasource_access', table.perm)
def post_update(self, table):
self.post_add(table)
appbuilder.add_view(
TableModelView,
"Tables",
category="Sources",
icon='fa-table',)
appbuilder.add_separator("Sources")
class DruidClusterModelView(CaravelModelView, DeleteMixin): # noqa
datamodel = SQLAInterface(models.DruidCluster)
add_columns = [
'cluster_name',
'coordinator_host', 'coordinator_port', 'coordinator_endpoint',
'broker_host', 'broker_port', 'broker_endpoint',
]
edit_columns = add_columns
list_columns = ['cluster_name', 'metadata_last_refreshed']
if config['DRUID_IS_ACTIVE']:
appbuilder.add_view(
DruidClusterModelView,
"Druid Clusters",
icon="fa-cubes",
category="Sources",
category_icon='fa-database',)
class SliceModelView(CaravelModelView, DeleteMixin): # noqa
datamodel = SQLAInterface(models.Slice)
add_template = "caravel/add_slice.html"
can_add = False
label_columns = {
'created_by_': 'Creator',
'datasource_link': 'Datasource',
}
list_columns = [
'slice_link', 'viz_type',
'datasource_link', 'created_by_', 'modified']
order_columns = utils.list_minus(list_columns, ['created_by_', 'modified'])
edit_columns = [
'slice_name', 'description', 'viz_type', 'druid_datasource',
'table', 'dashboards', 'params', 'cache_timeout']
base_order = ('changed_on', 'desc')
description_columns = {
'description': Markup(
"The content here can be displayed as widget headers in the "
"dashboard view. Supports "
"<a href='https://daringfireball.net/projects/markdown/'>"
"markdown</a>"),
}
appbuilder.add_view(
SliceModelView,
"Slices",
icon="fa-bar-chart",
category="",
category_icon='',)
class SliceAsync(SliceModelView): # noqa
list_columns = [
'slice_link', 'viz_type',
'created_by_', 'modified', 'icons']
label_columns = {
'icons': ' ',
'created_by_': 'Creator',
'viz_type': 'Type',
'slice_link': 'Slice',
}
appbuilder.add_view_no_menu(SliceAsync)
class DashboardModelView(CaravelModelView, DeleteMixin): # noqa
datamodel = SQLAInterface(models.Dashboard)
label_columns = {
'created_by_': 'Creator',
}
list_columns = ['dashboard_link', 'created_by_', 'modified']
order_columns = utils.list_minus(list_columns, ['created_by_', 'modified'])
edit_columns = [
'dashboard_title', 'slug', 'slices', 'position_json', 'css',
'json_metadata']
add_columns = edit_columns
base_order = ('changed_on', 'desc')
description_columns = {
'position_json': (
"This json object describes the positioning of the widgets in "
"the dashboard. It is dynamically generated when adjusting "
"the widgets size and positions by using drag & drop in "
"the dashboard view"),
'css': (
"The css for individual dashboards can be altered here, or "
"in the dashboard view where changes are immediately "
"visible"),
'slug': "To get a readable URL for your dashboard",
}
def pre_add(self, obj):
obj.slug = obj.slug.strip() or None
if obj.slug:
obj.slug = obj.slug.replace(" ", "-")
obj.slug = re.sub(r'\W+', '', obj.slug)
def pre_update(self, obj):
self.pre_add(obj)
appbuilder.add_view(
DashboardModelView,
"Dashboards",
icon="fa-dashboard",
category="",
category_icon='',)
class DashboardModelViewAsync(DashboardModelView): # noqa
list_columns = ['dashboard_link', 'created_by_', 'modified']
label_columns = {
'created_by_': 'Creator',
'dashboard_link': 'Dashboard',
}
appbuilder.add_view_no_menu(DashboardModelViewAsync)
class LogModelView(CaravelModelView):
datamodel = SQLAInterface(models.Log)
list_columns = ('user', 'action', 'dttm')
edit_columns = ('user', 'action', 'dttm', 'json')
base_order = ('dttm', 'desc')
appbuilder.add_view(
LogModelView,
"Action Log",
category="Security",
icon="fa-list-ol")
class DruidDatasourceModelView(CaravelModelView, DeleteMixin): # noqa
datamodel = SQLAInterface(models.DruidDatasource)
list_columns = [
'datasource_link', 'cluster', 'owner',
'created_by_', 'created_on',
'changed_by_', 'changed_on',
'offset']
order_columns = utils.list_minus(
list_columns, ['created_by_', 'changed_by_'])
related_views = [DruidColumnInlineView, DruidMetricInlineView]
edit_columns = [
'datasource_name', 'cluster', 'description', 'owner',
'is_featured', 'is_hidden', 'default_endpoint', 'offset',
'cache_timeout']
page_size = 500
base_order = ('datasource_name', 'asc')
description_columns = {
'offset': "Timezone offset (in hours) for this datasource",
'description': Markup(
"Supports <a href='"
"https://daringfireball.net/projects/markdown/'>markdown</a>"),
}
def post_add(self, datasource):
datasource.generate_metrics()
utils.merge_perm(sm, 'datasource_access', datasource.perm)
def post_update(self, datasource):
self.post_add(datasource)
if config['DRUID_IS_ACTIVE']:
appbuilder.add_view(
DruidDatasourceModelView,
"Druid Datasources",
category="Sources",
icon="fa-cube")
@app.route('/health')
def health():
return "OK"
@app.route('/ping')
def ping():
return "OK"
class R(BaseView):
"""used for short urls"""
@log_this
@expose("/<url_id>")
def index(self, url_id):
url = db.session.query(models.Url).filter_by(id=url_id).first()
if url:
print(url.url)
return redirect('/' + url.url)
else:
flash("URL to nowhere...", "danger")
return redirect('/')
@log_this
@expose("/shortner/", methods=['POST', 'GET'])
def shortner(self):
url = request.form.get('data')
obj = models.Url(url=url)
db.session.add(obj)
db.session.commit()
return("{request.headers[Host]}/r/{obj.id}".format(
request=request, obj=obj))
@expose("/msg/")
def msg(self):
"""Redirects to specified url while flash a message"""
flash(request.args.get("msg"), "info")
return redirect(request.args.get("url"))
appbuilder.add_view_no_menu(R)
class Caravel(BaseView):
"""The base views for Caravel!"""
@has_access
@expose("/explore/<datasource_type>/<datasource_id>/")
@expose("/datasource/<datasource_type>/<datasource_id>/") # Legacy url
@log_this
def explore(self, datasource_type, datasource_id):
error_redirect = '/slicemodelview/list/'
datasource_class = models.SqlaTable \
if datasource_type == "table" else models.DruidDatasource
datasources = (
db.session
.query(datasource_class)
.all()
)
datasources = sorted(datasources, key=lambda ds: ds.full_name)
datasource = [ds for ds in datasources if int(datasource_id) == ds.id]
datasource = datasource[0] if datasource else None
slice_id = request.args.get("slice_id")
slc = None
if slice_id:
slc = (
db.session.query(models.Slice)
.filter_by(id=slice_id)
.first()
)
if not datasource:
flash("The datasource seems to have been deleted", "alert")
return redirect(error_redirect)
all_datasource_access = self.appbuilder.sm.has_access(
'all_datasource_access', 'all_datasource_access')
datasource_access = self.appbuilder.sm.has_access(
'datasource_access', datasource.perm)
if not (all_datasource_access or datasource_access):
flash("You don't seem to have access to this datasource", "danger")
return redirect(error_redirect)
action = request.args.get('action')
if action in ('save', 'overwrite'):
return self.save(request.args, slc)
viz_type = request.args.get("viz_type")
if not viz_type and datasource.default_endpoint:
return redirect(datasource.default_endpoint)
if not viz_type:
viz_type = "table"
obj = viz.viz_types[viz_type](
datasource,
form_data=request.args,
slice_=slc)
if request.args.get("json") == "true":
status = 200
try:
payload = obj.get_json()
except Exception as e:
logging.exception(e)
if config.get("DEBUG"):
raise e
payload = str(e)
status = 500
resp = Response(
payload,
status=status,
headers=generate_download_headers("json"),
mimetype="application/json")
return resp
elif request.args.get("csv") == "true":
status = 200
payload = obj.get_csv()
return Response(
payload,
status=status,
headers=generate_download_headers("csv"),
mimetype="application/csv")
else:
if request.args.get("standalone") == "true":
template = "caravel/standalone.html"
else:
template = "caravel/explore.html"
resp = self.render_template(
template, viz=obj, slice=slc, datasources=datasources)
try:
pass
except Exception as e:
if config.get("DEBUG"):
raise(e)
return Response(
str(e),
status=500,
mimetype="application/json")
return resp
def save(self, args, slc):
"""Saves (inserts or overwrite a slice) """
session = db.session()
slice_name = args.get('slice_name')
action = args.get('action')
# TODO use form processing form wtforms
d = args.to_dict(flat=False)
del d['action']
del d['previous_viz_type']
as_list = ('metrics', 'groupby', 'columns')
for k in d:
v = d.get(k)
if k in as_list and not isinstance(v, list):
d[k] = [v] if v else []
if k not in as_list and isinstance(v, list):
d[k] = v[0]
table_id = druid_datasource_id = None
datasource_type = args.get('datasource_type')
if datasource_type in ('datasource', 'druid'):
druid_datasource_id = args.get('datasource_id')
elif datasource_type == 'table':
table_id = args.get('datasource_id')
if action == "save":
slc = models.Slice()
msg = "Slice [{}] has been saved".format(slice_name)
elif action == "overwrite":
msg = "Slice [{}] has been overwritten".format(slice_name)
slc.params = json.dumps(d, indent=4, sort_keys=True)
slc.datasource_name = args.get('datasource_name')
slc.viz_type = args.get('viz_type')
slc.druid_datasource_id = druid_datasource_id
slc.table_id = table_id
slc.datasource_type = datasource_type
slc.slice_name = slice_name
if action == "save":
session.add(slc)
elif action == "overwrite":
session.merge(slc)
session.commit()
flash(msg, "info")
return redirect(slc.slice_url)
@has_access
@expose("/checkbox/<model_view>/<id_>/<attr>/<value>", methods=['GET'])
def checkbox(self, model_view, id_, attr, value):
"""endpoint for checking/unchecking any boolean in a sqla model"""
model = None
if model_view == 'TableColumnInlineView':
model = models.TableColumn
elif model_view == 'DruidColumnInlineView':
model = models.DruidColumn
obj = db.session.query(model).filter_by(id=id_).first()
if obj:
setattr(obj, attr, value == 'true')
db.session.commit()
return Response("OK", mimetype="application/json")
@has_access
@expose("/activity_per_day")
def activity_per_day(self):
"""endpoint to power the calendar heatmap on the welcome page"""
Log = models.Log # noqa
qry = (
db.session
.query(
Log.dt,
sqla.func.count())
.group_by(Log.dt)
.all()
)
payload = {str(time.mktime(dt.timetuple())): ccount for dt, ccount in qry if dt}
return Response(json.dumps(payload), mimetype="application/json")
@has_access
@expose("/save_dash/<dashboard_id>/", methods=['GET', 'POST'])
def save_dash(self, dashboard_id):
"""Save a dashboard's metadata"""
data = json.loads(request.form.get('data'))
positions = data['positions']
slice_ids = [int(d['slice_id']) for d in positions]
session = db.session()
Dash = models.Dashboard # noqa
dash = session.query(Dash).filter_by(id=dashboard_id).first()
dash.slices = [o for o in dash.slices if o.id in slice_ids]
dash.position_json = json.dumps(data['positions'], indent=4)
md = dash.metadata_dejson
if 'filter_immune_slices' not in md:
md['filter_immune_slices'] = []
md['expanded_slices'] = data['expanded_slices']
dash.json_metadata = json.dumps(md, indent=4)
dash.css = data['css']
session.merge(dash)
session.commit()
session.close()
return "SUCCESS"
@has_access
@expose("/testconn", methods=["POST", "GET"])
def testconn(self):
"""Tests a sqla connection"""
try:
uri = request.json.get('uri')
connect_args = request.json.get('extras', {}).get('engine_params', {}).get('connect_args', {})
engine = create_engine(uri, connect_args=connect_args)
engine.connect()
return json.dumps(engine.table_names(), indent=4)
except Exception:
return Response(
traceback.format_exc(),
status=500,
mimetype="application/json")
@expose("/favstar/<class_name>/<obj_id>/<action>/")
def favstar(self, class_name, obj_id, action):
session = db.session()
FavStar = models.FavStar # noqa
count = 0
favs = session.query(FavStar).filter_by(
class_name=class_name, obj_id=obj_id, user_id=g.user.id).all()
if action == 'select':
if not favs:
session.add(
FavStar(
class_name=class_name, obj_id=obj_id, user_id=g.user.id,
dttm=datetime.now()))
count = 1
elif action == 'unselect':
for fav in favs:
session.delete(fav)
else:
count = len(favs)
session.commit()
return Response(
json.dumps({'count': count}),
mimetype="application/json")
@has_access
@expose("/dashboard/<dashboard_id>/")
def dashboard(self, dashboard_id):
"""Server side rendering for a dashboard"""
session = db.session()
qry = session.query(models.Dashboard)
if dashboard_id.isdigit():
qry = qry.filter_by(id=int(dashboard_id))
else:
qry = qry.filter_by(slug=dashboard_id)
templates = session.query(models.CssTemplate).all()
dash = qry.first()
# Hack to log the dashboard_id properly, even when getting a slug
@log_this
def dashboard(**kwargs): # noqa
pass
dashboard(dashboard_id=dash.id)
pos_dict = {}
if dash.position_json:
pos_dict = {
int(o['slice_id']): o
for o in json.loads(dash.position_json)}
return self.render_template(
"caravel/dashboard.html", dashboard=dash,
templates=templates,
pos_dict=pos_dict)
@has_access
@expose("/sql/<database_id>/")
@log_this
def sql(self, database_id):
mydb = db.session.query(
models.Database).filter_by(id=database_id).first()
engine = mydb.get_sqla_engine()
tables = engine.table_names()
table_name = request.args.get('table_name')
return self.render_template(
"caravel/sql.html",
tables=tables,
table_name=table_name,
db=mydb)
@has_access
@expose("/table/<database_id>/<table_name>/")
@log_this
def table(self, database_id, table_name):
mydb = db.session.query(
models.Database).filter_by(id=database_id).first()
cols = mydb.get_columns(table_name)
df = pd.DataFrame([(c['name'], c['type']) for c in cols])
df.columns = ['col', 'type']
tbl_cls = (
"dataframe table table-striped table-bordered "
"table-condensed sql_results").split(' ')
return self.render_template(
"caravel/ajah.html",
content=df.to_html(
index=False,
na_rep='',
classes=tbl_cls))
@has_access
@expose("/select_star/<database_id>/<table_name>/")
@log_this
def select_star(self, database_id, table_name):
mydb = db.session.query(
models.Database).filter_by(id=database_id).first()
t = mydb.get_table(table_name)
fields = ", ".join(
[c.name for c in t.columns] or "*")
s = "SELECT\n{}\nFROM {}".format(fields, table_name)
return self.render_template(
"caravel/ajah.html",
content=s
)
@has_access
@expose("/runsql/", methods=['POST', 'GET'])
@log_this
def runsql(self):
"""Runs arbitrary sql and returns and html table"""
session = db.session()
limit = 1000
data = json.loads(request.form.get('data'))
sql = data.get('sql')
database_id = data.get('database_id')
mydb = session.query(models.Database).filter_by(id=database_id).first()
if (
not self.appbuilder.sm.has_access(
'all_datasource_access', 'all_datasource_access')):
raise Exception("test")
content = ""
if mydb:
eng = mydb.get_sqla_engine()
if limit:
sql = sql.strip().strip(';')
qry = (
select('*')
.select_from(TextAsFrom(text(sql), ['*']).alias('inner_qry'))
.limit(limit)
)
sql = str(qry.compile(eng, compile_kwargs={"literal_binds": True}))
try:
df = pd.read_sql_query(sql=sql, con=eng)
content = df.to_html(
index=False,
na_rep='',
classes=(
"dataframe table table-striped table-bordered "
"table-condensed sql_results").split(' '))
except Exception as e:
content = (
'<div class="alert alert-danger">'
"{}</div>"
).format(e.message)
session.commit()
return content
@has_access
@expose("/refresh_datasources/")
def refresh_datasources(self):
"""endpoint that refreshes druid datasources metadata"""
session = db.session()
for cluster in session.query(models.DruidCluster).all():
try:
cluster.refresh_datasources()
except Exception as e:
flash(
"Error while processing cluster '{}'\n{}".format(
cluster, str(e)),
"danger")
logging.exception(e)
return redirect('/druidclustermodelview/list/')
cluster.metadata_last_refreshed = datetime.now()
flash(
"Refreshed metadata from cluster "
"[" + cluster.cluster_name + "]",
'info')
session.commit()
return redirect("/druiddatasourcemodelview/list/")
@expose("/autocomplete/<datasource>/<column>/")
def autocomplete(self, datasource, column):
"""used for filter autocomplete"""
client = utils.get_pydruid_client()
top = client.topn(
datasource=datasource,
granularity='all',
intervals='2013-10-04/2020-10-10',
aggregations={"count": doublesum("count")},
dimension=column,
metric='count',
threshold=1000,
)
values = sorted([d[column] for d in top[0]['result']])
return json.dumps(values)
@app.errorhandler(500)
def show_traceback(self):
if config.get("SHOW_STACKTRACE"):
error_msg = traceback.format_exc()
else:
error_msg = "FATAL ERROR\n"
error_msg = (
"Stacktrace is hidden. Change the SHOW_STACKTRACE "
"configuration setting to enable it")
return render_template(
'caravel/traceback.html',
error_msg=error_msg,
title=ascii_art.stacktrace,
art=ascii_art.error), 500
@has_access
@expose("/welcome")
def welcome(self):
"""Personalized welcome page"""
return self.render_template('caravel/welcome.html', utils=utils)
appbuilder.add_view_no_menu(Caravel)
if config['DRUID_IS_ACTIVE']:
appbuilder.add_link(
"Refresh Druid Metadata",
href='/caravel/refresh_datasources/',
category='Sources',
category_icon='fa-database',
icon="fa-cog")
class CssTemplateModelView(CaravelModelView, DeleteMixin):
datamodel = SQLAInterface(models.CssTemplate)
list_columns = ['template_name']
edit_columns = ['template_name', 'css']
add_columns = edit_columns
appbuilder.add_separator("Sources")
appbuilder.add_view(
CssTemplateModelView,
"CSS Templates",
icon="fa-css3",
category="Sources",
category_icon='')
# ---------------------------------------------------------------------
# Redirecting URL from previous names
class RegexConverter(BaseConverter):
def __init__(self, url_map, *items):
super(RegexConverter, self).__init__(url_map)
self.regex = items[0]
app.url_map.converters['regex'] = RegexConverter
@app.route('/<regex("panoramix\/.*"):url>')
def panoramix(url): # noqa
return redirect(request.full_path.replace('panoramix', 'caravel'))
@app.route('/<regex("dashed\/.*"):url>')
def dashed(url): # noqa
return redirect(request.full_path.replace('dashed', 'caravel'))
# ---------------------------------------------------------------------

View File

@@ -1,5 +1,11 @@
codeclimate-test-reporter
coveralls
flake8
mock
mysqlclient
nose
psycopg2
pyyaml
sphinx
sphinx_bootstrap_theme
sphinx-rtd-theme
sphinxcontrib.youtube

View File

@@ -87,9 +87,9 @@ qthelp:
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/caravel.qhcp"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/superset.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/caravel.qhc"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/superset.qhc"
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@@ -104,8 +104,8 @@ devhelp:
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/caravel"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/caravel"
@echo "# mkdir -p $$HOME/.local/share/devhelp/superset"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/superset"
@echo "# devhelp"
epub:

View File

@@ -21,7 +21,7 @@
<img src="_static/img/dash.png">
<div class="carousel-caption">
<div>
<h1>Caravel</h1>
<h1>Superset</h1>
<p>
an open source data visualization platform
</p>
@@ -80,7 +80,7 @@
<hr/>
<div class="container">
<div class="jumbotron">
<h1>Caravel</h1>
<h1>Superset</h1>
<p>
is an open source data visualization platform that provides easy
exploration of your data and allows you to create and share

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
rm -rf _build
make html
#cp -r ../caravel/assets/images/ _build/html/_static/img/
cp -r ../caravel/assets/images/ _static/img/
rm -rf /tmp/caravel-docs
cp -r _build/html /tmp/caravel-docs
#cp -r ../superset/assets/images/ _build/html/_static/img/
cp -r ../superset/assets/images/ _static/img/
rm -rf /tmp/superset-docs
cp -r _build/html /tmp/superset-docs

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# caravel documentation build configuration file, created by
# superset documentation build configuration file, created by
# sphinx-quickstart on Thu Dec 17 15:42:06 2015.
#
# This file is execfile()d with the current directory set to its
@@ -15,7 +15,7 @@
import sys
import os
import shlex
import sphinx_bootstrap_theme
import sphinx_rtd_theme
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -51,8 +51,8 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'caravel'
copyright = u'2015, Maxime Beauchemin, Airbnb'
project = "Superset's documentation"
copyright = None
author = u'Maxime Beauchemin'
# The version info for the project you're documenting, acts as replacement for
@@ -113,19 +113,15 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'bootstrap'
html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
# 'bootswatch_theme': 'cosmo',
'navbar_title': 'Caravel Documentation',
'navbar_fixed_top': "false",
'navbar_sidebarrel': False,
'navbar_site_name': "Topics",
#'navbar_class': "navbar navbar-left",
'collapse_navigation': False,
'display_version': False,
}
# Add any paths that contain custom themes here, relative to this directory.
@@ -213,7 +209,7 @@ html_show_copyright = False
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'caraveldoc'
htmlhelp_basename = 'supersetdoc'
# -- Options for LaTeX output ---------------------------------------------
@@ -235,7 +231,7 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'caravel.tex', u'Caravel Documentation',
(master_doc, 'superset.tex', u'Superset Documentation',
u'Maxime Beauchemin', 'manual'),
]
@@ -265,7 +261,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'Caravel', u'caravel Documentation',
(master_doc, 'Superset', u'superset Documentation',
[author], 1)
]
@@ -279,8 +275,8 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Caravel', u'Caravel Documentation',
author, 'Caravel', 'One line description of project.',
(master_doc, 'Superset', u'Superset Documentation',
author, 'Superset', 'One line description of project.',
'Miscellaneous'),
]

48
docs/druid.rst Normal file
View File

@@ -0,0 +1,48 @@
Druid
=====
Superset works well with Druid, though currently not all
advanced features out of Druid are covered. This page clarifies what is
covered and what isn't and explains how to use some of the features.
.. note ::
Currently Airbnb runs against Druid ``0.8.x`` and previous /
following versions are not tested against.
Supported
'''''''''
Aggregations
------------
Common aggregations, or Druid metrics can be defined and used in Superset.
The first and simpler use case is to use the checkbox matrix expose in your
datasource's edit view (``Sources -> Druid Datasources ->
[your datasource] -> Edit -> [tab] List Druid Column``).
Clicking the ``GroupBy`` and ``Filterable`` checkboxes will make the column
appear in the related dropdowns while in explore view. Checking
``Count Distinct``, ``Min``, ``Max`` or ``Sum`` will result in creating
new metrics that will appear in the ``List Druid Metric`` tab upon saving the
datasource. By editing these metrics, you'll notice that they their ``json``
element correspond to Druid aggregation definition. You can create your own
aggregations manually from the ``List Druid Metric`` tab following Druid
documentation.
.. image:: _static/img/druid_agg.png
:scale: 50 %
Post-Aggregations
-----------------
Druid supports post aggregation and this works in Superset. All you have to
do is creating a metric, much like you would create an aggregation manually,
but specify ``postagg`` as a ``Metric Type``. You then have to provide a valid
json post-aggregation definition (as specified in the Druid docs) in the
Json field.
Not yet supported
'''''''''''''''''
- Regex filters
- Lookups / joins

View File

@@ -4,7 +4,7 @@ FAQ
Can I query/join multiple tables at one time?
---------------------------------------------
Not directly no. A Caravel SQLAlchemy datasource can only be a single table
Not directly no. A Superset SQLAlchemy datasource can only be a single table
or a view.
When working with tables, the solution would be to materialize
@@ -14,15 +14,15 @@ through some scheduled batch process.
A view is a simple logical layer that abstract an arbitrary SQL queries as
a virtual table. This can allow you to join and union multiple tables, and
to apply some transformation using arbitrary SQL expressions. The limitation
there is your database performance as Caravel effectively will run a query
there is your database performance as Superset effectively will run a query
on top of your query (view). A good practice may be to limit yourself to
joining your main large table to one or many small tables only, and avoid
using ``GROUP BY`` where possible as Caravel will do its own ``GROUP BY`` and
using ``GROUP BY`` where possible as Superset will do its own ``GROUP BY`` and
doing the work twice might slow down performance.
Whether you use a table or a view, the important factor is whether your
database is fast enough to serve it in an interactive fashion to provide
a good user experience in Caravel.
a good user experience in Superset.
How BIG can my data source be?
@@ -32,3 +32,90 @@ It can be gigantic! As mentioned above, the main criteria is whether your
database can execute queries and return results in a time frame that is
acceptable to your users. Many distributed databases out there can execute
queries that scan through terabytes in an interactive fashion.
How do I create my own visualization?
-------------------------------------
We are planning on making it easier to add new visualizations to the
framework, in the meantime, we've tagged a few pull requests as
``example`` to give people examples of how to contribute new
visualizations.
https://github.com/airbnb/superset/issues?q=label%3Aexample+is%3Aclosed
Why are my queries timing out?
------------------------------
If you are seeing timeouts (504 Gateway Time-out) when running queries,
it's because the web server is timing out web requests. If you want to
increase the default (50), you can specify the timeout when starting the
web server with the ``-t`` flag, which is expressed in seconds.
``superset runserver -t 300``
Why is the map not visible in the mapbox visualization?
-------------------------------------------------------
You need to register to mapbox.com, get an API key and configure it as
``MAPBOX_API_KEY`` in ``superset_config.py``.
How to add dynamic filters to a dashboard?
------------------------------------------
It's easy: use the ``Filter Box`` widget, build a slice, and add it to your
dashboard.
The ``Filter Box`` widget allows you to define a query to populate dropdowns
that can be use for filtering. To build the list of distinct values, we
run a query, and sort the result by the metric you provide, sorting
descending.
The widget also has a checkbox ``Date Filter``, which enables time filtering
capabilities to your dashboard. After checking the box and refreshing, you'll
see a ``from`` and a ``to`` dropdown show up.
But what about if you don't want certain widgets to get filtered on your
dashboard? You can do that by editing your dashboard, and in the form,
edit the ``JSON Metadata`` field, more specifically the
``filter_immune_slices`` key, that receives an array of sliceIds that should
never be affected by any dashboard level filtering.
..code::
{
"filter_immune_slices": [324, 65, 92],
"expanded_slices": {},
"filter_immune_slice_fields": {
"177": ["country_name", "__from", "__to"],
"32": ["__from", "__to"]
}
}
In the json blob above, slices 324, 65 and 92 won't be affected by any
dashboard level filtering.
Now note the ``filter_immune_slice_fields`` key. This one allows you to
be more specific and define for a specific slice_id, which filter fields
should be disregarded.
Note the use of the ``__from`` and ``__to`` keywords, those are reserved
for dealing with the time boundary filtering mentioned above.
But what happens with filtering when dealing with slices coming from
different tables or databases? If the column name is shared, the filter will
be applied, it's as simple as that.
Why does fabmanager or superset freezed/hung/not responding when started (my home directory is NFS mounted)?
-----------------------------------------------------------------------------------------
superset creates and uses an sqlite database at ``~/.superset/superset.db``. Sqlite is known to `don't work well if used on NFS`__ due to broken file locking implementation on NFS.
__ https://www.sqlite.org/lockingv3.html
One work around is to create a symlink from ~/.superset to a directory located on a non-NFS partition.
Another work around is to change where superset stores the sqlite database by adding ``SQLALCHEMY_DATABASE_URI = 'sqlite:////new/localtion/superset.db'`` in superset_config.py (create the file if needed), then adding the directory where superset_config.py lives to PYTHONPATH environment variable (e.g. ``export PYTHONPATH=/opt/logs/sandbox/airbnb/``).

View File

@@ -70,3 +70,17 @@ Gallery
.. image:: _static/img/viz_thumbnails/treemap.png
:scale: 25 %
.. image:: _static/img/viz_thumbnails/cal_heatmap.png
:scale: 25 %
.. image:: _static/img/viz_thumbnails/horizon.png
:scale: 25 %
.. image:: _static/img/viz_thumbnails/mapbox.png
:scale: 25 %
.. image:: _static/img/viz_thumbnails/separator.png
:scale: 25 %
.. image:: _static/img/viz_thumbnails/histogram.png
:scale: 25 %

View File

@@ -1,7 +1,13 @@
.. image:: _static/img/caravel.jpg
Superset's documentation
''''''''''''''''''''''''
.. warning:: This project used to be name Panoramix and has been renamed
to Caravel in March 2016
Superset is a data exploration platform designed to be visual, intuitive
and interactive.
----------------
.. warning:: This project was originally named Panoramix, was renamed to
Caravel in March 2016, and is currently named Superset as of November 2016
Overview
=======================================
@@ -24,6 +30,21 @@ Features
- Integration with most RDBMS through SqlAlchemy
- Deep integration with Druid.io
------
.. image:: https://camo.githubusercontent.com/82e264ef777ba06e1858766fe3b8817ee108eb7e/687474703a2f2f672e7265636f726469742e636f2f784658537661475574732e676966
------
.. image:: https://camo.githubusercontent.com/4991ff37a0005ea4e4267919a52786fda82d2d21/687474703a2f2f672e7265636f726469742e636f2f755a6767594f645235672e676966
------
.. image:: https://camo.githubusercontent.com/a389af15ac1e32a3d0fee941b4c62c850b1d583b/687474703a2f2f672e7265636f726469742e636f2f55373046574c704c76682e676966
------
Contents
---------
@@ -32,8 +53,11 @@ Contents
installation
tutorial
security
sqllab
videos
gallery
druid
faq

View File

@@ -4,19 +4,19 @@ Installation & Configuration
Getting Started
---------------
Caravel is currently only tested using Python 2.7.*. Python 3 support is
on the roadmap, Python 2.6 won't be supported.
Superset is tested using Python 2.7 and Python 3.4+. Python 3 is the recommended version,
Python 2.6 won't be supported.
OS dependencies
---------------
Caravel stores database connection information in its metadata database.
Superset stores database connection information in its metadata database.
For that purpose, we use the ``cryptography`` Python library to encrypt
connection passwords. Unfortunately this library has OS level dependencies.
You may want to attempt the next step
("Caravel installation and initialization") and come back to this step if
("Superset installation and initialization") and come back to this step if
you encounter an error.
Here's how to install them:
@@ -24,13 +24,13 @@ Here's how to install them:
For **Debian** and **Ubuntu**, the following command will ensure that
the required dependencies are installed: ::
sudo apt-get install build-essential libssl-dev libffi-dev python-dev python-pip
sudo apt-get install build-essential libssl-dev libffi-dev python-dev python-pip libsasl2-dev libldap2-dev
For **Fedora** and **RHEL-derivatives**, the following command will ensure
that the required dependencies are installed: ::
sudo yum upgrade python-setuptools
sudo yum install gcc libffi-devel python-devel python-pip python-wheel openssl-devel
sudo yum install gcc libffi-devel python-devel python-pip python-wheel openssl-devel libsasl2-devel openldap-devel
**OSX**, system python is not recommended. brew's python also ships with pip ::
@@ -40,60 +40,105 @@ that the required dependencies are installed: ::
**Windows** isn't officially supported at this point, but if you want to
attempt it, download `get-pip.py <https://bootstrap.pypa.io/get-pip.py>`_, and run ``python get-pip.py`` which may need admin access. Then run the following: ::
C:\> \path\to\vcvarsall.bat x86_amd64
C:\> set LIB=C:\OpenSSL-1.0.1f-64bit\lib;%LIB%
C:\> set INCLUDE=C:\OpenSSL-1.0.1f-64bit\include;%INCLUDE%
C:\> pip install cryptography
# You may also have to create C:\Temp
C:\> md C:\Temp
Python virtualenv
-----------------
It is recommended to install Superset inside a virtualenv. Python 3 already ships virtualenv, for
Python 2 you need to install it. If it's packaged for your operating systems install it from there
otherwise you can install from pip: ::
Caravel installation and initialization
---------------------------------------
Follow these few simple steps to install Caravel.::
pip install virtualenv
# Install caravel
pip install caravel
You can create and activate a virtualenv by: ::
# virtualenv is shipped in Python 3 as pyvenv
virtualenv venv
. ./venv/bin/activate
On windows the syntax for activating it is a bit different: ::
venv\Scripts\activate
Once you activated your virtualenv everything you are doing is confined inside the virtualenv.
To exit a virtualenv just type ``deactivate``.
Python's setup tools and pip
----------------------------
Put all the chances on your side by getting the very latest ``pip``
and ``setuptools`` libraries.::
pip install --upgrade setuptools pip
Superset installation and initialization
----------------------------------------
Follow these few simple steps to install Superset.::
# Install superset
pip install superset
# Create an admin user
fabmanager create-admin --app caravel
fabmanager create-admin --app superset
# Initialize the database
caravel db upgrade
# Create default roles and permissions
caravel init
superset db upgrade
# Load some data to play with
caravel load_examples
superset load_examples
# Start the development web server
caravel runserver -d
# Create default roles and permissions
superset init
# Start the web server on port 8088
superset runserver -p 8088
# To start a development web server, use the -d switch
# superset runserver -d
After installation, you should be able to point your browser to the right
hostname:port `http://localhost:8088 <http://localhost:8088>`_, login using
the credential you entered while creating the admin account, and navigate to
`Menu -> Admin -> Refresh Metadata`. This action should bring in all of
your datasources for Caravel to be aware of, and they should show up in
your datasources for Superset to be aware of, and they should show up in
`Menu -> Datasources`, from where you can start playing with your data!
Please note that *gunicorn*, Superset default application server, does not
work on Windows so you need to use the development web server.
The development web server though is not intended to be used on production systems
so better use a supported platform that can run *gunicorn*.
Configuration behind a load balancer
------------------------------------
If you are running superset behind a load balancer or reverse proxy (e.g. NGINX
or ELB on AWS), you may need to utilise a healthcheck endpoint so that your
load balancer knows if your superset instance is running. This is provided
at ``/health`` which will return a 200 response containing "OK" if the
webserver is running.
If the load balancer is inserting X-Forwarded-For/X-Forwarded-Proto headers, you
should set `ENABLE_PROXY_FIX = True` in the superset config file to extract and use
the headers.
Configuration
-------------
To configure your application, you need to create a file (module)
``caravel_config.py`` and make sure it is in your PYTHONPATH. Here are some
``superset_config.py`` and make sure it is in your PYTHONPATH. Here are some
of the parameters you can copy / paste in that configuration module: ::
#---------------------------------------------------------
# Caravel specifix config
# Superset specific config
#---------------------------------------------------------
ROW_LIMIT = 5000
WEBSERVER_THREADS = 8
SUPERSET_WORKERS = 4
CARAVEL_WEBSERVER_PORT = 8088
SUPERSET_WEBSERVER_PORT = 8088
#---------------------------------------------------------
#---------------------------------------------------------
@@ -104,28 +149,36 @@ of the parameters you can copy / paste in that configuration module: ::
# The SQLAlchemy connection string to your database backend
# This connection defines the path to the database that stores your
# caravel metadata (slices, connections, tables, dashboards, ...).
# superset metadata (slices, connections, tables, dashboards, ...).
# Note that the connection information to connect to the datasources
# you want to explore are managed directly in the web UI
SQLALCHEMY_DATABASE_URI = 'sqlite:////tmp/caravel.db'
SQLALCHEMY_DATABASE_URI = 'sqlite:////path/to/superset.db'
# Flask-WTF flag for CSRF
CSRF_ENABLED = True
# Set this API key to enable Mapbox visualizations
MAPBOX_API_KEY = ''
This file also allows you to define configuration parameters used by
Flask App Builder, the web framework used by Caravel. Please consult
Flask App Builder, the web framework used by Superset. Please consult
the `Flask App Builder Documentation
<http://flask-appbuilder.readthedocs.org/en/latest/config.html>`_
for more information on how to configure Caravel.
for more information on how to configure Superset.
Please make sure to change:
* *SQLALCHEMY_DATABASE_URI*, by default it is stored at *~/.superset/superset.db*
* *SECRET_KEY*, to a long random string
Database dependencies
---------------------
Caravel does not ship bundled with connectivity to databases, except
Superset does not ship bundled with connectivity to databases, except
for Sqlite, which is part of the Python standard library.
You'll need to install the required packages for the database you
want to use as your metadata database as well as the packages needed to
connect to the databases you want to access through Caravel.
connect to the databases you want to access through Superset.
Here's a list of some of the recommended packages.
@@ -146,6 +199,12 @@ Here's a list of some of the recommended packages.
+---------------+-------------------------------------+-------------------------------------------------+
| MSSQL | ``pip install pymssql`` | ``mssql://`` |
+---------------+-------------------------------------+-------------------------------------------------+
| Impala | ``pip install impyla`` | ``impala://`` |
+---------------+-------------------------------------+-------------------------------------------------+
| SparkSQL | ``pip install pyhive`` | ``jdbc+hive://`` |
+---------------+-------------------------------------+-------------------------------------------------+
| Greenplum | ``pip install psycopg2`` | ``postgresql+psycopg2://`` |
+---------------+-------------------------------------+-------------------------------------------------+
Note that many other database are supported, the main criteria being the
existence of a functional SqlAlchemy dialect and Python driver. Googling
@@ -156,15 +215,18 @@ database you want to connect to should get you to the right place.
Caching
-------
Caravel uses `Flask-Cache <https://pythonhosted.org/Flask-Cache/>`_ for
Superset uses `Flask-Cache <https://pythonhosted.org/Flask-Cache/>`_ for
caching purpose. Configuring your caching backend is as easy as providing
a ``CACHE_CONFIG``, constant in your ``caravel_config.py`` that
a ``CACHE_CONFIG``, constant in your ``superset_config.py`` that
complies with the Flask-Cache specifications.
Flask-Cache supports multiple caching backends (Redis, Memcache,
SimpleCache (in-memory), or the local filesystem).
Flask-Cache supports multiple caching backends (Redis, Memcached,
SimpleCache (in-memory), or the local filesystem). If you are going to use
Memcached please use the pylibmc client library as python-memcached does
not handle storing binary data correctly. If you use Redis, please install
[python-redis](https://pypi.python.org/pypi/redis).
For setting your timeouts, this is done in the Caravel metadata and goes
For setting your timeouts, this is done in the Superset metadata and goes
up the "timeout searchpath", from your slice configuration, to your
data source's configuration, to your database's and ultimately falls back
into your global default defined in ``CACHE_CONFIG``.
@@ -192,7 +254,7 @@ Schemas (Postgres & Redshift)
Postgres and Redshift, as well as other database,
use the concept of **schema** as a logical entity
on top of the **database**. For Caravel to connect to a specific schema,
on top of the **database**. For Superset to connect to a specific schema,
there's a **schema** parameter you can set in the table form.
@@ -224,14 +286,69 @@ Druid
* Navigate to your datasources
Note that you can run the ``caravel refresh_druid`` command to refresh the
Note that you can run the ``superset refresh_druid`` command to refresh the
metadata from your Druid cluster(s)
CORS
-----
The extra CORS Dependency must be installed:
superset[cors]
The following keys in `superset_config.py` can be specified to configure CORS:
* ``ENABLE_CORS``: Must be set to True in order to enable CORS
* ``CORS_OPTIONS``: options passed to Flask-CORS (`documentation <http://flask-cors.corydolphin.com/en/latest/api.html#extension>`)
Upgrading
---------
Upgrading should be as straightforward as running::
pip install caravel --upgrade
caravel db upgrade
pip install superset --upgrade
superset db upgrade
superset init
SQL Lab
-------
SQL Lab is a powerful SQL IDE that works with all SQLAlchemy compatible
databases out there. By default, queries are run in a web request, and
may eventually timeout as queries exceed the maximum duration of a web
request in your environment, whether it'd be a reverse proxy or the Superset
server itself.
In the modern analytics world, it's not uncommon to run large queries that
run for minutes or hours.
To enable support for long running queries that
execute beyond the typical web request's timeout (30-60 seconds), it is
necessary to deploy an asynchronous backend, which consist of one or many
Superset worker, which is implemented as a Celery worker, and a Celery
broker for which we recommend using Redis or RabbitMQ.
It's also preferable to setup an async result backend as a key value store
that can hold the long-running query results for a period of time. More
details to come as to how to set this up here soon.
SQL Lab supports templating in queries, and it's possible to override
the default Jinja context in your environment by defining the
``JINJA_CONTEXT_ADDONS`` in your superset configuration. Objects referenced
in this dictionary are made available for users to use in their SQL.
Making your own build
---------------------
For more advanced users, you may want to build Superset from sources. That
would be the case if you fork the project to add features specific to
your environment.::
# assuming $SUPERSET_HOME as the root of the repo
cd $SUPERSET_HOME/superset/assets
npm install
npm run prod
cd $SUPERSET_HOME
python setup.py install

150
docs/security.rst Normal file
View File

@@ -0,0 +1,150 @@
Security
========
Security in Superset is handled by Flask AppBuilder (FAB). FAB is a
"Simple and rapid application development framework, built on top of Flask.".
FAB provides authentication, user management, permissions and roles.
Provided Roles
--------------
Superset ships with a set of roles that are handled by Superset itself.
You can assume that these roles will stay up-to-date as Superset evolves.
Even though it's possible for ``Admin`` usrs to do so, it is not recommended
that you alter these roles in any way by removing
or adding permissions to them as these roles will be re-synchronized to
their original values as you run your next ``superset init`` command.
Since it's not recommended to alter the roles described here, it's right
to assume that your security strategy should be to compose user access based
on these base roles and roles that you create. For instance you could
create a role ``Financial Analyst`` that would be made of set of permissions
to a set of data sources (tables) and/or databases. Users would then be
granted ``Gamma``, ``Financial Analyst``, and perhaps ``sql_lab``.
Admin
"""""
Admins have all possible rights, including granting or revoking rights from
other users and altering other people's slices and dashboards.
Alpha
"""""
Alpha have access to all data sources, but they cannot grant or revoke access
from other users. They are also limited to altering the objects that they
own. Alpha users can add and alter data sources.
Gamma
"""""
Gamma have limited access. They can only consume data coming from data sources
they have been giving access to through another complementary role.
They only have access to view the slices and
dashboards made from data sources that they have access to. Currently Gamma
users are not able to alter or add data sources. We assume that they are
mostly content consumers, though they can create slices and dashboards.
Also note that when Gamma users look at the dashboards and slices list view,
they will only see the objects that they have access to.
sql_lab
"""""""
The ``sql_lab`` role grants access to SQL Lab. Note that while ``Admin``
users have access to all databases by default, both ``Alpha`` and ``Gamma``
users need to be given access on a per database basis.
Managing Gamma per data source access
-------------------------------------
Here's how to provide users access to only specific datasets. First make
sure the users with limited access have [only] the Gamma role assigned to
them. Second, create a new role (``Menu -> Security -> List Roles``) and
click the ``+`` sign.
.. image:: _static/img/create_role.png
:scale: 50 %
This new window allows you to give this new role a name, attribute it to users
and select the tables in the ``Permissions`` dropdown. To select the data
sources you want to associate with this role, simply click in the dropdown
and use the typeahead to search for your table names.
You can then confirm with your Gamma users that they see the objects
(dashboards and slices) associated with the tables related to their roles.
Customizing
-----------
The permissions exposed by FAB are very granular and allow for a great level
of customization. FAB creates many permissions automagically for each model
that is create (can_add, can_delete, can_show, can_edit, ...) as well as for
each view. On top of that, Superset can expose more granular permissions like
``all_datasource_access``.
We do not recommend altering the 3 base roles as there
are a set of assumptions that Superset build upon. It is possible though for
you to create your own roles, and union them to existing ones.
Permissions
"""""""""""
Roles are composed of a set of permissions, and Superset has many categories
of permissions. Here are the different categories of permissions:
- **Model & action**: models are entities like ``Dashboard``,
``Slice``, or ``User``. Each model has a fixed set of permissions, like
``can_edit``, ``can_show``, ``can_delete``, ``can_list``, ``can_add``, and
so on. By adding ``can_delete on Dashboard`` to a role, and granting that
role to a user, this user will be able to delete dashboards.
- **Views**: views are individual web pages, like the ``explore`` view or the
``SQL Lab`` view. When granted to a user, he/she will see that view in
the its menu items, and be able to load that page.
- **Data source**: For each data source, a permission is created. If the user
does not have the ``all_datasource_access`` permission granted, the user
will only be able to see Slices or explore the data sources that are granted
to them
- **Database**: Granting access to a database allows for the user to access
all data sources within that database, and will enable the user to query
that database in SQL Lab, provided that the SQL Lab specific permission
have been granted to the user
Restricting access to a subset of data sources
""""""""""""""""""""""""""""""""""""""""""""""
The best way to go is probably to give user ``Gamma`` plus one or many other
roles that would add access to specific data sources. We recommend that you
create individual roles for each access profile. Say people in your finance
department might have access to a set of databases and data sources, and
these permissions can be consolidated in a single role. Users with this
profile then need to be attributed ``Gamma`` as a foundation to the models
and views they can access, and that ``Finance`` role that is a collection
of permissions to data objects.
One user can have many roles, so a finance executive could be granted
``Gamma``, ``Finance``, and perhaps another ``Executive`` role that gather
a set of data sources that power dashboards only made available to executives.
When looking at its dashboard list, this user will only see the
list of dashboards it has access to, based on the roles and
permissions that were attributed.
Restricting the access to some metrics
""""""""""""""""""""""""""""""""""""""
Sometimes some metrics are relatively sensitive (e.g. revenue).
We may want to restrict those metrics to only a few roles.
For example, assumed there is a metric ``[cluster1].[datasource1].[revenue]``
and only Admin users are allowed to see it. Heres how to restrict the access.
1. Edit the datasource (``Menu -> Source -> Druid datasources -> edit the
record "datasource1"``) and go to the tab ``List Druid Metric``. Check
the checkbox ``Is Restricted`` in the row of the metric ``revenue``.
2. Edit the role (``Menu -> Security -> List Roles -> edit the record
“Admin”``), in the permissions field, type-and-search the permission
``metric access on [cluster1].[datasource1].[revenue] (id: 1)``, then
click the Save button on the bottom of the page.
Any users without the permission will see the error message
*Access to the metrics denied: revenue (Status: 500)* in the slices.
It also happens when the user wants to access a post-aggregation metric that
is dependent on revenue.

60
docs/sqllab.rst Normal file
View File

@@ -0,0 +1,60 @@
SQL Lab
=======
SQL Lab is a modern, feature-rich SQL IDE written in
`React <https://facebook.github.io/react/>`_.
Feature Overview
----------------
- Connects to just about any database backend
- A multi-tab environment to work on multiple queries at a time
- A smooth flow to visualize your query results using Superset's rich
visualization capabilities
- Browse database metadata: tables, columns, indexes, partitions
- Support for long-running queries
- uses the `Celery distributed queue <http://www.python.org/>`_
to dispatch query handling to workers
- supports defining a "results backend" to persist query results
- A search engine to find queries executed in the past
- Supports templating using the
`Jinja templating language <http://jinja.pocoo.org/docs/dev/>`_
which allows for using macros in your SQL code
Extra features
--------------
- Hit ``alt + enter`` as a keyboard shortcut to run your query
Templating with Jinja
---------------------
.. code-block:: sql
SELECT *
FROM some_table
WHERE partition_key = '{{ presto.latest_partition('some_table') }}'
Templating unleashes the power and capabilities of a
programming language within your SQL code.
Templates can also be used to write generic queries that are
parameterized so they can be re-used easily.
Available macros
''''''''''''''''
We expose certain modules from Python's standard library in
Superset's Jinja context:
- ``time``: ``time``
- ``datetime``: ``datetime.datetime``
- ``uuid``: ``uuid``
- ``random``: ``random``
- ``relativedelta``: ``dateutil.relativedelta.relativedelta``
- more to come!
`Jinja's builtin filters <http://jinja.pocoo.org/docs/dev/templates/>`_ can be also be applied where needed.
.. autoclass:: superset.jinja_context.PrestoTemplateProcessor
:members:

View File

@@ -3,7 +3,7 @@ Tutorial
This basic linear tutorial will take you through connecting to a database,
adding a table, creating a slice and a dashboard. First you'll need to tell
Caravel where to find the database you want to
Superset where to find the database you want to
query. First go to the database menu
.. image:: _static/img/tutorial/db_menu.png
@@ -29,7 +29,7 @@ plus (``+``) sign there (similar to the one ).
Now enter the name of the table in the ``Table Name`` textbox, and select
the database you just created in the ``Database`` dropdown, hit save. At this
moment, Caravel fetched the column names, their data types and tries to guess
moment, Superset fetched the column names, their data types and tries to guess
which fields are metrics in dimensions. From the list view, edit the table
that you just created by clicking the tiny pen icon.
@@ -42,9 +42,9 @@ showing you the list of columns in your table as well as their data types.
.. image:: _static/img/tutorial/matrix.png
:scale: 30 %
Click the checkboxes here that inform Caravel how your columns should be
Click the checkboxes here that inform Superset how your columns should be
shown in the explore view, and which metrics should be created. Make sure
to inform Caravel about your date columns. You could also create
to inform Superset about your date columns. You could also create
"SQL expression" columns here, or metrics in that tab as aggregate expressions,
but let's not do that just yet. Hit ``save``.

View File

@@ -2,11 +2,11 @@ Videos
======
Here is a collection of short videos showing different aspect
of Caravel.
of Superset.
Quick Intro
'''''''''''
This video demonstrates how Caravel works at a high level, it shows how
This video demonstrates how Superset works at a high level, it shows how
to navigate through datasets and dashboards that are already available.
.. youtube:: https://www.youtube.com/watch?v=3Txm_nj_R7M
@@ -41,7 +41,7 @@ to toggle them on dashboards.
Adding a Table
''''''''''''''
This videos shows you how to expose a new table in Caravel, and how to
This videos shows you how to expose a new table in Superset, and how to
define the semantics on how this can be accessed by others in the ``Explore``
and ``Dashboard`` views.

7
pypi_push.sh Normal file
View File

@@ -0,0 +1,7 @@
cd superset/assets/
rm build/*
npm run prod
cd ../..
python setup.py register
python setup.py sdist upload

9
run_specific_test.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
echo $DB
rm -f .coverage
export SUPERSET_CONFIG=tests.superset_test_config
set -e
superset/bin/superset version -v
export SOLO_TEST=1
# e.g. tests.core_tests:CoreTests.test_templated_sql_json
nosetests $1

View File

@@ -1,6 +1,13 @@
#!/usr/bin/env bash
rm /tmp/caravel_unittests.db
echo $DB
rm ~/.superset/unittests.db
rm ~/.superset/celerydb.sqlite
rm ~/.superset/celery_results.sqlite
rm -f .coverage
export CARAVEL_CONFIG=tests.caravel_test_config
caravel/bin/caravel db upgrade
export SUPERSET_CONFIG=tests.superset_test_config
set -e
superset/bin/superset db upgrade
superset/bin/superset db upgrade # running twice on purpose as a test
superset/bin/superset version -v
python setup.py nosetests
coveralls

View File

@@ -1,5 +1,5 @@
[metadata]
name = Caravel
name = Superset
summary = a data exploration platform
description-file = README.md
author = Maxime Beauchemin
@@ -7,7 +7,7 @@ author-email = maximebeauchemin@gmail.com
license = Apache License, Version 2.0
[files]
packages = caravel
packages = superset
[build_sphinx]
source-dir = docs/
@@ -21,4 +21,8 @@ upload-dir = docs/_build/html
verbosity=3
detailed-errors=1
with-coverage=1
cover-package=caravel
nocapture=1
cover-package=superset
[pycodestyle]
max-line-length=90

View File

@@ -1,45 +1,68 @@
import imp
import os
import json
from setuptools import setup, find_packages
VERSION = '0.8.8'
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
PACKAGE_DIR = os.path.join(BASE_DIR, 'superset', 'static', 'assets')
PACKAGE_FILE = os.path.join(PACKAGE_DIR, 'package.json')
with open(PACKAGE_FILE) as package_file:
version_string = json.load(package_file)['version']
setup(
name='caravel',
name='superset',
description=(
"A interactive data visualization platform build on SqlAlchemy "
"and druid.io"),
version=VERSION,
version=version_string,
packages=find_packages(),
include_package_data=True,
zip_safe=False,
scripts=['caravel/bin/caravel'],
scripts=['superset/bin/superset'],
install_requires=[
'alembic>=0.8.5, <0.9.0',
'cryptography>=1.1.1, <2.0.0',
'flask-appbuilder>=1.6.0, <2.0.0',
'flask-cache>=0.13.1, <0.14.0',
'flask-migrate>=1.5.1, <2.0.0',
'flask-script>=2.0.5, <3.0.0',
'flask-sqlalchemy==2.0.0',
'flask-testing>=0.4.2, <0.5.0',
'flask>=0.10.1, <1.0.0',
'humanize>=0.5.1, <0.6.0',
'gunicorn>=19.3.0, <20.0.0',
'markdown>=2.6.2, <3.0.0',
'pandas==0.18.0',
'celery==3.1.23',
'cryptography==1.5.3',
'flask-appbuilder==1.8.1',
'flask-cache==0.13.1',
'flask-migrate==1.5.1',
'flask-script==2.0.5',
'flask-testing==0.5.0',
'flask-sqlalchemy==2.0',
'humanize==0.5.1',
'gunicorn==19.6.0',
'markdown==2.6.6',
'pandas==0.18.1',
'parsedatetime==2.0.0',
'pydruid>=0.2.2, <0.3',
'python-dateutil>=2.4.2, <3.0.0',
'requests>=2.7.0, <3.0.0',
'sqlalchemy>=1.0.12, <2.0.0',
'sqlalchemy-utils>=0.31.3, <0.32.0',
'sqlparse>=0.1.16, <0.2.0',
'werkzeug>=0.11.2, <0.12.0',
'pydruid==0.3.0',
'PyHive>=0.2.1',
'python-dateutil==2.5.3',
'requests==2.10.0',
'simplejson==3.8.2',
'six==1.10.0',
'sqlalchemy==1.0.13',
'sqlalchemy-utils==0.32.7',
'sqlparse==0.1.19',
'thrift>=0.9.3',
'thrift-sasl>=0.2.1',
'werkzeug==0.11.10',
],
extras_require={
'cors': ['Flask-Cors>=2.0.0'],
},
tests_require=[
'codeclimate-test-reporter',
'coverage',
'mock',
'nose',
],
tests_require=['coverage'],
author='Maxime Beauchemin',
author_email='maximebeauchemin@gmail.com',
url='https://github.com/airbnb/caravel',
url='https://github.com/airbnb/superset',
download_url=(
'https://github.com/airbnb/caravel/tarball/' + VERSION),
'https://github.com/airbnb/superset/tarball/' + version_string),
classifiers=[
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
],
)

89
superset/__init__.py Normal file
View File

@@ -0,0 +1,89 @@
"""Package's main module!"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import logging
import os
from logging.handlers import TimedRotatingFileHandler
from flask import Flask, redirect
from flask_appbuilder import SQLA, AppBuilder, IndexView
from flask_appbuilder.baseviews import expose
from flask_cache import Cache
from flask_migrate import Migrate
from superset.source_registry import SourceRegistry
from werkzeug.contrib.fixers import ProxyFix
from superset import utils
APP_DIR = os.path.dirname(__file__)
CONFIG_MODULE = os.environ.get('SUPERSET_CONFIG', 'superset.config')
app = Flask(__name__)
app.config.from_object(CONFIG_MODULE)
conf = app.config
if not app.debug:
# In production mode, add log handler to sys.stderr.
app.logger.addHandler(logging.StreamHandler())
app.logger.setLevel(logging.INFO)
db = SQLA(app)
utils.pessimistic_connection_handling(db.engine.pool)
cache = Cache(app, config=app.config.get('CACHE_CONFIG'))
migrate = Migrate(app, db, directory=APP_DIR + "/migrations")
# Logging configuration
logging.basicConfig(format=app.config.get('LOG_FORMAT'))
logging.getLogger().setLevel(app.config.get('LOG_LEVEL'))
if app.config.get('ENABLE_TIME_ROTATE'):
logging.getLogger().setLevel(app.config.get('TIME_ROTATE_LOG_LEVEL'))
handler = TimedRotatingFileHandler(app.config.get('FILENAME'),
when=app.config.get('ROLLOVER'),
interval=app.config.get('INTERVAL'),
backupCount=app.config.get('BACKUP_COUNT'))
logging.getLogger().addHandler(handler)
if app.config.get('ENABLE_CORS'):
from flask_cors import CORS
CORS(app, **app.config.get('CORS_OPTIONS'))
if app.config.get('ENABLE_PROXY_FIX'):
app.wsgi_app = ProxyFix(app.wsgi_app)
if app.config.get('UPLOAD_FOLDER'):
try:
os.makedirs(app.config.get('UPLOAD_FOLDER'))
except OSError:
pass
class MyIndexView(IndexView):
@expose('/')
def index(self):
return redirect('/superset/welcome')
appbuilder = AppBuilder(
app, db.session,
base_template='superset/base.html',
indexview=MyIndexView,
security_manager_class=app.config.get("CUSTOM_SECURITY_MANAGER"))
sm = appbuilder.sm
get_session = appbuilder.get_session
results_backend = app.config.get("RESULTS_BACKEND")
# Registering sources
module_datasource_map = app.config.get("DEFAULT_MODULE_DS_MAP")
module_datasource_map.update(app.config.get("ADDITIONAL_MODULE_DS_MAP"))
SourceRegistry.register_sources(module_datasource_map)
from superset import views, config # noqa

3
superset/assets/.babelrc Normal file
View File

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

View File

@@ -0,0 +1,9 @@
**/*{.,-}min.js
**/*.sh
coverage/**
dist/*
images/*
node_modules/*
node_modules*/*
stylesheets/*
vendor/*

18
superset/assets/.eslintrc Normal file
View File

@@ -0,0 +1,18 @@
{
"extends": "airbnb",
"parserOptions":{
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"prefer-template": 0,
"new-cap": 0,
"no-restricted-syntax": 0,
"guard-for-in": 0,
"prefer-arrow-callback": 0,
"func-names": 0,
"react/jsx-no-bind": 0,
"no-confusing-arrow": 0,
}
}

View File

@@ -0,0 +1,54 @@
verbose: true
instrumentation:
root: './javascripts'
extensions: ['.js', '.jsx']
excludes: [
'dist/**'
]
embed-source: false
variable: __coverage__
compact: true
preserve-comments: false
complete-copy: false
save-baseline: true
baseline-file: ./coverage/coverage-baseline.json
include-all-sources: true
include-pid: false
es-modules: true
reporting:
print: summary
reports:
- lcov
dir: ./coverage
watermarks:
statements: [50, 80]
lines: [50, 80]
functions: [50, 80]
branches: [50, 80]
report-config:
clover: {file: clover.xml}
cobertura: {file: cobertura-coverage.xml}
json: {file: coverage-final.json}
json-summary: {file: coverage-summary.json}
lcovonly: {file: lcov.info}
teamcity: {file: null, blockName: Code Coverage Summary}
text: {file: null, maxCols: 0}
text-lcov: {file: lcov.info}
text-summary: {file: null}
hooks:
hook-run-in-context: false
post-require-hook: null
handle-sigint: false
check:
global:
statements: 0
lines: 0
branches: 0
functions: 0
excludes: []
each:
statements: 0
lines: 0
branches: 0
functions: 0
excludes: []

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

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