Compare commits

...

508 Commits

Author SHA1 Message Date
Arik Fraimovich
900d558857 CirlceCI: Docker build for release branches. 2017-11-02 10:59:21 +02:00
Arik Fraimovich
c6dc9affed Redshift: change default SSL mode to prefer. 2017-11-02 10:57:54 +02:00
Arik Fraimovich
96486b5c58 Update v3 CHANGELOG. 2017-11-02 10:33:53 +02:00
Arik Fraimovich
7c1565017f Merge pull request #2072 from getredash/feature/query_results
🎉  Add: data source to run queries on top of query results
2017-11-01 16:22:32 +02:00
Arik Fraimovich
7197370ad4 Add Query Results to the default query runners list. 2017-11-01 16:14:45 +02:00
Arik Fraimovich
1cbf09cbbe Add: data source to run queries on top of query results. 2017-11-01 16:10:04 +02:00
Arik Fraimovich
28b4450fa9 Merge pull request #2068 from kyoshidajp/copy_param_value_when_forking
Copy parameters value when forking a query
2017-11-01 15:18:22 +02:00
Arik Fraimovich
a799303f53 Merge pull request #2071 from getredash/bugfixes
Fix #1824: allow only user API key to be used with query refresh API.
2017-11-01 15:12:53 +02:00
Arik Fraimovich
59d6eb662c Merge pull request #2070 from getredash/bugfixes
Fix: require full access to the data source to fork a query.
2017-11-01 15:06:36 +02:00
Arik Fraimovich
4e4a3e13ab Fix #1824: allow only user API key to be used with query refresh API. 2017-11-01 15:05:55 +02:00
Arik Fraimovich
095d07bcb8 Disable fork button for those can't fork 2017-11-01 14:56:11 +02:00
Arik Fraimovich
71a235c79b Merge pull request #2069 from getredash/bugfixes
Fix #1979: API key of one query could be used to get results of another one
2017-11-01 14:47:23 +02:00
Arik Fraimovich
2bc3885977 Fix: require full access to the data source to fork a query.
Ref #1825.
2017-11-01 14:46:29 +02:00
Arik Fraimovich
97217f56c1 Remove unused variables 2017-11-01 13:52:41 +02:00
Arik Fraimovich
ba36f7395d Fix #1979: API key of one query could be used to get results of another one 2017-11-01 13:51:09 +02:00
Arik Fraimovich
ea7ca9e632 Merge pull request #2067 from getredash/params_refresh
Use query result for drop down values implementation updates
2017-11-01 13:50:49 +02:00
Katsuhiko YOSHIDA
5e5fc736bf Copy parameters value when forking a query 2017-11-01 06:27:03 +09:00
Arik Fraimovich
f38e76ad10 Merge pull request #2061 from kyoshidajp/delete_groupid_from_user_when_deleting_group
Delete the group id from user when deleting a group
2017-10-31 22:45:44 +02:00
Arik Fraimovich
80a6f357e3 Merge pull request #2063 from deecay/box-sortx
Fix: Boxplot xaxis sort issue
2017-10-31 22:39:26 +02:00
Arik Fraimovich
bd91288d1a Save only the query id instead of query id and name 2017-10-31 12:40:02 +02:00
Arik Fraimovich
38389a28ed Update eslint config to use longer lines 2017-10-31 12:06:52 +02:00
Arik Fraimovich
9ef9f29213 Query based parameter changes:
- Use $onChanges instead of $watch (fixes an issue where the query
results was constantly reloading).
- Choose the first value when first loading the options.
2017-10-31 12:05:55 +02:00
Arik Fraimovich
a3c2082b7f Fix: move boto3 import to the correct location 2017-10-30 14:53:22 +02:00
deecay
bc5516e941 Fix: Boxplot xaxis sort issue 2017-10-30 17:08:51 +09:00
Katsuhiko YOSHIDA
65ac8c715e Add user info to db.session 2017-10-29 22:30:53 +09:00
Arik Fraimovich
9874361466 Merge pull request #2060 from kyoshidajp/fix_export_excel
Fix error when exporting list data as Excel file
2017-10-29 15:10:34 +02:00
Arik Fraimovich
b28c8fa227 Merge pull request #2045 from myouju/master
Added 'Use Glue Data Catalog' options in Athena
2017-10-29 14:43:25 +02:00
Katsuhiko YOSHIDA
048bd53eac Delete the group id from user when deleting a group 2017-10-29 11:28:15 +09:00
Katsuhiko YOSHIDA
95c707d028 Fix error when exporting list data as Excel file 2017-10-28 23:16:02 +09:00
Arik Fraimovich
41ec4c857b Update CHANGELOG 2017-10-26 22:17:58 +03:00
Arik Fraimovich
e62acb1d99 Merge pull request #2056 from getredash/boilerplate
Reduce boilerplate in frontend code
2017-10-26 12:06:43 +03:00
Arik Fraimovich
a9dc00aaa6 Remove last relative imports 2017-10-26 10:54:28 +03:00
Arik Fraimovich
38c6152aa0 Move init code into app/config/index.js from app/index.js 2017-10-26 10:00:59 +03:00
Arik Fraimovich
fb723328d4 Add app/lib folder for general client code (instead of app/utils) 2017-10-26 10:00:36 +03:00
Arik Fraimovich
047475562d Support for non relative path imports in client code:
So this:

import { Paginator } from "../../lib";

Becomes:

import { Paginator } from "@/lib";

Makes code cleaner and more portable.
2017-10-26 09:58:24 +03:00
Arik Fraimovich
acd33ec852 Auto register Angular components, pages, etc 2017-10-25 23:34:51 +03:00
Arik Fraimovich
340a23e71c Merge pull request #2054 from jezdez/docker-entrypoint-wildcard
Allow running any command inside the container via the docker entrypoint script.
2017-10-25 22:39:55 +03:00
Jannis Leidel
3db1b7f265 Allow running any command inside the container via the docker entrypoint script.
This allows running generic things like opening a bash shell:

docker-compose run server bash
2017-10-25 20:43:32 +02:00
Arik Fraimovich
845357fa02 Add logging to route registration. 2017-10-25 17:53:28 +03:00
Arik Fraimovich
f75e31fa8e Merge pull request #2042 from isomura/modSetupShMkdir
Make /opt/redash directory if it's not exist.
2017-10-23 10:57:11 +03:00
Arik Fraimovich
38be723179 Merge branch 'master' into modSetupShMkdir 2017-10-23 10:56:50 +03:00
Arik Fraimovich
18bf44453d Update bootstrap script to use v2.0.1. 2017-10-22 15:13:30 +03:00
Arik Fraimovich
374f11252f Add v2.0.1. 2017-10-22 15:03:17 +03:00
Arik Fraimovich
2d3566abce Merge pull request #2046 from sylvainv/patch-1
Make use of REDASH_BASE_PATH variable in setup script
2017-10-22 14:47:58 +03:00
Arik Fraimovich
17d6bfff63 Merge pull request #2012 from yershalom/create_prometheus_ds
Added / on api path to prevent wrong url param
2017-10-22 09:55:41 +03:00
Arik Fraimovich
73540175d8 Merge pull request #2021 from hhamalai/configurable_invitation_token_age
Make invitation token max age configurable
2017-10-22 09:55:11 +03:00
Sylvain
8c693efb3e Add missing $REDASH_BASE_PATH usage 2017-10-19 01:39:08 -05:00
Sylvain
51392d0398 Add missing $REDASH_BASE_PATH usage 2017-10-19 01:37:11 -05:00
Sylvain
78888c2082 Make use of REDASH_BASE_PATH variable 2017-10-19 01:35:14 -05:00
Arik Fraimovich
bc6bd1b316 Merge pull request #2044 from getredash/redshift
Redshift: add support for the new ACM root CA.
2017-10-18 15:59:05 +03:00
Arik Fraimovich
4060344a72 Merge pull request #2038 from atharvai/feature/redshift-spectrum
Add support for AWS Redshift Spectrum (external) tables
2017-10-18 15:56:14 +03:00
yukimaeno
6522325060 fixed private method 2017-10-18 21:51:46 +09:00
yukimaeno
ae6564e912 Added 'Use Glue Data Catalog' options in Athena 2017-10-18 21:41:13 +09:00
Arik Fraimovich
2af70a6c2d Redshift: add support for the new ACM root CA. 2017-10-18 14:58:50 +03:00
Arik Fraimovich
a3a1dcf4ba Merge pull request #2040 from cyriac/patch-2
Show query editor's Archive/Publish Query drop-down only on saved queries
2017-10-18 11:20:31 +03:00
isomura
eb979ef130 Make /opt/redash directory if it's not exist. 2017-10-18 09:54:56 +09:00
Cyriac Thomas
7f7fdbba54 show query builder Archive/Publish Query dropdown only on saved queries 2017-10-17 20:28:20 +05:30
Arik Fraimovich
fa213d72a7 Merge pull request #2039 from getredash/filters
Improve filters UI
2017-10-17 17:02:52 +03:00
Arik Fraimovich
d2bf935edb Improve filters UI (labels for */-, label for filter, show values when selected) 2017-10-17 16:35:46 +03:00
Arik Fraimovich
c4349f5c64 Merge pull request #2037 from getredash/patches
Add: option to set allowDiskUse in MongoDB queries
2017-10-16 10:19:55 +03:00
Arik Fraimovich
b5a6f4a166 Merge pull request #2028 from kyoshidajp/autofocus_in_1st_input_item
Set auto focus in first input items
2017-10-16 10:08:19 +03:00
Arik Fraimovich
79807dfa14 Typo fix. 2017-10-16 10:07:25 +03:00
Arik Fraimovich
0b0ec90987 Update gunicorn to latest version 2017-10-16 09:19:13 +03:00
Arik Fraimovich
a9fc220ec8 Merge pull request #2034 from getredash/patches
Add: disabled status to Organization
2017-10-16 09:18:55 +03:00
Arik Fraimovich
ee9bbbaa7c Merge pull request #2024 from cyriac/patch-1
Fixed stage label typo error on sankey and sunburst-sequence editors
2017-10-15 22:51:57 +03:00
Arik Fraimovich
12cc4e5ff9 Add: option to set allowDiskUse in MongoDB queries. 2017-10-15 15:54:04 +03:00
Arik Fraimovich
b5b5643090 Add: disabled status to Organization 2017-10-15 15:51:41 +03:00
Arik Fraimovich
6718081a49 Merge pull request #2033 from getredash/patches
Add: option to disable SQLAlchemy connection pool
2017-10-15 15:51:12 +03:00
Arik Fraimovich
138087861c Add missing import 2017-10-15 15:39:41 +03:00
Arik Fraimovich
9a88cf1743 Merge branch 'patches' of github.com:getredash/redash into patches 2017-10-15 15:37:51 +03:00
Arik Fraimovich
2ca93599ef Merge pull request #2032 from getredash/patches
Add: option to set a time limit on adhoc queries
2017-10-15 15:37:22 +03:00
Arik Fraimovich
ef85a06d60 Fix import. 2017-10-15 15:16:31 +03:00
Arik Fraimovich
f7ffc75ba4 Add: option to disable SQLA connection pool. 2017-10-15 15:09:18 +03:00
Arik Fraimovich
f28eda4174 Merge pull request #2031 from getredash/patches
Add: option to disable sending an invite to a new user
2017-10-15 15:07:17 +03:00
Arik Fraimovich
c5458af1a0 Add: option to set a time limit on adhoc queries 2017-10-15 15:02:34 +03:00
Arik Fraimovich
c28ced14c6 Merge pull request #2030 from getredash/patches
Change: make log format configurable.
2017-10-15 15:00:27 +03:00
Arik Fraimovich
1110e17c4a Add: option to disable sending an invite to a new user 2017-10-15 14:56:26 +03:00
Arik Fraimovich
3b9c31a056 Change: make log format configurable. 2017-10-15 14:52:12 +03:00
Arik Fraimovich
38b655ce3a Merge pull request #2029 from getredash/patches
Change: sort series by name.
2017-10-15 14:36:41 +03:00
Arik Fraimovich
0ec9b73eb2 Change: sort series by name. 2017-10-15 14:33:55 +03:00
Katsuhiko YOSHIDA
b67369daa4 Set auto focus in first input items 2017-10-15 00:02:14 +09:00
Cyriac Thomas
cbc7eee592 fixed stage label typo on sankey and sunburst-sequence editors 2017-10-13 14:44:52 +05:30
Atharva Inamdar
d512cef5af Add support for AWS Redshift Spectrum (external) tables 2017-10-12 14:46:07 +01:00
Atharva Inamdar
c6d1fc103c Merge pull request #1 from getredash/master
merge base master with this fork
2017-10-12 14:43:54 +01:00
Harri Hämäläinen
bf5b31b252 Make invitation token max age configurable 2017-10-12 09:04:45 +03:00
Arik Fraimovich
0c404fa602 Merge pull request #1906 from kitsuyui/add-query-runner-azure-sql-data-warehouse
Query Runner for Azure SQL Data Warehouse
2017-10-11 10:27:02 +03:00
Arik Fraimovich
0ebb6ada3c Merge pull request #2017 from yutannihilation/fix-docker-compose
Fix docker-compose.production.yml
2017-10-11 10:01:25 +03:00
Hiroaki Yutani
d2e519cc3b fix docker-compose.production.yml 2017-10-11 09:53:04 +09:00
Shalom Yerushalmy
9b38f1e81c Added / on api path to prevent wrong url param 2017-10-10 16:28:11 +03:00
Arik Fraimovich
f03c173c57 Merge pull request #2003 from yershalom/create_prometheus_ds
Create prometheus ds
2017-10-10 11:43:59 +03:00
Arik Fraimovich
f89842801f Make URL required. 2017-10-10 11:41:34 +03:00
Arik Fraimovich
56d4ad74a8 Change get_schema to call requests directly. 2017-10-10 11:40:33 +03:00
Arik Fraimovich
334e95afa0 Merge pull request #2004 from modomoto/make_test_run_configurable
Allow setting test file with docker test run
2017-10-09 17:31:25 +03:00
Arik Fraimovich
0443d84848 Merge pull request #2008 from getredash/patches
Change: use outdated queries count stored already in Redis.
2017-10-09 16:43:59 +03:00
Arik Fraimovich
d38f251688 Change: use outdated queries count stored already in Redis. 2017-10-09 16:28:34 +03:00
Arik Fraimovich
890243eb20 Merge pull request #2007 from getredash/patches
Show links based on permissions the user have.
2017-10-09 16:23:34 +03:00
Arik Fraimovich
9fed3266e6 Show links based on permissions the user have. 2017-10-09 16:21:23 +03:00
Shalom Yerushalmy
8fb665be08 Some pep8 styling 2017-10-09 16:19:29 +03:00
Shalom Yerushalmy
c19253648e Changed prometheus name 2017-10-09 16:18:04 +03:00
Mehmet Emin INAC
b8d2df7567 Allow setting nosetests options via environment variable
By setting TEST_ARGS environment variable with -e option of docker-compose
we can set nosetests options to run the tests as we want, like so;

`docker-compose run --rm -e TEST_ARGS="--with-coverage tests/handlers/test_dashboards.py" server tests`
2017-10-09 15:12:04 +02:00
Arik Fraimovich
4603152930 Merge pull request #2006 from getredash/patches
Fix: support UTF8 in MySQL schema
2017-10-09 16:10:41 +03:00
Shalom Yerushalmy
e33e90a69d Remove stftime cause redash already handles this 2017-10-09 16:10:22 +03:00
Arik Fraimovich
f5dcb5d58d Merge pull request #2005 from getredash/patches
Fix: TreasureData queries were failing when returning 0 rows.
2017-10-09 16:09:45 +03:00
Arik Fraimovich
f2f6abe775 Fix: support UTF8 in MySQL schema 2017-10-09 16:09:38 +03:00
Arik Fraimovich
c33189a355 Fix: TreasureData queries were failing when returning 0 rows. 2017-10-09 16:05:58 +03:00
Shalom Yerushalmy
781d997e76 Added redash types 2017-10-09 16:05:11 +03:00
Shalom Yerushalmy
35e02d8043 Changed the timestamp to float 2017-10-09 16:03:35 +03:00
Arik Fraimovich
720af7dabf Update .gitignore 2017-10-09 13:45:58 +03:00
Shalom Yerushalmy
487a8c798c Added back the response.raise_for_status() line 2017-10-09 13:43:43 +03:00
Shalom Yerushalmy
0f580f4540 Changed file due to Arik's request 2017-10-09 13:37:23 +03:00
Arik Fraimovich
cb21024e5c Merge pull request #1981 from yershalom/upgrade_cassandra_version
Upgrade cassandra version
2017-10-09 11:39:03 +03:00
Shalom Yerushalmy
df7b970ff7 Fixed line from 123 char to 120 char due to code climate fail 2017-10-09 11:28:26 +03:00
Shalom Yerushalmy
ff4edb4fbd Added new Promethues data source 2017-10-09 11:20:30 +03:00
Arik Fraimovich
131c9ef036 Merge pull request #1976 from muddydixon/feature/docker-compose-restart-always
users using docker-compose require restart always
2017-10-09 09:36:19 +03:00
Arik Fraimovich
a3071a3ba1 Restart only postgres/redis in dev setup. 2017-10-09 09:36:09 +03:00
Arik Fraimovich
8d5ce85954 Merge pull request #1993 from deecay/box-color
Fix: Setting series color for boxplot
2017-10-09 09:28:09 +03:00
Arik Fraimovich
9d3ae2c34a Merge pull request #1998 from modomoto/fix_revoke_permissons_bug
[FIX] Revoke permission should respect to given grantee and access type.
2017-10-09 09:27:10 +03:00
Mehmet Emin INAC
6d2337b332 Revoke permission should respect to given grantee and access type.
The issue is, if you try to revoke the permission of a user from an
object, all the permissions on this object get removed. The fix is
assigning filtered query object to it's own reference.

According to SQLAlchemy documentation, `filter` method applies to
the **copy** of the query object which means calling filter doesn't
affect the object receiving filter call. For more information;
http://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.filter
2017-10-06 12:52:35 +02:00
Arik Fraimovich
1ef2238d65 Merge pull request #1995 from cclauss/modernize-python2-code
Modernize Python 2 code to get ready for Python 3
2017-10-04 22:29:32 +03:00
muddydixon
521d05279b fixed according to https://github.com/getredash/redash/pull/1976#issuecomment-333370285 2017-10-04 10:01:10 +09:00
cclauss
01e85f218a Modernize Python 2 code to get ready for Python 3 2017-10-04 02:06:53 +02:00
Arik Fraimovich
8af028bc90 Merge pull request #1994 from kravets-levko/fix/eslint-error
Fixed eslint "Cannot read property 'length' of undefined" error
2017-10-03 22:49:43 +03:00
Levko Kravets
85da5fced1 Fixed eslint "Cannot read property 'length' of undefined" error 2017-10-03 21:15:10 +03:00
deecay
038d3b1004 Fix: Setting series color for boxplot 2017-10-03 10:25:07 +09:00
Arik Fraimovich
6cf2b94a10 Merge pull request #1989 from getredash/patches
Add option to set the flask-limiter storage engine
2017-10-02 17:26:27 +03:00
Arik Fraimovich
c930c44e3a Add option to set the flask-limiter storage engine 2017-10-02 17:25:54 +03:00
Arik Fraimovich
0753332ef8 Merge pull request #1988 from getredash/patches
Fix: don't crash query editor when there are unclosed curly brackets.
2017-10-02 17:01:22 +03:00
Arik Fraimovich
ed9e409e17 Fix: don't crash query editor when there are unclosed curly brackets. 2017-10-02 16:58:27 +03:00
Arik Fraimovich
c40fffa107 Merge pull request #1986 from getredash/patches
Fix: error value in charts wasn't displayed if it was 0.
2017-10-02 16:43:27 +03:00
Arik Fraimovich
d597665a86 Fix: error value in charts wasn't displayed if it was 0. 2017-10-02 16:43:06 +03:00
Arik Fraimovich
b0bec26138 Merge pull request #1975 from rohithmenon/bugfix/query_based_param
Bugfix/query based param
2017-10-01 14:32:48 +03:00
Arik Fraimovich
0d44466967 Merge pull request #1984 from getredash/patches
Cohort visualization: make it friendlier to use.
2017-10-01 14:26:47 +03:00
Arik Fraimovich
f4cb62782a Merge pull request #1983 from getredash/patches
Fix: Queries#all_queries was sometimes returning wrong number of queries
2017-10-01 14:26:20 +03:00
Arik Fraimovich
3cadd6731c Fix: tests entering endless loop, due to bad input. 2017-10-01 14:26:04 +03:00
Arik Fraimovich
fc18b84f69 Cohort visualization: make it friendlier to use.
Now it can handle gaps in data, so it's easier to generate the data needed.
2017-10-01 14:24:10 +03:00
Arik Fraimovich
f7fc679427 Merge pull request #1965 from alexmuller/firefox-textarea-keydown-prevent-enter
Prevent line breaks in EditInPlace description when using Firefox
2017-10-01 14:23:29 +03:00
Arik Fraimovich
e674b715ef Merge pull request #1966 from alexmuller/different-markdown-library
Use a different markdown library
2017-10-01 14:22:23 +03:00
Arik Fraimovich
029f6335ed Add missing import. 2017-10-01 14:19:26 +03:00
Shalom Yerushalmy
fb4153add7 Upgarde cassasndra-driver version to 3.11.0 2017-09-28 13:28:21 +03:00
Arik Fraimovich
ada8a1255b Fix: Queries#all_queries was sometimes returning wrong number of queries. 2017-09-27 18:17:57 +03:00
Arik Fraimovich
505f338da9 Merge pull request #1978 from getredash/patches
Fix #1950: record_event fails for api events
2017-09-27 18:08:57 +03:00
Arik Fraimovich
18d9b2eec9 Fix #1950: record_event fails for api events 2017-09-27 18:04:21 +03:00
muddydixon
41a03352b9 users using docker-compose require restart always 2017-09-27 17:26:39 +09:00
Rohith Menon
50f817e265 Merged with upstream 2017-09-26 23:18:50 -07:00
Rohith Menon
04ddb289ee Merged with upstream 2017-09-26 23:13:02 -07:00
Rohith Menon
0152250e14 Bugfix: column.type not set by many data sources [sqlite, postgres etc] 2017-09-26 23:07:18 -07:00
Alex Muller
f574cdd179 Use a different markdown library
`marked` has some security vulnerabilities which have been unresolved
for a while. `markdown` seems to be better supported.
2017-09-22 19:08:07 +01:00
Alex Muller
458f213ea7 Update npm-shrinkwrap
Not sure why this hadn't been updated previously.
2017-09-22 18:42:46 +01:00
Alex Muller
f2caae6eb1 Use event.preventDefault() on EditInPlace textarea
Before this change, pressing enter in Firefox 55 would insert a line
break into the description field and then save it.

This change prevents the line break from being inserted before saving.

There's no change to Chrome's behaviour from this change.
2017-09-22 17:54:02 +01:00
Alex Muller
c01cd89de9 Remove magic numbers from EditInPlace()
This makes it a lot easier to read and figure out what's going on.
2017-09-22 17:51:30 +01:00
Alex Muller
5ea3ed7308 Update redirected link in README 2017-09-22 17:51:10 +01:00
Arik Fraimovich
50eb9a86c9 Merge pull request #1961 from fan-t-endo/writer_encode_errors
UnicodeWriter errors code to environment
2017-09-21 21:39:59 +03:00
Shalom Yerushalmy
12cbfc5d12 Added timeout to cassandra 2017-09-18 12:06:35 +03:00
kitsuy
ba7ed5c6f0 Renaming SQL Server to SQL Server ODBC 2017-09-15 19:50:39 +09:00
kitsuy
4fbfa682fe Import types_map and MSSQLJSONEncoder from mssql. It is same. 2017-09-15 19:24:47 +09:00
kitsuy
fb1139a2ea Remove query encoding.
Do not have to encode query.
`execute()` take an unicode query arguments in pyodbc.
2017-09-15 19:02:20 +09:00
kitsuy
8d8ec1a5f8 Rename to Microsoft SQL Server (ODBC) 2017-09-15 18:31:33 +09:00
kitsuy
7582b3174d Add default driver in configuration_schema to specify driver more easily 2017-09-15 18:28:38 +09:00
kitsuy
154b554ecd Remove tds_version (no longer used) 2017-09-15 18:26:21 +09:00
kitsuy
316e014cfa Rename to SQLServerODBC for more precise. 2017-09-15 18:24:39 +09:00
fan-t-endo
048d8fcb5b UnicodeWriter errors code to environment 2017-09-15 17:56:27 +09:00
Arik Fraimovich
8bbb1cdfd4 Fix: wrong variable name used (dataRow instead of row)
Thanks @wu123456. 

Closes #1926.
2017-09-13 22:20:05 +03:00
Arik Fraimovich
94175b8a52 Merge pull request #1899 from queeno/add_oracle_53_support
Fix #1843: Remove deprecated cx_Oracle types
2017-09-13 18:48:14 +03:00
Simon Aquino
c350b43a5a Update oracle client version 2017-09-13 17:40:44 +02:00
Simon Aquino
b379c13e8b Update supported Oracle version 2017-09-13 17:39:04 +02:00
Simon Aquino
7d91e9d173 Fix #1843: Remove deprecated cx_Oracle types
FIXED_UNICODE, LONG_NCHAR, LONG_UNICODE and UNICODE have been removed
from cx_Oracle version 5.3 and should be removed from the TYPES_MAP.
2017-09-13 17:31:40 +02:00
Arik Fraimovich
1b15ea8af9 Merge pull request #1727 from crowdworks/salesforce-error-message
improve Salesforce error message
2017-09-13 17:39:19 +03:00
Arik Fraimovich
e76efc9cdf Merge pull request #1896 from StantonVentures/textbox_editing_fix
Textbox editing fix
2017-09-13 16:03:22 +03:00
Arik Fraimovich
0a311bf63f Merge pull request #1873 from deecay/fix-custom-js
Custom JS code chart improvements
2017-09-13 15:46:43 +03:00
Arik Fraimovich
5069edb9b1 Merge pull request #1876 from TylerBrock/ssl-postgres
Add SSL configuration option for PostgreSQL
2017-09-13 15:44:07 +03:00
Arik Fraimovich
90162b6331 Merge pull request #1920 from deecay/counter-format-string
Counter value string formatting
2017-09-13 15:39:52 +03:00
Arik Fraimovich
398812a14f Merge pull request #1928 from rohithmenon/feature/query_based_parameter
Feature/query based parameter
2017-09-13 15:35:50 +03:00
Arik Fraimovich
2e44872b49 Merge pull request #1955 from getredash/fix_mysql
MySQL: multiple queries support & connection timeout
2017-09-13 14:48:44 +03:00
Arik Fraimovich
e02fdb3e37 MySQL: add support for multiple queries (returning results only of the last one) 2017-09-13 14:38:56 +03:00
Arik Fraimovich
234edd339c MySQL: add connection timeout for bad hosts 2017-09-13 14:38:28 +03:00
Arik Fraimovich
e5cbdf3036 Merge pull request #1946 from Posnet/select-all
Add ability to easily select all for multi-filter
2017-09-13 14:29:15 +03:00
Arik Fraimovich
9b85890204 Merge pull request #1954 from labradorcouk/master
Upgraded dql version to 0.5.24
2017-09-13 14:26:53 +03:00
Antonio Terreno
6295e88d43 Upgraded dql version to 0.5.24 - this allows to query tables in dynamo which have keys with dashes in the name 2017-09-13 09:12:44 +01:00
Arik Fraimovich
7796a57d43 Merge pull request #1930 from mfouilleul/master
Cassandra: get_schema support for both C* 2.x and 3.x, support for SortedSet type serialization.
2017-09-12 15:59:36 +03:00
Rohith Menon
df7fd13bfd Hovertext length (#3)
* Namelength for hoverlabel to avoid truncation

* Update npm-shinkwrap.json
2017-09-06 21:08:18 -07:00
Rohith Menon
6a5a843478 Merge branch 'master' of https://github.com/getredash/redash 2017-09-05 10:35:40 -07:00
Alec Posney
7d4fb280ba Add ability to easily select all for multi-filter
The multi filter option is useful but lacking in an easy easy way to
select all values. I have added in a psudo option '*' that when selected
automatically fills out the mutli-select with all possible filters.

I have also added in a second psudo option '-' which becomes available
_if_ the multi-filter has all possible values selected.
This makes it easy to clear the multi-filter.
2017-09-05 13:31:04 +10:00
Arik Fraimovich
2a22b98c77 Merge pull request #1944 from getredash/fix_permissions
Fix: collaborators couldn't edit visualizations or schedule
2017-09-03 15:00:27 +03:00
Arik Fraimovich
6b56e4a3e3 Allow collaborators to update query schedule. 2017-09-03 14:31:42 +03:00
Arik Fraimovich
47fc6612bf Allow collaborators to create, delete and edit visualizations. 2017-09-03 14:28:34 +03:00
Rohith Menon
f3e5c22c07 Merge/query based parameter (#2)
* Feature: Query based parameter (drop-down)

* Restrict to string column for query parameter

* Fix lint errors

* Fix html in paramters.html

* Addressed comments from @arikfr
2017-08-23 20:48:02 -07:00
Maxime Fouilleul
b42d2c5784 Fix codeclimate notices (trailing space) 2017-08-17 18:19:19 +02:00
Maxime Fouilleul
478a86a892 Fix codeclimate notices (SQL) 2017-08-17 18:17:53 +02:00
Maxime Fouilleul
9e0205d148 Improve and fix cassandra query runner 2017-08-17 18:10:16 +02:00
Rohith Menon
59b7961bcd Addressed comments from @arikfr 2017-08-16 16:04:00 -07:00
Arik Fraimovich
5b54a777d9 Merge pull request #1863 from 44px/ng-annotate-deprecation
Replace deprecated ng-annotate with babel plugin
2017-08-16 16:21:23 +03:00
Arik Fraimovich
3af9b333a8 Merge pull request #1898 from StantonVentures/security_lib_updates_7_27_2017
update libraries
2017-08-16 16:19:57 +03:00
Arik Fraimovich
dcaecdbe16 Merge pull request #1921 from deecay/error-bar-color
Fix: error bar color in sync with series color
2017-08-16 16:19:04 +03:00
Arik Fraimovich
3aa7d86699 Update bootstrap.sh 2017-08-16 16:16:00 +03:00
Rohith Menon
feab2a7e7b Fix html in paramters.html 2017-08-15 13:53:55 -07:00
Rohith Menon
d18220c1af Feature/query based parameter (#1)
* Feature: Query based parameter (drop-down)

* Restrict to string column for query parameter

* Fix lint errors
2017-08-14 21:47:48 -07:00
Rohith Menon
8074a91b29 Fix lint errors 2017-08-14 09:20:58 -07:00
Rohith Menon
72560d985f Restrict to string column for query parameter 2017-08-14 09:15:28 -07:00
Rohith Menon
ff2c8524de Feature: Query based parameter (drop-down) 2017-08-14 00:38:41 -07:00
Alison
1bdea11fe3 updates based on PR comments 2017-08-11 21:17:30 -05:00
Arik Fraimovich
a7bed64707 Merge pull request #1836 from amarjayr/master
LDAP (Active Directory) implementation
2017-08-09 20:46:17 +03:00
Arik Fraimovich
dc969fe0b5 Bump version. 2017-08-09 20:45:26 +03:00
Amar Ramachandran
588c868060 Make ldap3 requirement optional 2017-08-09 10:32:44 -07:00
Arik Fraimovich
89de5f2a18 Fix #1776: restart was using wrong command on new AMI builds 2017-08-08 14:21:43 +03:00
Arik Fraimovich
fe32877864 Update CHANGELOG.md 2017-08-08 13:59:31 +03:00
deecay
e739f90405 Fix: error bar color in sync with series color 2017-08-08 18:27:42 +09:00
deecay
a07135c638 Move counter visualiation formatting controls to tab 2017-08-08 17:45:37 +09:00
Arik Fraimovich
6b531ac568 Merge pull request #1891 from yoavbls/master
Update bootstrap.sh to latest release
2017-08-06 22:06:15 +03:00
Arik Fraimovich
5bce695fcc Remove unused import 2017-08-06 21:02:55 +03:00
Arik Fraimovich
ba910280a1 Mark query/visualization view events coming from dashboards 2017-08-06 21:02:11 +03:00
Arik Fraimovich
2f386781d7 Merge pull request #1915 from getredash/patches
Add 12 & 24 hours refresh rate option to dashboards.
2017-08-06 20:53:43 +03:00
Arik Fraimovich
4c70349ee1 Add 12 & 24 hours refresh rate option to dashboards. 2017-08-06 20:53:07 +03:00
Arik Fraimovich
74b9c51dea Merge pull request #1914 from getredash/patches
Enable memory optimization for Excel exporter.
2017-08-06 20:51:47 +03:00
Arik Fraimovich
d95c22fa24 Enable memory optimizatino for Excel exporter. 2017-08-06 20:51:05 +03:00
Arik Fraimovich
3a7611309c Merge pull request #1913 from getredash/patches
Fix: pivottable not updating after first save
2017-08-06 20:49:45 +03:00
Arik Fraimovich
5281d6c281 Merge pull request #1912 from getredash/patches
Snowflake support is no longer enabled by default
2017-08-06 20:47:56 +03:00
Arik Fraimovich
5bc1e71143 Fix: pivottable not updating after first save 2017-08-06 20:47:52 +03:00
Arik Fraimovich
31ebfb80d7 Snowflake support is no longer enabled by default 2017-08-06 20:45:10 +03:00
Arik Fraimovich
1f8ed8a6c2 Merge pull request #1911 from getredash/patches
Add SQLAlchemy pool settings.
2017-08-06 20:40:51 +03:00
Arik Fraimovich
3d10718650 Merge branch 'master' into patches 2017-08-06 20:40:34 +03:00
Arik Fraimovich
4a4ee49187 Add SQLAlchemy pool settings. 2017-08-06 20:39:37 +03:00
Arik Fraimovich
7bf7b00633 Merge pull request #1910 from getredash/patches
Fix: PostgreSQL passwords with spaces were not supported.
2017-08-06 20:38:16 +03:00
Arik Fraimovich
68e3fe65ba Add: support for category type y axis. 2017-08-06 20:37:39 +03:00
Arik Fraimovich
7fe096fba8 Fix: PostgreSQL passwords with spaces were not supported.
Closes #1056.
2017-08-06 20:34:58 +03:00
Arik Fraimovich
f80951457d Fix: handle the case of query's data source is null 2017-08-06 20:32:51 +03:00
Arik Fraimovich
1da165edc1 Add hard timeout on refresh schema job 2017-08-06 20:32:00 +03:00
Arik Fraimovich
61c7c556b6 Update CHANGELOG.
Thank you @laughingman7743, @ziahamza, @yamamanx, @miketheman, @eyalzek, @alexpekurovsky, @44px, @amarjayr, @unixwitch, @danielerapati, @yershalom, @msnider and @alison985.
2017-08-06 15:53:48 +03:00
Arik Fraimovich
6170c48ed2 Merge pull request #1874 from alexpekurovsky/master
Setting optional limit for done tasks
2017-08-06 14:33:42 +03:00
kitsuy
974f69aecf Query Runner for Azure SQL Data Warehouse
- This is almost copied from mssql.py.
- Microsoft's driver installation is here: https://www.microsoft.com/en-us/sql-server/developer-get-started/node/ubuntu/
2017-08-03 13:22:45 +09:00
deecay
1a8078ab03 Fix: Custom code keeps appending trace per refresh 2017-07-31 19:22:25 +09:00
Alison
1bc8d586c3 update libraries
Based on pyup auto-PR httplib2 and cryptography needed updating which
necessitated updating pyOpenSSL as well.
2017-07-27 21:15:39 -05:00
Alison
a795f1463b Fixes dashboard textbox editing
Combines mozilla/redash PR’s 86 and 95.

There was a bug that saved textbox content on a dashboard when you
tried to close without saving. This fixes it.
2017-07-26 23:15:22 -05:00
Alison
aae77a8b25 Merge remote-tracking branch 'getredash/master' 2017-07-25 15:19:24 -05:00
deecay
c278209883 Counter visualiation formatting 2017-07-25 19:26:52 +09:00
alexpekurovsky
7f8ef2a050 Applying better quality code by @arikfr 2017-07-25 10:19:08 +03:00
alexpekurovsky
d21e11ba33 Creating limit filter for each task state and global limit 2017-07-24 10:13:32 +03:00
alexpekurovsky
1fc990f11a Setting limit for all task states 2017-07-23 13:50:23 +03:00
yoavbls
a09a767641 Update bootstrap.sh to latest release 2017-07-22 17:55:57 +03:00
Arik Fraimovich
12ef64f10d Merge pull request #1889 from StantonVentures/upstream_1888
Send alert ID to UI
2017-07-21 21:04:44 +03:00
Alison
776e52a77c Send alert ID to UI
This will allow alert detail page links on the alert list page to work again.
2017-07-21 12:54:21 -05:00
Tyler Brock
6d8880c10d Add SSL configuration option for PostgreSQL 2017-07-19 11:14:40 -07:00
Arik Fraimovich
5d5af369e6 Merge pull request #1870 from eyalzek/log-stream-config
Add the option to configure log stream
2017-07-19 15:40:26 +03:00
Arik Fraimovich
1d7fef4f7d Merge pull request #1871 from unixwitch/configurable-web-worker-count
docker: make gunicorn worker count configurable
2017-07-19 15:39:27 +03:00
Arik Fraimovich
35cb0bc805 Remove REDASH_WEB_WORKERS from docker-compose.yml.
In development (`dev_server`) we don't use this variable.
2017-07-19 15:39:16 +03:00
Arik Fraimovich
5310385f15 Merge pull request #1884 from miketheman/miketheman/query_link
Add link to query page from admin view
2017-07-19 15:35:08 +03:00
Mike Fiedler
41c791ff42 Add link to query page from admin view
When we have a long0running query, it is useful to look up what that
query definition is. Having a link directly from the admin page cuts
down on the manual step of opying the query id and pasting that into the
address bar.

Signed-off-by: Mike Fiedler <miketheman@gmail.com>
2017-07-18 10:54:13 -04:00
alexpekurovsky
9b3f910326 Setting optional limit for done tasks 2017-07-12 15:23:01 +03:00
deecay
aacc4b7b46 Fix: Custom code keeps appending trace per refresh 2017-07-11 17:44:05 +09:00
deecay
605a70d554 Add toggle for automatically updating graph 2017-07-11 17:31:16 +09:00
deecay
73466dc0e0 Make custom js textarea resizable 2017-07-11 15:43:09 +09:00
deecay
3fd90c6289 Fix: Custom code didn't load into editor 2017-07-11 10:15:47 +09:00
eyalzek
97624a3e2c Add the option to configure log stream
by default when using python's `logging` module and a `StreamHandler`,
the stream is directed to `sys.stderr`
(https://docs.python.org/2/library/logging.handlers.html)

By setting the `REDASH_LOG_STDOUT` environment variable to `true` we
enable the option to stream the logs to `sys.stdout`. Setting this
configuration option to `false` (or leaving it as default) will
initialize the `StreamHandler` to `sys.stderr` - meaning the original
behavior remains unchanged.
2017-07-07 23:09:32 +02:00
Liss Tarnell
d69c9409dd docker: make gunicorn worker count configurable
Allow $REDASH_WEB_WORKERS to be set in the environment to change the
number of Gunicorn workers that are started (currently hardcoded to
four).  If not set, the default is four, so this will not affect
existing users at all.

Documentated by example in docker-compose example manifests.
2017-07-06 18:45:47 +01:00
Arik Fraimovich
5bb5f46c02 Merge pull request #1860 from AmarJayR/patch-1
Remove /forgot endpoint if REDASH_PASSWORD_LOGIN_ENABLED is false
2017-07-03 23:22:27 +03:00
Arik Fraimovich
2b6fe22b3f Merge pull request #1865 from msnider/bug/salesforce-sandbox
Fix bug getting the Salesforce sandbox parameter
2017-07-03 23:21:48 +03:00
Alexander Shepelin
53f0716aca Replace deprecated ng-annotate with babel plugin 2017-07-02 14:22:36 +03:00
Arik Fraimovich
fd798ddcf5 Merge pull request #1833 from 44px/webpack2
Update to Webpack 2
2017-07-01 12:39:53 +03:00
Matt Snider
812177a4e0 Fix bug getting the Salesforce sandbox parameter 2017-06-29 20:54:34 -05:00
Amar Ramachandran
b9e08897ac Move ldap auth logic to function 2017-06-29 11:33:57 -07:00
Amar Ramachandran
e445fa436e 404 on forgot endpoint if password disabled 2017-06-29 11:29:58 -07:00
Arik Fraimovich
797a0a30ca Merge pull request #1859 from getredash/fix/alerts
Add: ability to customize Athena configuration schema
2017-06-29 16:06:02 +03:00
Arik Fraimovich
dfd16f3d7a Add: ability to customize Athena configuration schema 2017-06-29 16:04:50 +03:00
Arik Fraimovich
a80aae0ec7 Merge pull request #1858 from getredash/fix/alerts
Add CLI command to open IPython shell
2017-06-29 15:23:34 +03:00
Arik Fraimovich
c8ad866a53 Add CLI command to open IPython shell 2017-06-29 12:21:09 +03:00
Arik Fraimovich
fe2f08cfd6 Merge pull request #1857 from getredash/fix/alerts
Angular configuration improvements
2017-06-29 12:13:22 +03:00
Arik Fraimovich
f64769cc80 Disable Angular debug info (should improve performance) 2017-06-29 12:11:34 +03:00
Arik Fraimovich
831dfe6c8d Enable strict checking for Angular DI 2017-06-29 12:11:15 +03:00
Arik Fraimovich
3b4da81ec6 Merge pull request #1856 from getredash/fix/alerts
Add: ability to set dashboard level filters from UI
2017-06-29 11:03:57 +03:00
Arik Fraimovich
248c540543 Add: ability to set dashboard level filters from UI
Closes #1855.
2017-06-29 10:57:35 +03:00
Arik Fraimovich
0fb0ba6473 Merge pull request #1853 from getredash/fix/alerts
Update boto version to support Athena
2017-06-29 09:49:42 +03:00
Arik Fraimovich
019a09945e Update boto version to support Athena 2017-06-29 09:49:06 +03:00
Arik Fraimovich
520a5f8fa4 Merge pull request #1839 from yamamanx/writer_encode
UnicodeWriter character code to environment
2017-06-29 09:44:41 +03:00
mitsuhiro_yamashita
f840681377 change this to WRITER_ENCODING and REDASH_CSV_WRITER_ENCODING 2017-06-29 08:21:22 +09:00
Amar Ramachandran
300421792c Add log error when LDAP connection fails
Integrate ldap login in login template
2017-06-28 13:32:24 -07:00
Arik Fraimovich
0ab25c317c Merge pull request #1852 from getredash/fix/alerts
Allow sorting alerts list
2017-06-28 22:15:40 +03:00
Arik Fraimovich
c8adf322a9 Allow sorting alerts 2017-06-28 22:11:05 +03:00
Arik Fraimovich
fae1e7152a Merge pull request #1851 from getredash/fix/alerts
Don't allow saving dashboard with empty name
2017-06-28 19:30:23 +03:00
Arik Fraimovich
e543e0c466 Don't allow saving dashboard with empty name 2017-06-28 19:22:59 +03:00
Arik Fraimovich
d61002a544 Merge pull request #1850 from getredash/fix/alerts
No need to load user for recent queries.
2017-06-28 19:22:49 +03:00
Arik Fraimovich
92f93f8ff6 No need to load user for recent queries. 2017-06-28 19:10:26 +03:00
Arik Fraimovich
bf17bdc32d Merge pull request #1793 from danielerapati/fix/alert_with_no_query_result
safeguard alerts against empty query results
2017-06-28 17:31:43 +03:00
Arik Fraimovich
70292c888c Merge pull request #1524 from rainforestapp/master
Allow params once again in embeds
2017-06-28 17:29:14 +03:00
Arik Fraimovich
69cb5b72e1 Merge pull request #1849 from getredash/fix/alerts
Show friendly error message in case of duplicate data source name.
2017-06-28 17:25:13 +03:00
Arik Fraimovich
470d2ad359 Show friendly error message in case of duplicate data source name. 2017-06-28 17:19:17 +03:00
Arik Fraimovich
e85fa2a42c Merge pull request #1848 from getredash/fix/alerts
Fix: when setting rearm on a new alert, it wasn't persisted.
2017-06-28 16:53:17 +03:00
Arik Fraimovich
42116abcb3 Fix: when setting rearm on a new alert, it wasn't persisted. 2017-06-28 16:34:50 +03:00
Arik Fraimovich
2e0b930192 Merge pull request #1847 from getredash/fix/alerts
Fix: alert destination details were not updating.
2017-06-28 16:30:14 +03:00
Arik Fraimovich
24ba110965 Fix: alert destination details were not updating.
Closes #1842.
2017-06-28 16:01:54 +03:00
Arik Fraimovich
e1eeb67025 Merge pull request #1846 from getredash/athena-updates
Athena updates: bring back ability to disable annotations and disable formatter
2017-06-28 14:19:56 +03:00
Arik Fraimovich
71c9cbd5a4 Missing os import. 2017-06-28 12:40:11 +03:00
Arik Fraimovich
e1ac5bb038 Athena: bring back the option to disable query annotations. 2017-06-28 12:30:37 +03:00
Arik Fraimovich
c2e84c92c6 Athena: use simple formatter to avoid the need to escape "%" character. 2017-06-28 12:28:59 +03:00
Arik Fraimovich
090962d09c Merge pull request #1841 from laughingman7743/fix_query_cancellation_condition
Fix query cancellation condition for Athena query runner
2017-06-25 10:29:24 +03:00
laughingman7743
df945a12b0 Fix query cancellation condition 2017-06-24 20:53:35 +09:00
mitsuhiro-yamashita
3f99f0c6d5 Expected 2 blank lines 2017-06-22 23:38:01 +09:00
mitsuhiro-yamashita
4706bebde0 UnicodeWriter character code to environment 2017-06-22 23:34:21 +09:00
Amar Ramachandran
85f729260b Clean up file 2017-06-20 15:33:32 -07:00
Amar Ramachandran
8bf2c15db8 Add ldap auth logic 2017-06-20 15:33:32 -07:00
Amar Ramachandran
9ea4784f87 Add ldap3 requirement 2017-06-20 15:33:32 -07:00
Amar Ramachandran
8be9613640 Add ldap env. config settings 2017-06-20 15:33:32 -07:00
Amar Ramachandran
b611c98112 Add ldap blueprint 2017-06-20 15:33:32 -07:00
Alexander Shepelin
ad3dbad8ac remove ng-annotate-loader from npm-shrinkwrap since it moved to dev dependencies 2017-06-20 23:14:24 +03:00
Alexander Shepelin
62c8bd3531 fix angular core libraries version mismatch 2017-06-20 22:43:59 +03:00
Alexander Shepelin
c8d66b3335 bring back node-sass, update file-loader 2017-06-20 22:43:58 +03:00
Alexander Shepelin
0217d419d1 update ng-annotate loader, move it to devDependencies 2017-06-20 22:43:58 +03:00
Alexander Shepelin
45f448e0d1 update dev server 2017-06-20 22:43:58 +03:00
Alexander Shepelin
ee4b05eb98 update css-related loaders 2017-06-20 22:43:58 +03:00
Alexander Shepelin
202c53c7d7 update config file to webpack2 format 2017-06-20 22:43:41 +03:00
Arik Fraimovich
9816403c45 Merge pull request #1834 from AmarJayR/patch-1
Fix template variable typo
2017-06-20 08:52:08 +03:00
Amar Ramachandran
b6a1178499 Fix template variable typo
Should be email instead of username (see redash/templates/login.html:47)
2017-06-19 17:10:40 -07:00
Arik Fraimovich
2fe6110e0f Merge pull request #1831 from getredash/feature_auto_publish
Fix: if column had no type it would use previous column's type
2017-06-18 13:02:19 +03:00
Arik Fraimovich
c4e18bb481 Fix: if column had no type it would use previous column's type 2017-06-18 12:31:32 +03:00
Arik Fraimovich
add8f0eeeb Merge pull request #1830 from getredash/feature_auto_publish
Change: auto publish named queries
2017-06-18 12:30:32 +03:00
Arik Fraimovich
694d971df9 Auto publish named queries 2017-06-18 12:12:02 +03:00
Arik Fraimovich
36c93ce212 Merge pull request #1809 from laughingman7743/impl_athena_query_runner_using_restapi
Implement Athena query runner using RestAPI
2017-06-15 15:02:23 +03:00
laughingman7743
35fe1f23e3 Fix configuration schema key name 2017-06-15 20:59:50 +09:00
Arik Fraimovich
2517abb27f Merge pull request #1821 from getredash/fix_api_session
Fix: /api/session API call wasn't working when multi tenancy enabled
2017-06-14 12:13:04 +03:00
Arik Fraimovich
1d749a83e1 Fix: /api/session API call wasn't working when multi tenancy enabled 2017-06-14 12:06:18 +03:00
Arik Fraimovich
fc50a7b9bb Remove outdated comment 2017-06-14 12:05:49 +03:00
Arik Fraimovich
6f72d456d2 Remove unused code 2017-06-14 12:05:49 +03:00
Arik Fraimovich
1182f8c6b0 Merge pull request #1813 from 44px/parameters-styles
UI change: add some space between parameters
2017-06-13 11:24:27 +03:00
Arik Fraimovich
f090f947b7 Merge pull request #1820 from getredash/patches
Fix: clear null values from options dictionary
2017-06-13 09:52:24 +03:00
Arik Fraimovich
a8246471f4 Fix: clear null values from options dictionary 2017-06-13 09:42:20 +03:00
Arik Fraimovich
229c33939c Merge pull request #1819 from getredash/patches
Fix: form component was inserting empty values
2017-06-13 09:41:24 +03:00
Arik Fraimovich
ef2eaf1fa9 Fix: form component was inserting empty values 2017-06-13 09:39:33 +03:00
Arik Fraimovich
d30f4f155a Merge pull request #1818 from getredash/patches
Fix: BigQuery wasn't loading due to bad import
2017-06-13 09:38:53 +03:00
Arik Fraimovich
b2e5df6af2 Fix: BigQuery wasn't loading due to bad import 2017-06-13 09:38:39 +03:00
Arik Fraimovich
0470cd6592 Stupid typo fix 2017-06-12 12:41:44 +03:00
Arik Fraimovich
a517dad456 Merge pull request #1817 from getredash/metrics
Metrics code update
2017-06-12 12:40:32 +03:00
Alexander Shepelin
789ef1614d Merge remote-tracking branch 'upstream/master' into parameters-styles 2017-06-12 12:14:17 +03:00
Alexander Shepelin
3dfab5009c put parameter label on top of input 2017-06-12 12:02:03 +03:00
Arik Fraimovich
7d5d7c4a6b Change: report endpoints without dots for metrics 2017-06-12 11:35:44 +03:00
Arik Fraimovich
5056d2fa90 Fix: table name wasn't found for count queries. 2017-06-12 11:35:05 +03:00
Arik Fraimovich
1fad874dee Change: redirect to / when org not found 2017-06-12 09:47:08 +03:00
Arik Fraimovich
a2c79367de Merge pull request #1812 from 44px/fix-search-input-style
UI change: same view for input on search result page as in header
2017-06-11 14:39:00 +03:00
Arik Fraimovich
bcf129e646 Merge pull request #1814 from yershalom/master
Fixed cassandra DS bug by adding port to connection string
2017-06-11 14:38:18 +03:00
Shalom Yerushalmy
94077ccafd Fixed cassandra DS bug by adding port to connection string 2017-06-08 15:44:09 +03:00
Alexander Shepelin
411ef7bd00 Add some space between parameters 2017-06-07 23:27:39 +03:00
Alexander Shepelin
6b22c2c541 Same view for input on search result page as in header 2017-06-07 23:03:05 +03:00
laughingman7743
e385a147f6 Add Athena query runner as default query runners 2017-06-06 22:01:44 +09:00
laughingman7743
ad69a6be3f Add secret field 2017-06-05 23:28:28 +09:00
laughingman7743
a6c45da2ca Add KeyboardInterrupt handling 2017-06-05 23:26:56 +09:00
laughingman7743
d5c4d9336f Fix configuration schema name to same name as previous query runner 2017-06-05 23:25:49 +09:00
laughingman7743
c1f8e2a4e0 Add query runner name 2017-06-05 23:03:32 +09:00
laughingman7743
fe42195b5a Implement Athena query runner using RestAPI 2017-06-05 22:09:30 +09:00
Arik Fraimovich
6a0bb82f3c Merge pull request #1807 from 44px/fix-setup-redirect
Fix redirect to /setup on the last setup step
2017-06-05 07:42:51 +03:00
Alexander Shepelin
69825e001f fix redirect to /setup after install 2017-06-04 17:24:47 +03:00
Arik Fraimovich
ad8571f2e3 Merge pull request #1803 from getredash/fix_1725
Fix: delete data source doesn't work when query results referenced by queries.
2017-06-02 18:17:52 +03:00
Arik Fraimovich
fbd3b92ba0 Fix: delete data source doesn't work when query results referenced by queries. 2017-06-02 18:11:34 +03:00
Arik Fraimovich
4f6c433f1b Merge pull request #1796 from shotat/feature/re-dash
Fix deprecated `re-dash` expressions in CONTRIBUTING.md
2017-06-01 14:46:39 +03:00
Arik Fraimovich
412f469035 Merge pull request #1800 from hfm/fix-changelog
Fix CHANGELOG markdown
2017-06-01 14:37:48 +03:00
OKUMURA Takahiro
eee38557d1 Fix markdown
the changelog markdown before v1.0 is broken.
2017-06-01 14:46:18 +09:00
Daniele Rapati
23cb92cf6d safeguard alerts against empty query results
alert will revert to UNKNOWN_STATE in case of no data
2017-05-30 22:30:32 +01:00
shotat
108137bd7e fix CONTRIBUTING.md 2017-05-30 23:51:21 +09:00
hamza zia
6bc53c3638 use default embed.py 2017-05-30 16:33:56 +05:00
hamza zia
e54fff402e fixed import bug 2017-05-30 16:32:09 +05:00
hamza zia
8d125354d2 merged changes 2017-05-30 14:19:18 +05:00
hamza zia
fc96e14a8f Only run queries when given parameters 2017-05-30 12:26:09 +05:00
hamza zia
178dfa59c1 Merge branch 'master' into master 2017-05-30 09:09:37 +05:00
Arik Fraimovich
8719de7120 Merge pull request #1792 from getredash/fix_1725
Fix: remove unneeded calls to app_context()
2017-05-29 23:37:27 +03:00
Arik Fraimovich
af8bdf4fd1 Fix: remove unneeded calls to app_context()
When the extra app_context was popped from the stack, it was
triggering flask-sqlalchemy's teardown handler, which was removing
the session causing objects to become detached before they should
be.
2017-05-29 16:41:42 +03:00
Arik Fraimovich
764e347b74 Merge pull request #1736 from shimpeko/extend_redis_expiry_time
Extend expiry time to prevent duplicate execution
2017-05-23 10:53:10 +03:00
Arik Fraimovich
2f1b1a69bd Add CHANGELOG entry. 2017-05-23 10:52:54 +03:00
Arik Fraimovich
b2fea428dd Fix: use correct APIs 2017-05-23 09:19:14 +03:00
Shimpei Kodama
1f1d7996ec Shorten celery task expiry time to avoid too many redis objects 2017-05-23 12:24:03 +09:00
Arik Fraimovich
47dc9a136f Merge pull request #1779 from getredash/patches
Add: "dumb" recents option
2017-05-22 23:15:38 +03:00
Arik Fraimovich
6ed86d9ce5 Merge pull request #1778 from getredash/patches
Upgrade Snowflake connector
2017-05-22 14:26:57 +03:00
Arik Fraimovich
8e760705a6 Add: "dumb" recents option
In some cases showing recent queries/dashboards based on events becomes
too "expensive" in terms of database resources. This is a fallback option
to show recently updated queries/dashboards instead.
2017-05-22 14:26:26 +03:00
Arik Fraimovich
9c606b9660 Update Snowflake connector 2017-05-22 14:18:34 +03:00
Arik Fraimovich
f65b3223f4 Remove chatty log lines 2017-05-22 14:17:27 +03:00
Arik Fraimovich
e85e962466 Update CHANGELOG and bump version 2017-05-18 15:37:18 +03:00
Arik Fraimovich
a7df809c4d Merge pull request #1774 from getredash/patches
Scheduled queries improvements:
2017-05-18 15:17:03 +03:00
Arik Fraimovich
beb29c66c2 Scheduled queries improvements:
* Schedule queries with parameters using the default value.
* Keep track of last execution (including failed ones) for scheduling purposes.
2017-05-18 15:10:19 +03:00
Arik Fraimovich
749171b186 Merge pull request #1746 from alexanderlz/master
[Data Sources] Add: MemSQL query runner
2017-05-18 14:01:53 +03:00
Arik Fraimovich
40a8187b1e Merge pull request #1773 from getredash/patches
Split refresh schemas into separate tasks and add a timeout.
2017-05-18 14:00:26 +03:00
Alexander Leibzon
6b7234c910 fixes 2017-05-18 14:00:13 +03:00
Arik Fraimovich
3807510bfe Split refresh schemas into separate tasks and add a timeout. 2017-05-18 13:39:34 +03:00
Arik Fraimovich
3650617928 Merge pull request #1772 from getredash/patches
Upgrade Sentry client.
2017-05-18 13:33:26 +03:00
Arik Fraimovich
d60843fa5b Sentry: don't install logging hook. 2017-05-18 13:26:36 +03:00
Arik Fraimovich
5a5917a04a Sentry: upgrade client version. 2017-05-18 13:24:53 +03:00
Arik Fraimovich
ae642fddf7 Merge pull request #1771 from getredash/patches
Show API key in a dialog instead of alert
2017-05-18 13:21:28 +03:00
Arik Fraimovich
b4a8fb76de Merge pull request #1770 from getredash/patches
UI changes
2017-05-18 12:58:58 +03:00
Arik Fraimovich
b885ccb09c Show API Key in a modal dialog instead of alert. 2017-05-18 11:53:16 +03:00
Arik Fraimovich
b70c329307 Dynamic form: change order of name and type (type first now). 2017-05-18 11:37:54 +03:00
Arik Fraimovich
1aa54543ed Retry reload of query results if it had an error 2017-05-18 11:28:40 +03:00
Arik Fraimovich
e050c085df Counter: support negative indexes to iterate from the end of the results. 2017-05-18 11:28:25 +03:00
Arik Fraimovich
62962d28ca Only split columns with __/:: that end with filter/MultiFilter. 2017-05-18 11:15:21 +03:00
Arik Fraimovich
d7c502eb50 Query results: better type guessing on the client side. 2017-05-18 11:13:11 +03:00
Arik Fraimovich
dd7841dc15 Merge pull request #1769 from getredash/patches
Improvements to background jobs
2017-05-18 11:09:35 +03:00
Arik Fraimovich
14c751b39e Cohort: handle the case where the value/total might be strings. 2017-05-18 11:07:39 +03:00
Arik Fraimovich
f4297ff3b0 Handle the case when the task object might not exist. 2017-05-18 09:36:21 +03:00
Arik Fraimovich
79ffbbbe4b Don't include paused datasource's queries in outdated queries count. 2017-05-18 09:34:32 +03:00
Arik Fraimovich
4c1cb037a0 Include Celery task name in statsd metrics. 2017-05-18 09:33:28 +03:00
Arik Fraimovich
f679dc7562 Put a limit on how many keys we remove at a time to make sure it
can handle large lists.
2017-05-18 09:31:04 +03:00
Arik Fraimovich
76470b9f09 Fix: don't remove locks for queries with task status of PENDING.
It's possible the Celery metadata object was expired, but the task
is still running (which will result in PENDING status when querying
the AsyncResult object).
2017-05-18 09:25:44 +03:00
Arik Fraimovich
3edec570f1 Merge pull request #1767 from getredash/patches
Google Spreadsheets: add timeout to request.
2017-05-17 18:53:44 +03:00
Arik Fraimovich
a2e07b46f2 Merge pull request #1756 from deecay/dashboard-list-sort
Change: Sort dashboard-list in /dashboards
2017-05-17 18:36:58 +03:00
Arik Fraimovich
326a80895c Merge pull request #1615 from deecay/pivot_hide_control
Add: option to hide pivot table controls
2017-05-17 18:35:31 +03:00
Arik Fraimovich
d200cc7405 Merge pull request #1690 from deecay/sqlite-unicode-error
Fix: SQLite utf-8 error messages
2017-05-17 18:34:10 +03:00
Arik Fraimovich
c4dff40e1d Merge pull request #1760 from rockwotj/master
Add: static enum parameter type.
2017-05-17 18:17:56 +03:00
Arik Fraimovich
21636c4d65 Rename Enum to Dropdown List. 2017-05-17 18:17:08 +03:00
Arik Fraimovich
52084c322f Google Spreadsheets: add timeout to request. 2017-05-17 18:15:04 +03:00
Arik Fraimovich
5fd2dadef4 Merge pull request #1765 from getredash/patches
Multiple improvements to data sources (MySQL, InfluxDB, BigQuery, MongoDB, TreasureData, Postgres, ElasticSearch, MSSQL, Google Spreadsheets)
2017-05-16 12:08:23 +03:00
Arik Fraimovich
f312e89323 Google Analytics: support for mcf queries & better errors. 2017-05-16 11:25:11 +03:00
Tyler Rockwood
0046cfa3ee Make it work 2017-05-15 14:41:20 -07:00
Tyler Rockwood
4ecc8da398 Addressing comments 2017-05-15 13:52:12 -07:00
Arik Fraimovich
382431e34b MySQL: support for RDS MySQL and SSL 2017-05-15 17:22:55 +03:00
Arik Fraimovich
6023dc5f3d MySQL: option to hide SSL settings. 2017-05-15 17:15:11 +03:00
Arik Fraimovich
2d38b38a7d Google Spreadsheets: handle distant future dates. 2017-05-15 16:55:57 +03:00
Arik Fraimovich
3513d84bb8 Less verbose logging in data sources. 2017-05-15 16:53:47 +03:00
Arik Fraimovich
24cd55f5cc Reduce log level for apiclient. 2017-05-15 16:51:39 +03:00
Arik Fraimovich
0bce6996bf MSSQL: return integers as floats. 2017-05-15 16:50:41 +03:00
Arik Fraimovich
6edfdfba63 BigQuery: remove print statement. 2017-05-15 16:49:10 +03:00
Arik Fraimovich
ddbbe1267a InfluxDB: simpler test connection query (show databases requries admin) 2017-05-15 16:48:16 +03:00
Arik Fraimovich
7fada5d5f7 ElasticSearch: debug_enabled should be false by default 2017-05-15 16:47:21 +03:00
Arik Fraimovich
c3f5a37e21 Postgres: support for loading materialized views in schema. 2017-05-15 16:29:03 +03:00
Arik Fraimovich
a76c87b3ae MongoDB: add $oids JSON extension. 2017-05-15 16:27:27 +03:00
Arik Fraimovich
4a0612328e TreasureData: improve error handling and upgrade client. 2017-05-15 16:25:18 +03:00
Arik Fraimovich
931c322ad7 Merge pull request #1763 from getredash/multi_org
Support for MULTI_ORG in v1
2017-05-15 12:58:16 +03:00
Arik Fraimovich
58c61641d3 Merge pull request #1759 from alexanderlz/mongo_csv_download_fix
Change: ignore additional columns in csv output
2017-05-11 15:30:19 +03:00
Tyler Rockwood
b675cd19d7 Add static enum option to redash 2017-05-09 13:41:58 -07:00
Alexander Leibzon
17b9f976c8 fix #1664 2017-05-09 17:56:40 +03:00
Alexander Leibzon
248808e165 Merge remote-tracking branch 'upstream/master' 2017-05-09 17:53:08 +03:00
Arik Fraimovich
7c6327be57 Clicking logo should take to account homepage 2017-05-09 10:42:30 +03:00
Arik Fraimovich
a86ece66b5 Add chunkhash to filename only when running production build. 2017-05-09 10:41:26 +03:00
deecay
fd9461ef20 Resolving merge 2017-05-08 11:48:14 +09:00
deecay
f121c609ad Change: Sort dashboard-list in /dashboards 2017-05-08 11:28:41 +09:00
Arik Fraimovich
24f3e071e3 Merge pull request #1754 from getredash/fix_ds_new
Fix: properties of data source were not reset when changing type.
2017-05-07 12:38:51 +03:00
Arik Fraimovich
914977f279 Fix: properties of data source were not reset when changing type.
Fixes #1748.
2017-05-07 12:35:23 +03:00
Arik Fraimovich
97b92d8887 Merge pull request #1738 from suemoc/disable-annotations-athena
[Athena] Fix: queries throwing errors except for SELECT
2017-05-07 10:21:48 +03:00
Arik Fraimovich
c1981b17a4 Merge pull request #1747 from denisov-vlad/jira-maxresults
[JQL] Fix: allow to override maxResults
2017-05-05 09:34:49 +03:00
Vladislav Denisov
ea7c6c2be3 jql: fixed maxResults in count query 2017-05-05 09:26:09 +03:00
Arik Fraimovich
22e3a4d8f2 Merge pull request #1753 from fbertsch/presto_cancellation
[Presto] Add: query cancellation support
2017-05-04 23:21:59 +03:00
Frank Bertsch
75ebbe148b Add presto query cancellation 2017-05-04 11:10:04 -05:00
Arik Fraimovich
75f90c190b Update test for new path 2017-05-04 11:49:10 +03:00
Arik Fraimovich
8aa053ce21 Log public dashboard view event 2017-05-04 11:27:27 +03:00
Arik Fraimovich
23ba8b4aa1 use relative links 2017-05-04 10:39:39 +03:00
Arik Fraimovich
6dde3170ab Make embed & shared dashboard routes use mutli_org template 2017-05-04 10:39:32 +03:00
Arik Fraimovich
b9144a9d7a WIP: support for MULTI_ORG mode (#1447) 2017-05-03 23:53:23 +03:00
Yohei Susa
a1a0d766fe Add environment variable for switching query annotations of Athena to disable 2017-05-03 22:17:59 +09:00
Vladislav Denisov
48322856d9 jql: maxResults fix 2017-05-03 08:52:51 +03:00
Alexander Leibzon
b9f8b6cdbf reformat, as for pep-8 2017-05-02 23:49:09 +03:00
Alexander Leibzon
805ea3cb46 Merge remote-tracking branch 'upstream/master' 2017-05-02 23:37:12 +03:00
Alexander Leibzon
79187cd29a get_schema fix 2017-05-02 23:36:11 +03:00
Arik Fraimovich
ccaf78767b Merge pull request #1700 from deecay/plotly-bump
Change: upgrade Plotly.js.
2017-05-02 22:02:21 +03:00
Arik Fraimovich
94a14f93a8 Remove dev dependencies from npm-shrinkwrap.json. 2017-05-02 22:00:09 +03:00
Arik Fraimovich
5ba6af6ad4 Merge pull request #1713 from deecay/plotly-box
Change: Box plot library from d3.js to Plotly.js
2017-04-30 23:14:18 +03:00
Arik Fraimovich
25760494d7 Merge pull request #1636 from axibase/propertyOrder
[Feature] add: the propertyOrder field to specify order in DataSource settings
2017-04-30 12:31:04 +03:00
Arik Fraimovich
a1fbd511a9 Merge pull request #1651 from axibase/feature/datasource_default_fileds_values
Fix: set default values in options to enable 'default: True' for checkbox
2017-04-30 12:30:32 +03:00
Arik Fraimovich
1b756de479 Merge pull request #1670 from ahamino/fix-google-integration
Change: newer Google API client library
2017-04-30 12:29:34 +03:00
Arik Fraimovich
a9e53a6c29 Merge pull request #1709 from dotneet/add_maximum_billing_tier_to_bigquery
[BigQuery] Add: maximumBillingTier configuration
2017-04-30 12:27:11 +03:00
Arik Fraimovich
111fbfd483 Merge pull request #1721 from jvanegmond/master
[JIRA JQL] Change: change default max results limit from 50 to 1000
2017-04-30 11:59:19 +03:00
Arik Fraimovich
437778a8be Merge pull request #1730 from mfouilleul/master
[Cassandra] Add: support for UUID serializing and setting protocol version
2017-04-30 11:57:55 +03:00
deecay
1c955a570d Bump Plotly version 2017-04-30 11:47:43 +09:00
Alexander Leibzon
469b041a2f Merge remote-tracking branch 'upstream/master' 2017-04-30 01:53:02 +03:00
deecay
7a47d6741d Change: Chosing box plot turns sortX off 2017-04-29 23:42:58 +09:00
Yohei Susa
214a231371 Disable query annotations to Athena query runner 2017-04-27 18:45:49 +09:00
Shimpei Kodama
eb3e30f70f Extend expiry time to prevent duplicate execution 2017-04-25 11:00:52 +09:00
Abdelrahman Mahmoud
7324f1f4c7 Fix code climate warnings 2017-04-23 12:41:49 -04:00
Abdelrahman Mahmoud
93df24de39 Fix Google analytics, Google Spreadsheet and Big Query integration .. upgrade outh2client and google-api-python-client 2017-04-23 12:41:49 -04:00
Maxime Fouilleul
9f21807647 remove useless param 2017-04-20 11:33:12 +02:00
Maxime Fouilleul
9b59394768 Fix default values for proto/cqlversion 2017-04-20 11:16:37 +02:00
Maxime Fouilleul
ac1b0a46f9 Fix trailing spaces (style) 2017-04-19 23:00:39 +02:00
Maxime Fouilleul
5d7795ca47 Fix code style 2017-04-19 22:14:27 +02:00
Maxime Fouilleul
519fb49f6a Improve cassandra lib 2017-04-19 16:39:43 +02:00
Arik Fraimovich
f504b682f3 Bump version. 2017-04-18 22:50:57 +03:00
Arik Fraimovich
f0719f5ea4 Fix: sort by header no longer working.
Closes #1726.
2017-04-18 22:50:33 +03:00
Akira Yumiyama
f852f935c5 improve Salesforce error message 2017-04-18 23:37:05 +09:00
Arik Fraimovich
939aae086f ADd changelog entry for favicons fix 2017-04-18 15:22:05 +03:00
Arik Fraimovich
742e38b08d Update CHANGELOG and bump version 2017-04-18 15:20:21 +03:00
Arik Fraimovich
3c7c93fc9f Fix: favicon wasn't showing up.
Closes #1719.
2017-04-18 15:19:57 +03:00
Arik Fraimovich
53ffff9759 Merge pull request #1716 from deecay/dashboard-tag-m17n
Fix: Non-ASCII dashboard tag
2017-04-18 15:02:27 +03:00
Arik Fraimovich
2e7fafc4d8 CHANGELOG update. 2017-04-18 14:59:44 +03:00
Jos van Egmond
ccf9cbd2c8 Raise JQL limit from default 50 to 1000 2017-04-14 16:06:38 +02:00
hamza zia
730b7c8cad merged upstream 2017-04-11 13:20:13 -04:00
Arik Fraimovich
c66b09effe Merge pull request #1717 from getredash/fix_embeds
Fix: page freezes when rendering large result set.
2017-04-11 18:33:11 +03:00
Arik Fraimovich
a087fe4bcd Fix: page freezes when rendering large result set.
Closes #1711.
2017-04-11 18:05:43 +03:00
deecay
ac557fd5b5 Change: Box plot library from d3 to Plotly 2017-04-07 19:35:28 +09:00
Arik Fraimovich
1f4946cc04 Merge pull request #1710 from getredash/fix_embeds
Fix: embeds were not rendering in PhantomJS.
2017-04-05 12:58:05 +03:00
Arik Fraimovich
08505a2208 Add changelog entry 2017-04-05 12:40:56 +03:00
Arik Fraimovich
e1c186bbf8 Fix: embeds were not rendering in PhantomJS.
Include polyfill for missing ArrayView functions.

Closes #1708.
2017-04-05 12:38:21 +03:00
devneko
91396f0c52 Add: maximumBillingTier for BigQuery 2017-04-05 16:11:17 +09:00
Arik Fraimovich
c83d354eed Merge pull request #1707 from getredash/docker-compose
Update docker-compose configuration:
2017-04-03 18:30:55 +03:00
Arik Fraimovich
81063731c9 Update docker-compose configuration:
* Use newer versions of Redis & PostgreSQL
* Use image for production docker-compose.
2017-04-03 18:28:46 +03:00
Arik Fraimovich
f66fe5ff80 Update packer configuration to create GCE image 2017-04-03 18:07:19 +03:00
Arik Fraimovich
8425698583 Update env 2017-04-03 13:18:34 +03:00
Arik Fraimovich
8b08b1a563 Merge pull request #1704 from getredash/new_bootstrap
New bootstrap script for Ubuntu 16.04
2017-04-03 13:16:53 +03:00
Arik Fraimovich
15b228b754 Update README 2017-04-03 12:54:23 +03:00
Arik Fraimovich
1db4157b29 Fix bootstrap script to be headless 2017-04-03 12:54:17 +03:00
Arik Fraimovich
079530cf63 Remove unused files 2017-04-03 12:54:05 +03:00
Arik Fraimovich
d2370a94c7 New bootstrap script 2017-04-03 10:30:06 +03:00
deecay
9f3fd021ab Fix backward compatibility and resolve conflict 2017-04-03 16:25:23 +09:00
deecay
83ed9fdc51 Fix: Dashboard tag for unicode dashboard names 2017-04-01 23:28:53 +09:00
deecay
9dedaa31c5 Fix: SQLite utf-8 error messages 2017-03-27 16:48:57 +09:00
deecay
de77ebd961 Bump Plotly version 2017-03-27 15:16:42 +09:00
hamza zia
7bdc42ff05 Merge pull request #1 from getredash/master
Upstream Update
2017-03-21 23:19:53 +08:00
rmakulov
5306814237 rm left overs 2017-03-15 19:01:56 +03:00
rmakulov
7939e04e74 set default values in options 2017-03-03 16:20:07 +03:00
rmakulov
f77da51a7d order on client side 2017-03-02 18:43:05 +03:00
rmakulov
ebaf012701 propertyOrder replaced with the field that defines an order 2017-02-27 19:05:43 +03:00
rmakulov
8c481cd7a7 added propertyOrder field 2017-02-27 15:50:29 +03:00
deecay
081ac5f651 Remove unnecessary comment line. 2017-02-21 17:18:14 +09:00
deecay
ac538c35e9 Add: option to hide pivot table controls 2017-02-20 15:09:48 +09:00
hamza zia
eeee592abc nuke separate endpiont for running queries in sync 2017-02-03 09:34:01 -08:00
hamza zia
ce65578c72 allowed embeds to recieve params 2017-01-15 22:23:18 +05:00
hamza zia
f4c25cb941 Merge branch 'master' of github.com:rainforestapp/redash 2017-01-15 20:36:59 +05:00
hamza zia
ba0daa218e JSON API that accepts params 2017-01-15 20:36:25 +05:00
Alexander Leibzon
fefcb928da add memsql as datasource 2016-09-27 19:13:44 +03:00
279 changed files with 18484 additions and 7010 deletions

8
.gitignore vendored
View File

@@ -9,14 +9,8 @@ celerybeat-schedule*
\#*#
*~
_build
# Vagrant related
.vagrant
Berksfile.lock
redash/dump.rdb
.vscode
.env
.ruby-version
venv
dump.rdb

View File

@@ -1,5 +1,201 @@
# Change Log
## v3.0.0 - UNRELEASED
### Added
- Query Result data source (run queries on query results).
- Athena: option to load schema from Glue catalog. @myouju
- Allow running any command inside the container via the Docker entrypoint script. @jezdez
- Make invitation token max age configurable. @hhamalai
- Redshift: add support for the new ACM root CA.
- Redshift: support for Spectrum (external) tables. @atharvai
- MongoDB: option to set allowDiskUse in queries.
- Option to disable SQLAlchemy connection pool.
- Option to set a time limit on adhoc queries.
- Option to disable sending an invite to a new user.
- Azure SQL Data Warehouse query runner. @kitsuyui
- Prometheus query runner. @yershalom
- Option to set the Flask-Limiter storage engine.
- Option to set UnicodeWriter's error handling method. @fan-t-endo
- PostgreSQL: SSL configuration option. @TylerBrock
- Counter visualization: additional formatting options. @deecay
- Query based drop down parameter. @rohithmenon
- MySQL: multiple queries support & connection timeout.
- Ability to select all in multi-filter. @Posnet
- LDAP (Active Directory) support. @amarjayr
### Changed
- Copy parameters when forking a query. @kyoshidajp
- Prevent using Query API Key with refresh API (previously it was just failing).
- Reduce boilerplate in frontend code.
- Set auto focus in first input items. @kyoshidajp
- Update gunicorn to latest version.
- Make log format configurable.
- Sort series by name.
- Allow setting test file with Docker test run. @meinac
- Use outdated queries count stored already in Redis.
- Show links based on permissions the user have.
- Cassandra: update driver version. @yershalom
- Docker-Compose: update configuration to always restart services. @muddydixon
- Modernize Python 2 code to get ready for Python 3. @cclauss
- Cohort visualization: make it friendlier to use by better handle gaps in data, so it's easier to generate the data needed.
- Use a different markdown library. @alexmuller
- Salesforce: improve error messages we receive from the API. @akiray03
- Custom JS code visualization improvements. @deecay
- DQL: Update version to 0.5.24. @aterreno
- Cassandra: get_schema support for both C* 2.x and 3.x, support for SortedSet type serialization. (@mfouilleul)
- Replace deprecated ng-annotate with babel plugin. @44px
- Update Python dependencies to recent versions. @alison985
- Bootstrap script: create /opt/redash directory only if it doesn't exist. @isomura
- Bootstrap script: make use of REDASH_BASE_PATH variable in setup script. @sylvain
### Fixed
- Require full data source access to fork a query.
- API key of one query could be used to get results of another one.
- Delete group id from user object when deleting the group. @kyoshidajp
- Sorting of X axis wasn't working for Box plot type visualizations. @deecay
- Exporting query results as excel was failing when one of the columns had array data. @kyoshidajp
- Show query editor's Archive/Publish Query drop-down only on saved queries. @cyriac
- Move misplaced configuration in docker-compose.production.yml. @yutannihilation
- MySQL: support UTF8 schema.
- TreasureData queries were failing when returning 0 rows.
- Use series color for Boxplot. @deecay
- Revoke permission should respect to given grantee and access type. @meinac
- Fixed eslint "Cannot read property 'length' of undefined" error. @kravets-levko
- Don't crash query editor when there are unclosed curly brackets.
- Error value in charts wasn't displayed if it was 0.
- Prevent line breaks in EditInPlace description when using Firefox. @alexmuller
- Queries#all_queries was sometimes returning wrong number of queries.
- record_event fails for API events.
- Cancel button on tasks admin page was broken.
- Remove deprecated cx_Oracle types. @queeno
- Textbox widgets were updating their value even when editor was cancelled. @alison985
- Collaborators couldn't edit visualizations or schedule.
- Use series color for error bar. @deecay
- Upgrade script was using the wrong restart command on new AMIs.
## v2.0.1 - 2017-10-22
This is a patch release, that adds support for Redshift ACM certificates (see #2044 for details).
## v2.0.0 - 2017-08-08
### Added
- [Cassandra] Support for UUID serializing and setting protocol version. @mfouilleul
- [BigQuery] Add maximumBillingTier to BigQuery configuration. @dotneet
- Add the propertyOrder field to specify order of data source settings. @rmakulov
- Add Plotly based Boxplot visualization. @deecay
- [Presto] Add: query cancellation support. @fbertsch
- [MongoDB] add $oids JSON extension.
- [PostgreSQL] support for loading materialized views in schema.
- [MySQL] Add option to hide SSL settings.
- [MySQL] support for RDS MySQL and SSL.
- [Google Analytics] support for mcf queries & better errors.
- Add: static enum parameter type. @rockwotj
- Add: option to hide pivot table controls. @deecay
- Retry reload of query results if it had an error.
- [Data Sources] Add: MemSQL query runner. @alexanderlz
- "Dumb" recents option (see #1779 for details)
- Athena: direct query runner using the instead of JDBC proxy. @laughingman7743
- Optionally support parameters in embeds. @ziahamza
- Sorting ability in alerts view.
- Option to change default encoding of CSV writer. @yamamanx
- Ability to set dashboard level filters from UI.
- CLI command to open IPython shell.
- Add link to query page from admin view. @miketheman
- Add the option to write logs to STDOUT instead of STDERR. @eyalzek
- Add limit parameter to tasks API. @alexpekurovsky
- Add SQLAlchemy pool settings.
- Support for category type y axis.
- Add 12 & 24 hours refresh rate option to dashboards.
### Changed
- Upgrade Google API client library for all Google data sources. @ahamino
- [JIRA JQL] change default max results limit from 50 to 1000. @jvanegmond
- Upgrade to newer Plotly version. @deecay
- [Athena] Configuration flag to disable query annotations for Athena. @suemoc
- Ignore extra columns in CSV output. @alexanderlz
- [TreasureData] improve error handling and upgrade client.
- [InfluxDB] simpler test connection query (show databases requires admin).
- [MSSQL] Mark integers as decimals as well, as sometimes decimal columns being returned
with integer column type.
- [Google Spreadsheets] add timeout to requests.
- Sort dashboards list by name. @deecay
- Include Celery task name in statsd metrics.
- Don't include paused datasource's queries in outdated queries count.
- Cohort: handle the case where the value/total might be strings.
- Query results: better type guessing on the client side.
- Counter: support negative indexes to iterate from the end of the results.
- Data sources and destinations configuration: change order of name and type (type first now).
- Show API Key in a modal dialog instead of alert.
- Sentry: upgrade client version.
- Sentry: don't install logging hook.
- Split refresh schemas into separate tasks and add a timeout.
- Execute scheduled queries with parameters using their default value.
- Keep track of last query execution (including failed ones) for scheduling purposes.
- Same view for input on search result page as in header. @44px
- Metrics: report endpoints without dots for metrics.
- Redirect to / when org not found.
- Improve parameters label placement. @44px
- Auto-publish queries when they are named (with option to disable; #1830).
- Show friendly error message in case of duplicate data source name.
- Don't allow saving dashboard with empty name.
- Enable strict checking for Angular DI.
- Disable Angular debug info (should improve performance).
- Update to Webpack 2. @44px
- Remove /forgot endpoint if REDASH_PASSWORD_LOGIN_ENABLED is false. @amarjayr
- Docker: make Gunicorn worker count configurable. @unixwitch
- Snowflake support is no longer enabled by default.
- Enable memory optimization for Excel exporter.
### Fixed
- Fix: set default values in options to enable 'default: True' for checkbox. @rmakulov
- Support MULTI_ORG again.
- [Google Spreadsheets] handle distant future dates.
- [SQLite] better handle utf-8 error messages.
- Fix: don't remove locks for queries with task status of PENDING.
- Only split columns with __/:: that end with filter/MultiFilter.
- Alert notifications fail (sometime) with a SQLAlchemy error.
- Safeguard against empty query results when checking alert status. @danielerapati
- Delete data source doesn't work when query results referenced by queries.
- Fix redirect to /setup on the last setup step. @44px
- Cassandra: use port setting in connection options. @yershalom
- Metrics: table name wasn't found for count queries.
- BigQuery wasn't loading due to bad import.
- DynamicForm component was inserting empty values.
- Clear null values from data source options dictionary.
- /api/session API call wasn't working when multi tenancy enabled
- If column had no type it would use previous column's type.
- Alert destination details were not updating.
- When setting rearm on a new alert, it wasn't persisted.
- Salesforce: sandbox parameter should be optional. @msnider
- Alert page wasn't properly linked from alerts list. @alison985
- PostgreSQL passwords with spaces were not supported. (#1056)
- PivotTable wasn't updating after first save.
## v1.0.3 - 2017-04-18
### Fixed
- Fix: sort by column no longer working.
## v1.0.2 - 2017-04-18
### Fixed
- Fix: favicon wasn't showing up.
- Fix: support for unicode in dashboard tags. @deecay
- Fix: page freezes when rendering large result set.
- Fix: chart embeds were not rendering in PhantomJS.
## v1.0.1 - 2017-04-02
### Added
@@ -39,8 +235,11 @@
- Fix: page header wasn't updating on dashboards page @MichaelJAndy
- Fix: keyboard shortcuts didn't work in parameter inputs
## v1.0.0-rc.2 - 2017-02-22
### Other
- Change default job expiry times to: job lock expire after 12 hours (previously: 6 hours) and Celery task result object expire after 4 hours (previously: 1 hour). @shimpeko
## v1.0.0-rc.2 - 2017-02-22
### Changed
@@ -171,109 +370,110 @@ We're releasing a new upgrade script -- see [here](https://redash.io/help-onprem
### Added
61fe16e #1374: Add: allow '*' in REDASH_CORS_ACCESS_CONTROL_ALLOW_ORIGIN (Allen Short)
2f09043 #1113: Add: share modify/access permissions for queries and dashboard (whummer)
3db0eea #1341: Add: support for specifying SAML nameid-format (zoetrope)
b0ecd0e #1343: Add: support for local SAML metadata file (zoetrope)
0235d37 #1335: Add: allow changing alert email subject. (Arik Fraimovich)
2135dfd #1333: Add: control over y axis min/max values (Arik Fraimovich)
49e788a #1328: Add: support for snapshot generation service (Arik Fraimovich)
229ca6c #1323: Add: collect runtime metrics for Celery tasks (Arik Fraimovich)
931a1f3 #1315: Add: support for loading BigQuery schema (Arik Fraimovich)
39b4f9a #1314: Add: support MongoDB SSL connections (Arik Fraimovich)
ca1ca9b #1312: Add: additional configuration for Celery jobs (Arik Fraimovich)
fc00e61 #1310: Add: support for date/time with seconds parameters (Arik Fraimovich)
d72a198 #1307: Add: API to force refresh data source schema (Arik Fraimovich)
beb89ec #1305: Add: UI to edit dashboard text box widget (Kazuhito Hokamura)
808fdd4 #1298: Add: JIRA (JQL) query runner (Arik Fraimovich)
ff9e844 #1280: Add: configuration flag to disable scheduled queries (Hirotaka Suzuki)
ef4699a #1269: Add: Google Drive federated tables support in BigQuery query runner (Kurt Gooden)
2eeb947 #1236: Add: query runner for Cassandra and ScyllaDB (syerushalmy)
10b398e #1249: Add: override slack webhook parameters (mystelynx)
2b5e340 #1252: Add: Schema loading support for Presto query runner (using information_schema) (Rohan Dhupelia)
2aaf5dd #1250: Add: query snippets feature (Arik Fraimovich)
8d8af73 #1226: Add: Sankey visualization (Arik Fraimovich)
a02edda #1222: Add: additional results format for sunburst visualization (Arik Fraimovich)
0e70188 #1213: Add: new sunburst sequence visualization (Arik Fraimovich)
9a6d2d7 #1204: Add: show views in schema browser for Vertica data sources (Matthew Carter)
600afa5 #1138: Add: ability to register user defined function (UDF) resources for BigQuery DataSource/Query (fabito)
b410410 #1166: Add: "every 14 days" refresh option (Arik Fraimovich)
906365f #967: Add: extend ElasticSearch query_runner to support aggregations (lloydw)
- 61fe16e #1374: Add: allow '*' in REDASH_CORS_ACCESS_CONTROL_ALLOW_ORIGIN (Allen Short)
- 2f09043 #1113: Add: share modify/access permissions for queries and dashboard (whummer)
- 3db0eea #1341: Add: support for specifying SAML nameid-format (zoetrope)
- b0ecd0e #1343: Add: support for local SAML metadata file (zoetrope)
- 0235d37 #1335: Add: allow changing alert email subject. (Arik Fraimovich)
- 2135dfd #1333: Add: control over y axis min/max values (Arik Fraimovich)
- 49e788a #1328: Add: support for snapshot generation service (Arik Fraimovich)
- 229ca6c #1323: Add: collect runtime metrics for Celery tasks (Arik Fraimovich)
- 931a1f3 #1315: Add: support for loading BigQuery schema (Arik Fraimovich)
- 39b4f9a #1314: Add: support MongoDB SSL connections (Arik Fraimovich)
- ca1ca9b #1312: Add: additional configuration for Celery jobs (Arik Fraimovich)
- fc00e61 #1310: Add: support for date/time with seconds parameters (Arik Fraimovich)
- d72a198 #1307: Add: API to force refresh data source schema (Arik Fraimovich)
- beb89ec #1305: Add: UI to edit dashboard text box widget (Kazuhito Hokamura)
- 808fdd4 #1298: Add: JIRA (JQL) query runner (Arik Fraimovich)
- ff9e844 #1280: Add: configuration flag to disable scheduled queries (Hirotaka Suzuki)
- ef4699a #1269: Add: Google Drive federated tables support in BigQuery query runner (Kurt Gooden)
- 2eeb947 #1236: Add: query runner for Cassandra and ScyllaDB (syerushalmy)
- 10b398e #1249: Add: override slack webhook parameters (mystelynx)
- 2b5e340 #1252: Add: Schema loading support for Presto query runner (using information_schema) (Rohan Dhupelia)
- 2aaf5dd #1250: Add: query snippets feature (Arik Fraimovich)
- 8d8af73 #1226: Add: Sankey visualization (Arik Fraimovich)
- a02edda #1222: Add: additional results format for sunburst visualization (Arik Fraimovich)
- 0e70188 #1213: Add: new sunburst sequence visualization (Arik Fraimovich)
- 9a6d2d7 #1204: Add: show views in schema browser for Vertica data sources (Matthew Carter)
- 600afa5 #1138: Add: ability to register user defined function (UDF) resources for BigQuery DataSource/Query (fabito)
- b410410 #1166: Add: "every 14 days" refresh option (Arik Fraimovich)
- 906365f #967: Add: extend ElasticSearch query_runner to support aggregations (lloydw)
### Changed
2de4aa2 #1395: Change: switch to requests in URL query runner (Arik Fraimovich)
db1a941 #1392: Change: Update documentation links to point at the new location. (Arik Fraimovich)
002f794 #1368: Change: added ability to disable auto update in admin views (Arik Fraimovich)
aa5d14e #1366: Change: improve error message for exception in the Python query runner (deecay)
880627c #1355: Change: pass the user object to the run_query method (Arik Fraimovich)
23c605b #1342: SAML: specify entity id (zoetrope)
015b1dc #1334: Change: allow specifying recipient address when sending email test message (Arik Fraimovich)
39aaa2f #1292: Change: improvements to map visualization (Arik Fraimovich)
b22191b #1332: Change: upgrade Python packages (Arik Fraimovich)
23ba98b #1331: Celery: Upgrade Celery to more recent version. (Arik Fraimovich)
3283116 #1330: Change: upgrade Requests to latest version. (Arik Fraimovich)
39091e0 #1324: Change: add more logging and information for refresh schemas task (Arik Fraimovich)
462faea #1316: Change: remove deprecated settings (Arik Fraimovich)
73e1837 #1313: Change: more flexible column width calculation (Arik Fraimovich)
e8eb840 #1279: Change: update bootstrap.sh to support Ubuntu 16.04 (IllusiveMilkman)
8cf0252 #1262: Change: upgrade Plot.ly version and switch to smaller build (Arik Fraimovich)
0b79fb8 #1306: Change: paginate queries page & add explicit urls. (Arik Fraimovich)
41f99f5 #1299: Change: send Content-Type header (application/json) in query results responses (Tsuyoshi Tatsukawa)
dfb1a20 #1297: Change: update Slack configuration titles. (Arik Fraimovich)
8c1056c #1294: Change: don't annotate BigQuery queries (Arik Fraimovich)
a3cf92e #1289: Change: use key_as_string when available (ElasticSearch query runner) (Arik Fraimovich)
e155191 #1285: Change: do not display Oracle tablespace name in schema browser (Matthew Carter)
6cbc39c #1282: Change: deduplicate Google Spreadsheet columns (Arik Fraimovich)
4caf2e3 #1277: Set specific version of cryptography lib (Arik Fraimovich)
d22f0d4 #1216: Change: bootstrap.sh - use non interactive dist-upgrade (Atsushi Sasaki)
19530f4 #1245: Change: switch from CodeMirror to Ace editor (Arik Fraimovich)
dfb92db #1234: Change: MongoDB query runner set DB name as mandatory (Arik Fraimovich)
b750843 #1230: Change: annotate Presto queries with metadata (Noriaki Katayama)
5b20fe2 #1217: Change: install libffi-dev for Cryptography (Ubuntu setup script) (Atsushi Sasaki)
a9fac34 #1206: Change: update pymssql version to 2.1.3 (kitsuyui)
5d43cbe #1198: Change: add support for Standard SQL in BigQuery query runner (mystelynx)
84d0c22 #1193: Change: modify the argument order of moment.add function call (Kenya Yamaguchi)
- 2de4aa2 #1395: Change: switch to requests in URL query runner (Arik Fraimovich)
- db1a941 #1392: Change: Update documentation links to point at the new location. (Arik Fraimovich)
- 002f794 #1368: Change: added ability to disable auto update in admin views (Arik Fraimovich)
- aa5d14e #1366: Change: improve error message for exception in the Python query runner (deecay)
- 880627c #1355: Change: pass the user object to the run_query method (Arik Fraimovich)
- 23c605b #1342: SAML: specify entity id (zoetrope)
- 015b1dc #1334: Change: allow specifying recipient address when sending email test message (Arik Fraimovich)
- 39aaa2f #1292: Change: improvements to map visualization (Arik Fraimovich)
- b22191b #1332: Change: upgrade Python packages (Arik Fraimovich)
- 23ba98b #1331: Celery: Upgrade Celery to more recent version. (Arik Fraimovich)
- 3283116 #1330: Change: upgrade Requests to latest version. (Arik Fraimovich)
- 39091e0 #1324: Change: add more logging and information for refresh schemas task (Arik Fraimovich)
- 462faea #1316: Change: remove deprecated settings (Arik Fraimovich)
- 73e1837 #1313: Change: more flexible column width calculation (Arik Fraimovich)
- e8eb840 #1279: Change: update bootstrap.sh to support Ubuntu 16.04 (IllusiveMilkman)
- 8cf0252 #1262: Change: upgrade Plot.ly version and switch to smaller build (Arik Fraimovich)
- 0b79fb8 #1306: Change: paginate queries page & add explicit urls. (Arik Fraimovich)
- 41f99f5 #1299: Change: send Content-Type header (application/json) in query results responses (Tsuyoshi Tatsukawa)
- dfb1a20 #1297: Change: update Slack configuration titles. (Arik Fraimovich)
- 8c1056c #1294: Change: don't annotate BigQuery queries (Arik Fraimovich)
- a3cf92e #1289: Change: use key_as_string when available (ElasticSearch query runner) (Arik Fraimovich)
- e155191 #1285: Change: do not display Oracle tablespace name in schema browser (Matthew Carter)
- 6cbc39c #1282: Change: deduplicate Google Spreadsheet columns (Arik Fraimovich)
- 4caf2e3 #1277: Set specific version of cryptography lib (Arik Fraimovich)
- d22f0d4 #1216: Change: bootstrap.sh - use non interactive dist-upgrade (Atsushi Sasaki)
- 19530f4 #1245: Change: switch from CodeMirror to Ace editor (Arik Fraimovich)
- dfb92db #1234: Change: MongoDB query runner set DB name as mandatory (Arik Fraimovich)
- b750843 #1230: Change: annotate Presto queries with metadata (Noriaki Katayama)
- 5b20fe2 #1217: Change: install libffi-dev for Cryptography (Ubuntu setup script) (Atsushi Sasaki)
- a9fac34 #1206: Change: update pymssql version to 2.1.3 (kitsuyui)
- 5d43cbe #1198: Change: add support for Standard SQL in BigQuery query runner (mystelynx)
- 84d0c22 #1193: Change: modify the argument order of moment.add function call (Kenya Yamaguchi)
### Fixed
d6febb0 #1375: Fix: Download Dataset does not work when not logged in (Joshua Dechant)
96553ad #1369: Fix: missing format call in Elasticsearch test method (Adam Griffiths)
c57c765 #1365: Fix: compare retrieval times in UTC timezone (Allen Short)
37dff5f #1360: Fix: connection test was broken for MySQL (ichihara)
360028c #1359: Fix: schema loading query for Hive was wrong for non default schema (laughingman7743)
7ee41d4 #1358: Fix: make sure all calls to run_query updated with new parameter (Arik Fraimovich)
0d94479 #1329: Fix: Redis memory leak. (Arik Fraimovich)
7145aa2 #1325: Fix: queries API was doing N+1 queries in most cases (Arik Fraimovich)
cd2e927 #1311: Fix: BoxPlot visualization wasn't rendering on a dashboard (Arik Fraimovich)
a562ce7 #1309: Fix: properly render checkboxes in dynamic forms (Arik Fraimovich)
d48192c #1308: Fix: support for Unicode columns name in Google Spreadsheets (Arik Fraimovich)
e42f93f #1283: Fix: schema browser was unstable after opening a table (Arik Fraimovich)
170bd65 #1272: Fix: TreasureData get_schema method was returning array instead of string as column name (ariarijp)
4710c41 #1265: Fix: refresh modal not working for unsaved query (Arik Fraimovich)
bc3a5ab #1264: Fix: dashboard refresh not working (Arik Fraimovich)
6202d09 #1240: Fix: when shared dashboard token not found, return 404 (Wesley Batista)
93aac14 #1251: Fix: autocomplete went crazy when database has no autocomplete. (Arik Fraimovich)
b8eca28 #1246: Fix: support large schemas in schema browser (Arik Fraimovich)
b781003 #1223: Fix: Alert: when hipchat Alert.name is multibyte character, occur error. (toyama0919)
0b928e6 #1227: Fix: Bower install fails in vagrant (Kazuhito Hokamura)
a411af2 #1232: Fix: don't show warning when query string (parameters value) changes (Kazuhito Hokamura)
3dbb5a6 #1221: Fix: sunburst didn't handle all cases of path lengths (Arik Fraimovich)
a7cc1ee #1218: Fix: updated result not being saved when changing query text. (Arik Fraimovich)
0617833 #1215: Fix: email alerts not working (Arik Fraimovich)
78f65b1 #1187: Fix: read only users receive the permission error modal in query view (Arik Fraimovich)
bba801f #1167: Fix the version of setuptools on bootstrap script for Ubuntu (Takuya Arita)
ce81d69 #1160: Fix indentation in docker-compose-example.yml (Hirofumi Wakasugi)
dd759fe #1155: Fix: make all configuration values of Oracle required (Arik Fraimovich)
- d6febb0 #1375: Fix: Download Dataset does not work when not logged in (Joshua Dechant)
- 96553ad #1369: Fix: missing format call in Elasticsearch test method (Adam Griffiths)
- c57c765 #1365: Fix: compare retrieval times in UTC timezone (Allen Short)
- 37dff5f #1360: Fix: connection test was broken for MySQL (ichihara)
- 360028c #1359: Fix: schema loading query for Hive was wrong for non default schema (laughingman7743)
- 7ee41d4 #1358: Fix: make sure all calls to run_query updated with new parameter (Arik Fraimovich)
- 0d94479 #1329: Fix: Redis memory leak. (Arik Fraimovich)
- 7145aa2 #1325: Fix: queries API was doing N+1 queries in most cases (Arik Fraimovich)
- cd2e927 #1311: Fix: BoxPlot visualization wasn't rendering on a dashboard (Arik Fraimovich)
- a562ce7 #1309: Fix: properly render checkboxes in dynamic forms (Arik Fraimovich)
- d48192c #1308: Fix: support for Unicode columns name in Google Spreadsheets (Arik Fraimovich)
- e42f93f #1283: Fix: schema browser was unstable after opening a table (Arik Fraimovich)
- 170bd65 #1272: Fix: TreasureData get_schema method was returning array instead of string as column name (ariarijp)
- 4710c41 #1265: Fix: refresh modal not working for unsaved query (Arik Fraimovich)
- bc3a5ab #1264: Fix: dashboard refresh not working (Arik Fraimovich)
- 6202d09 #1240: Fix: when shared dashboard token not found, return 404 (Wesley Batista)
- 93aac14 #1251: Fix: autocomplete went crazy when database has no autocomplete. (Arik Fraimovich)
- b8eca28 #1246: Fix: support large schemas in schema browser (Arik Fraimovich)
- b781003 #1223: Fix: Alert: when hipchat Alert.name is multibyte character, occur error. (toyama0919)
- 0b928e6 #1227: Fix: Bower install fails in vagrant (Kazuhito Hokamura)
- a411af2 #1232: Fix: don't show warning when query string (parameters value) changes (Kazuhito Hokamura)
- 3dbb5a6 #1221: Fix: sunburst didn't handle all cases of path lengths (Arik Fraimovich)
- a7cc1ee #1218: Fix: updated result not being saved when changing query text. (Arik Fraimovich)
- 0617833 #1215: Fix: email alerts not working (Arik Fraimovich)
- 78f65b1 #1187: Fix: read only users receive the permission error modal in query view (Arik Fraimovich)
- bba801f #1167: Fix the version of setuptools on bootstrap script for Ubuntu (Takuya Arita)
- ce81d69 #1160: Fix indentation in docker-compose-example.yml (Hirofumi Wakasugi)
- dd759fe #1155: Fix: make all configuration values of Oracle required (Arik Fraimovich)
### Docs
a69ee0c #1225: Fix: RST formatting of the Vagrant documentation (Kazuhito Hokamura)
03837c0 #1242: Docs: add warning re. quotes on column names and BigQuery (Ereli)
9a98075 #1255: Docs: add documentation for InfluxDB (vishesh92)
e0485de #1195: Docs: fix typo in maintenance page title (Antoine Augusti)
7681d3e #1164: Docs: update permission documentation (Daniel Darabos)
bcd3670 #1156: Docs: add SSL parameters to nginx configuration (Josh Cox)
- a69ee0c #1225: Fix: RST formatting of the Vagrant documentation (Kazuhito Hokamura)
- 03837c0 #1242: Docs: add warning re. quotes on column names and BigQuery (Ereli)
- 9a98075 #1255: Docs: add documentation for InfluxDB (vishesh92)
- e0485de #1195: Docs: fix typo in maintenance page title (Antoine Augusti)
- 7681d3e #1164: Docs: update permission documentation (Daniel Darabos)
- bcd3670 #1156: Docs: add SSL parameters to nginx configuration (Josh Cox)
## v0.11.1.b2095 - 2016-08-02
@@ -291,73 +491,77 @@ Also, this release includes numerous smaller features, improvements, and bug fix
A big thank you goes to all who contributed code and documentation in this release: @AntoineAugusti, @James226, @adamlwgriffiths, @alexdebrie, @anthony-coble, @ariarijp, @dheerajrav, @edwardsharp, @machira, @nabilblk, @ninneko, @ordd, @tomerben, @toru-takahashi, @vishesh92, @vorakumar and @whummer.
### Added
d5e5b24 #1136: Feature: add --org option to all relevant CLI commands. (@adamlwgriffiths)
87e25f2 #1129: Feature: support for JSON query formatting (Mongo, ElasticSearch) (@arikfr)
6bb2716 #1121: Show error when failing to communicate with server (@arikfr)
f21276e #1119: Feature: add UI to delete alerts (@arikfr)
8656540 #1069: Feature: UI for query parameters (@arikfr)
790128c #1067: Feature: word cloud visualization (@anthony-coble)
8b73a2b #1098: Feature: UI for alert destinations & new destination types (@alexdebrie)
1fbeb5d #1092: Add Heroku support (@adamlwgriffiths)
f64622d #1089: Add support for serialising UUID type within MSSQL #961 (@James226)
857caab #1085: Feature: API to pause a data source (@arikfr)
214aa3b #1060: Feature: support configuring user's groups with SAML (@vorakumar)
e20a005 #1007: Issue#1006: Make bottom margin editable for Chart visualization (@vorakumar)
6e0dd2b #1063: Add support for date/time Y axis (@tomerben)
b5a4a6b #979: Feature: Add CLI to edit group permissions (@ninneko)
6d495d2 #1014: Add server-side parameter handling for embeds (@whummer)
5255804 #1091: Add caching for queries used in embeds (@whummer)
- d5e5b24 #1136: Feature: add --org option to all relevant CLI commands. (@adamlwgriffiths)
- 87e25f2 #1129: Feature: support for JSON query formatting (Mongo, ElasticSearch) (@arikfr)
- 6bb2716 #1121: Show error when failing to communicate with server (@arikfr)
- f21276e #1119: Feature: add UI to delete alerts (@arikfr)
- 8656540 #1069: Feature: UI for query parameters (@arikfr)
- 790128c #1067: Feature: word cloud visualization (@anthony-coble)
- 8b73a2b #1098: Feature: UI for alert destinations & new destination types (@alexdebrie)
- 1fbeb5d #1092: Add Heroku support (@adamlwgriffiths)
- f64622d #1089: Add support for serialising UUID type within MSSQL #961 (@James226)
- 857caab #1085: Feature: API to pause a data source (@arikfr)
- 214aa3b #1060: Feature: support configuring user's groups with SAML (@vorakumar)
- e20a005 #1007: Issue#1006: Make bottom margin editable for Chart visualization (@vorakumar)
- 6e0dd2b #1063: Add support for date/time Y axis (@tomerben)
- b5a4a6b #979: Feature: Add CLI to edit group permissions (@ninneko)
- 6d495d2 #1014: Add server-side parameter handling for embeds (@whummer)
- 5255804 #1091: Add caching for queries used in embeds (@whummer)
### Changed
0314313 #1149: Presto QueryRunner supports tinyint and smallint (@toru-takahashi)
8fa6fdb #1030: Make sure data sources list ordered by id (@arikfr)
8df822e #1141: Make create data source button more prominent (@arikfr)
96dd811 #1127: Mark basic_auth_password as secret (@adamlwgriffiths)
ad65391 #1130: Improve Slack notification style (@AntoineAugusti)
df637e3 #1116: Return meaningful error when there is no cached result. (@arikfr)
65635ec #1102: Switch to HipChat V2 API (@arikfr)
14fcf01 #1072: Remove counter from the tasks Done tab (as it always shows 50). #1047 (@arikfr)
1a1160e #1062: DynamoDB: Better exception handling (@arikfr)
ed45dcb #1044: Improve vagrant flow (@staritza)
8b5dc8e #1036: Add optional block for more scripts in template (@arikfr)
- 0314313 #1149: Presto QueryRunner supports tinyint and smallint (@toru-takahashi)
- 8fa6fdb #1030: Make sure data sources list ordered by id (@arikfr)
- 8df822e #1141: Make create data source button more prominent (@arikfr)
- 96dd811 #1127: Mark basic_auth_password as secret (@adamlwgriffiths)
- ad65391 #1130: Improve Slack notification style (@AntoineAugusti)
- df637e3 #1116: Return meaningful error when there is no cached result. (@arikfr)
- 65635ec #1102: Switch to HipChat V2 API (@arikfr)
- 14fcf01 #1072: Remove counter from the tasks Done tab (as it always shows 50). #1047 (@arikfr)
- 1a1160e #1062: DynamoDB: Better exception handling (@arikfr)
- ed45dcb #1044: Improve vagrant flow (@staritza)
- 8b5dc8e #1036: Add optional block for more scripts in template (@arikfr)
### Fixed
dbd48e1 #1143: Fix: use the email input type where needed (@ariarijp)
7445972 #1142: Fix: dates in filters might be duplicated (@arikfr)
5d0ed02 #1140: Fix: Hive should use the enabled variable (@arikfr)
392627d #1139: Fix: Impala data source referencing wrong variable (@arikfr)
c5bfbba #1133: Fix: query scrolling issues (@vishesh92)
c01d266 #1128: Fix: visualization options not updating after changing type (@arikfr)
6bc0e7a #1126: Fix #669: save fails when doing partial save of new query (@arikfr)
3ce27b9 #1118: Fix: remove alerts for archived queries (@arikfr)
4fabaae #1117: Fix #1052: filter not working for date/time values (@arikfr)
c107c94 #1077: Fix: install needed dependencies to use Hive in Docker image (@nabilblk)
abc790c #1115: Fix: allow non integers in alert reference value (@arikfr)
4ec473c #1110: Fix #1109: mixed group permissions resulting in wrong permission (@arikfr)
1ca5262 #1099: Fix RST syntax for links (@adamlwgriffiths)
daa6c1c #1096: Fix typo in env variable VERSION_CHECK (@AntoineAugusti)
cd06d27 #1095: Fix: use create_query permission for new query button. (@ordd)
2bc0b27 #1061: Fix: area chart stacking doesn't work (@machira)
8c21e91 #1108: Remove potnetially concurrency not safe code form enqueue_query (@arikfr)
e831218 #1084: Fix #1049: duplicate alerts when data source belongs to multiple groups (@arikfr)
6edb0ca #1080: Fix typo (@jeffwidman)
64d7538 #1074: Fix: ElasticSearch wasn't using correct type names (@toyama0919)
3f90dd9 #1064: Fix: old task trackers were not really removed (@arikfr)
e10ecd2 #1058: Bring back filters if dashboard filters are enabled (@AntoineAugusti)
701035f #1059: Fix: DynamoDB having issues when setting host (@arikfr)
2924d4f #1040: Small fixes to visualizations view (@arikfr)
fec0d5f #1037: Fix: multi filter wasn't working with __ syntax (@dheerajrav)
b066ce4 #1033: Fix: only ask for notification permissions if wasn't denied (@arikfr)
960c416 #1032: Fix: make sure we return dashboards only for current org only (@arikfr)
b3844d3 #1029: Hive: close connection only if it exists (@arikfr)
- dbd48e1 #1143: Fix: use the email input type where needed (@ariarijp)
- 7445972 #1142: Fix: dates in filters might be duplicated (@arikfr)
- 5d0ed02 #1140: Fix: Hive should use the enabled variable (@arikfr)
- 392627d #1139: Fix: Impala data source referencing wrong variable (@arikfr)
- c5bfbba #1133: Fix: query scrolling issues (@vishesh92)
- c01d266 #1128: Fix: visualization options not updating after changing type (@arikfr)
- 6bc0e7a #1126: Fix #669: save fails when doing partial save of new query (@arikfr)
- 3ce27b9 #1118: Fix: remove alerts for archived queries (@arikfr)
- 4fabaae #1117: Fix #1052: filter not working for date/time values (@arikfr)
- c107c94 #1077: Fix: install needed dependencies to use Hive in Docker image (@nabilblk)
- abc790c #1115: Fix: allow non integers in alert reference value (@arikfr)
- 4ec473c #1110: Fix #1109: mixed group permissions resulting in wrong permission (@arikfr)
- 1ca5262 #1099: Fix RST syntax for links (@adamlwgriffiths)
- daa6c1c #1096: Fix typo in env variable VERSION_CHECK (@AntoineAugusti)
- cd06d27 #1095: Fix: use create_query permission for new query button. (@ordd)
- 2bc0b27 #1061: Fix: area chart stacking doesn't work (@machira)
- 8c21e91 #1108: Remove potnetially concurrency not safe code form enqueue_query (@arikfr)
- e831218 #1084: Fix #1049: duplicate alerts when data source belongs to multiple groups (@arikfr)
- 6edb0ca #1080: Fix typo (@jeffwidman)
- 64d7538 #1074: Fix: ElasticSearch wasn't using correct type names (@toyama0919)
- 3f90dd9 #1064: Fix: old task trackers were not really removed (@arikfr)
- e10ecd2 #1058: Bring back filters if dashboard filters are enabled (@AntoineAugusti)
- 701035f #1059: Fix: DynamoDB having issues when setting host (@arikfr)
- 2924d4f #1040: Small fixes to visualizations view (@arikfr)
- fec0d5f #1037: Fix: multi filter wasn't working with __ syntax (@dheerajrav)
- b066ce4 #1033: Fix: only ask for notification permissions if wasn't denied (@arikfr)
- 960c416 #1032: Fix: make sure we return dashboards only for current org only (@arikfr)
- b3844d3 #1029: Hive: close connection only if it exists (@arikfr)
### Docs
6bb09d8 #1146: Docs: add a link to settings documentation. (@adamlwgriffiths)
095e759 #1103: Docs: add section about monitoring (@AntoineAugusti)
e942486 #1090: Contributing Guide (@arikfr)
3037c4f #1066: Docs: command type-o fix. (@edwardsharp)
2ee0065 #1038: Add an ISSUE_TEMPLATE.md to direct people at the forum (@arikfr)
f7322a4 #1021: Vagrant docs: add purging the cache step (@ariarijp)
- 6bb09d8 #1146: Docs: add a link to settings documentation. (@adamlwgriffiths)
- 095e759 #1103: Docs: add section about monitoring (@AntoineAugusti)
- e942486 #1090: Contributing Guide (@arikfr)
- 3037c4f #1066: Docs: command type-o fix. (@edwardsharp)
- 2ee0065 #1038: Add an ISSUE_TEMPLATE.md to direct people at the forum (@arikfr)
- f7322a4 #1021: Vagrant docs: add purging the cache step (@ariarijp)
---

View File

@@ -6,7 +6,7 @@ The following is a set of guidelines for contributing to Redash. These are guide
## Quick Links:
- [Feature Roadmap](https://trello.com/b/b2LUHU7A/re-dash-roadmap)
- [Feature Roadmap](https://trello.com/b/b2LUHU7A/redash-roadmap)
- [Feature Requests](https://discuss.redash.io/c/feature-requests)
- [Gitter Chat](https://gitter.im/getredash/redash) or [Slack](https://slack.redash.io)
- [Documentation](https://redash.io/help/)
@@ -29,7 +29,7 @@ The following is a set of guidelines for contributing to Redash. These are guide
- [Documentation](#documentation)
- Design?
[Addtional Notes](#additional-notes)
[Additional Notes](#additional-notes)
- [Release Method](#release-method)
- [Code of Conduct](#code-of-conduct)
@@ -46,9 +46,9 @@ When creating a new bug report, please make sure to:
### Suggesting Enhancements / Feature Requests
If you would like to suggest an enchancement or ask for a new feature:
If you would like to suggest an enhancement or ask for a new feature:
- Please check [the roadmap](https://trello.com/b/b2LUHU7A/re-dash-roadmap) for existing Trello card for what you want to suggest/ask. If there is, feel free to upvote it to signal interest or add your comments.
- Please check [the roadmap](https://trello.com/b/b2LUHU7A/redash-roadmap) for existing Trello card for what you want to suggest/ask. If there is, feel free to upvote it to signal interest or add your comments.
- If there is no existing card, open a thread in [the forum](https://discuss.redash.io/c/feature-requests) to start a discussion about what you want to suggest. Try to provide as much details and context as possible and include information about *the problem you want to solve* rather only *your proposed solution*.
### Pull Requests
@@ -56,7 +56,7 @@ If you would like to suggest an enchancement or ask for a new feature:
- **Code contributions are welcomed**. For big changes or significant features, it's usually better to reach out first and discuss what you want to implement and how (we recommend reading: [Pull Request First](https://medium.com/practical-blend/pull-request-first-f6bb667a9b6#.ozlqxvj36)). This to make sure that what you want to implement is aligned with our goals for the project and that no one else is already working on it.
- Include screenshots and animated GIFs in your pull request whenever possible.
- Please add [documentation](#documentation) for new features or changes in functionality along with the code.
- Please follow existing code style. We use PEP8 for Python and sensible style for Javascript.
- Please follow existing code style. We use PEP8 for Python and sensible style for JavaScript.
### Documentation

View File

@@ -4,6 +4,7 @@ FULL_VERSION=$(VERSION)+b$(CIRCLE_BUILD_NUM)
BASE_VERSION=$(shell python ./manage.py version | cut -d + -f 1)
# VERSION gets evaluated every time it's referenced, therefore we need to use VERSION here instead of FULL_VERSION.
FILENAME=$(CIRCLE_ARTIFACTS)/$(NAME).$(VERSION).tar.gz
TEST_ARGS?=--with-coverage --cover-package=redash tests/
deps:
if [ -d "./client/app" ]; then npm install; fi
@@ -17,4 +18,4 @@ upload:
python bin/release_manager.py $(CIRCLE_SHA1) $(BASE_VERSION) $(FILENAME)
test:
nosetests --with-coverage --cover-package=redash tests/
nosetests $(TEST_ARGS)

View File

@@ -43,7 +43,7 @@ You can try out the demo instance: http://demo.redash.io/ (login with any Google
## Reporting Bugs and Contributing Code
* Want to report a bug or request a feature? Please open [an issue](https://github.com/getredash/redash/issues/new).
* Want to help us build **_Redash_**? Fork the project, edit in a [dev environment](https://redash.io/help-onpremise/setup/setting-up-development-environment-using-vagrant.html), and make a pull request. We need all the help we can get!
* Want to help us build **_Redash_**? Fork the project, edit in a [dev environment](https://redash.io/help-onpremise/dev/guide.html), and make a pull request. We need all the help we can get!
## License

View File

@@ -19,7 +19,7 @@ scheduler() {
}
server() {
exec /usr/local/bin/gunicorn -b 0.0.0.0:5000 --name redash -w4 redash.wsgi:app
exec /usr/local/bin/gunicorn -b 0.0.0.0:5000 --name redash -w${REDASH_WEB_WORKERS:-4} redash.wsgi:app
}
help() {
@@ -72,7 +72,10 @@ case "$1" in
tests)
tests
;;
*)
help)
help
;;
*)
exec "$@"
;;
esac

View File

@@ -1,3 +1,4 @@
from __future__ import print_function
import os
import sys
import json
@@ -95,7 +96,7 @@ def get_changelog(commit_sha):
try:
pull_request = re.match("Merge pull request #(\d+)", subject).groups()[0]
pull_request = " #{}".format(pull_request)
except Exception, ex:
except Exception as ex:
pull_request = ""
author = subprocess.check_output(['git', 'log', '-1', '--pretty=format:"%an"', parents.split(' ')[-1]])[1:-1]
@@ -124,7 +125,7 @@ def update_release(version, build_filepath, commit_sha):
else:
release = create_release(version, commit_sha)
print "Using release id: {}".format(release['id'])
print("Using release id: {}".format(release['id']))
remove_previous_builds(release)
response = upload_asset(release, build_filepath)
@@ -135,8 +136,8 @@ def update_release(version, build_filepath, commit_sha):
if response.status_code != 200:
raise exception_from_error("Failed updating release description", response)
except Exception, ex:
print ex
except Exception as ex:
print(ex)
if __name__ == '__main__':
commit_sha = sys.argv[1]

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
import os
import argparse
import os
import subprocess
import sys
from collections import namedtuple
@@ -111,7 +111,10 @@ def restart_services():
# otherwise it won't notice that /opt/redash/current pointing at a different
# directory.
green("Restarting...")
try:
run('sudo /etc/init.d/redash_supervisord restart')
except subprocess.CalledProcessError as e:
run('sudo service supervisor restart')
def update_requirements(version_name):

View File

@@ -18,7 +18,7 @@ test:
- nosetests --with-xunit --xunit-file=$CIRCLE_TEST_REPORTS/junit.xml --with-coverage --cover-package=redash tests/
deployment:
github_and_docker:
branch: master
branch: [master, /release.*/]
commands:
- make pack
# Skipping uploads for now, until master is stable.

View File

@@ -1,4 +1,4 @@
{
"presets": ["es2015", "stage-2"],
"plugins": ["transform-object-assign"]
"plugins": ["angularjs-annotate", "transform-object-assign"]
}

View File

@@ -1,2 +1,3 @@
build/*.js
config/*.js
node_modules

View File

@@ -1,15 +1,30 @@
module.exports = {
root: true,
extends: 'airbnb-base',
extends: "airbnb-base",
settings: {
"import/resolver": "webpack"
},
env: {
"browser": true,
"node": true
},
rules: {
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-param-reassign': 0,
'no-mixed-operators': 0,
'no-underscore-dangle': 0,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
"prefer-destructuring": "off",
"prefer-template": "off",
"no-restricted-properties": "off",
"no-restricted-globals": "off",
"no-multi-assign": "off",
"max-len": ['error', 120, 2, {
ignoreUrls: true,
ignoreComments: false,
ignoreRegExpLiterals: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
}]
}
};

View File

@@ -4,12 +4,17 @@ body {
body.headless {
padding-top: 0px;
padding-bottom: 0px;
}
body.headless nav.app-header {
display: none;
}
body.headless div#footer {
display: none;
}
a[ng-click] {
cursor: pointer;
}
@@ -432,6 +437,10 @@ counter-renderer counter-name {
border: 1px solid rgba(0,0,0,.15);
}
.parameter-label {
display: block;
}
div.table-name {
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -1781,6 +1781,9 @@ fieldset[disabled] .form-control {
textarea.form-control {
height: auto;
}
textarea.v-resizable {
resize: vertical;
}
input[type="search"] {
-webkit-appearance: none;
}
@@ -8592,6 +8595,7 @@ a.thumbnail.active {
padding: 0;
white-space: nowrap;
margin: 0;
margin-bottom: 10px;
overflow: auto;
box-shadow: inset 0 -2px 0 0 #eee;
}

View File

@@ -0,0 +1,113 @@
import { contains, without, compact } from 'underscore';
import template from './alert-subscriptions.html';
function controller($scope, $q, $sce, currentUser, AlertSubscription, Destination, toastr) {
'ngInject';
$scope.newSubscription = {};
$scope.subscribers = [];
$scope.destinations = [];
$scope.currentUser = currentUser;
$q
.all([
Destination.query().$promise,
AlertSubscription.query({ alertId: $scope.alertId }).$promise,
])
.then((responses) => {
const destinations = responses[0];
const subscribers = responses[1];
const mapF = s => s.destination && s.destination.id;
const subscribedDestinations = compact(subscribers.map(mapF));
const subscribedUsers = compact(subscribers.map(s => !s.destination && s.user.id));
$scope.destinations = destinations.filter(d => !contains(subscribedDestinations, d.id));
if (!contains(subscribedUsers, currentUser.id)) {
$scope.destinations.unshift({ user: { name: currentUser.name } });
}
$scope.newSubscription.destination = $scope.destinations[0];
$scope.subscribers = subscribers;
});
$scope.destinationsDisplay = (d) => {
if (!d) {
return '';
}
let destination = d;
if (d.destination) {
destination = destination.destination;
} else if (destination.user) {
destination = {
name: `${d.user.name} (Email)`,
icon: 'fa-envelope',
type: 'user',
};
}
return $sce.trustAsHtml(`<i class="fa ${destination.icon}"></i>&nbsp;${destination.name}`);
};
$scope.saveSubscriber = () => {
const sub = new AlertSubscription({ alert_id: $scope.alertId });
if ($scope.newSubscription.destination.id) {
sub.destination_id = $scope.newSubscription.destination.id;
}
sub.$save(
() => {
toastr.success('Subscribed.');
$scope.subscribers.push(sub);
$scope.destinations = without($scope.destinations, $scope.newSubscription.destination);
if ($scope.destinations.length > 0) {
$scope.newSubscription.destination = $scope.destinations[0];
} else {
$scope.newSubscription.destination = undefined;
}
},
() => {
toastr.error('Failed saving subscription.');
},
);
};
$scope.unsubscribe = (subscriber) => {
const destination = subscriber.destination;
const user = subscriber.user;
subscriber.$delete(
() => {
toastr.success('Unsubscribed');
$scope.subscribers = without($scope.subscribers, subscriber);
if (destination) {
$scope.destinations.push(destination);
} else if (user.id === currentUser.id) {
$scope.destinations.push({ user: { name: currentUser.name } });
}
if ($scope.destinations.length === 1) {
$scope.newSubscription.destination = $scope.destinations[0];
}
},
() => {
toastr.error('Failed unsubscribing.');
},
);
};
}
export default function init(ngModule) {
ngModule.directive('alertSubscriptions', () => ({
restrict: 'E',
replace: true,
scope: {
alertId: '=',
},
template,
controller,
}));
}

View File

@@ -7,7 +7,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/"><img ng-src="{{$ctrl.logoUrl}}"/></a>
<a class="navbar-brand" ng-href="{{$ctrl.basePath}}"><img ng-src="{{$ctrl.logoUrl}}"/></a>
</div>
<div class="collapse navbar-collapse" uib-collapse="!isNavOpen">
<ul class="nav navbar-nav">
@@ -32,7 +32,7 @@
<li><a href="queries">Queries</a></li>
</ul>
</li>
<li>
<li ng-if="$ctrl.showAlertsLink">
<a href="alerts">Alerts</a>
</li>
</ul>

View File

@@ -1,16 +1,17 @@
import debug from 'debug';
import logoUrl from '@/assets/images/redash_icon_small.png';
import template from './app-header.html';
import logoUrl from '../../assets/images/redash_icon_small.png';
import './app-header.css';
const logger = debug('redash:appHeader');
function controller($rootScope, $location, $uibModal, Auth, currentUser, Dashboard) {
// TODO: logoUrl should come from clientconfig
function controller($rootScope, $location, $uibModal, Auth, currentUser, clientConfig, Dashboard) {
this.logoUrl = logoUrl;
this.basePath = clientConfig.basePath;
this.currentUser = currentUser;
this.showQueriesMenu = currentUser.hasPermission('view_query');
this.showAlertsLink = currentUser.hasPermission('list_alerts');
this.showNewQueryMenu = currentUser.hasPermission('create_query');
this.showSettingsMenu = currentUser.hasPermission('list_users');
this.showDashboardsMenu = currentUser.hasPermission('list_dashboards');
@@ -42,7 +43,7 @@ function controller($rootScope, $location, $uibModal, Auth, currentUser, Dashboa
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('appHeader', {
template,
controller,

View File

@@ -27,6 +27,6 @@ function cancelQueryButton() {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('cancelQueryButton', cancelQueryButton);
}

View File

@@ -17,6 +17,8 @@ const AddWidgetDialog = {
this.query = {};
this.selected_query = undefined;
this.text = '';
this.existing_text = '';
this.new_text = '';
this.widgetSizes = [{
name: 'Regular',
value: 1,
@@ -95,6 +97,6 @@ const AddWidgetDialog = {
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('addWidgetDialog', AddWidgetDialog);
}

View File

@@ -4,7 +4,14 @@
</div>
<div class="modal-body">
<p>
<input type="text" class="form-control" placeholder="Dashboard Name" ng-model="$ctrl.dashboard.name">
<input type="text" class="form-control" placeholder="Dashboard Name" ng-model="$ctrl.dashboard.name" autofocus>
</p>
<p ng-if="$ctrl.dashboard.id">
<label>
<input name="input" type="checkbox" ng-model="$ctrl.dashboard.dashboard_filters_enabled">
Use Dashboard Level Filters
</label>
</p>
<div gridster="$ctrl.gridsterOptions" ng-if="$ctrl.items | notEmpty">
@@ -17,5 +24,5 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" ng-disabled="$ctrl.saveInProgress" ng-click="$ctrl.dismiss()">Close</button>
<button type="button" class="btn btn-primary" ng-disabled="$ctrl.saveInProgress" ng-click="$ctrl.saveDashboard()">Save</button>
<button type="button" class="btn btn-primary" ng-disabled="$ctrl.saveInProgress || !$ctrl.isFormValid()" ng-click="$ctrl.saveDashboard()">Save</button>
</div>

View File

@@ -1,4 +1,4 @@
import { sortBy } from 'underscore';
import { isEmpty, sortBy } from 'underscore';
import template from './edit-dashboard-dialog.html';
const EditDashboardDialog = {
@@ -45,6 +45,8 @@ const EditDashboardDialog = {
});
}
this.isFormValid = () => !isEmpty(this.dashboard.name);
this.saveDashboard = () => {
this.saveInProgress = true;
@@ -65,6 +67,7 @@ const EditDashboardDialog = {
slug: this.dashboard.id,
name: this.dashboard.name,
version: this.dashboard.version,
dashboard_filters_enabled: this.dashboard.dashboard_filters_enabled,
layout: JSON.stringify(layout),
};
@@ -96,6 +99,6 @@ const EditDashboardDialog = {
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('editDashboardDialog', EditDashboardDialog);
}

View File

@@ -4,11 +4,11 @@
</div>
<div class="modal-body">
<div class="form-group">
<textarea class="form-control" ng-model="$ctrl.widget.text" rows="3"></textarea>
<textarea class="form-control" ng-model="$ctrl.widget.new_text" rows="3"></textarea>
</div>
<div ng-show="$ctrl.widget.text">
<div ng-show="$ctrl.widget.new_text">
<strong>Preview:</strong>
<p ng-bind-html="$ctrl.widget.text | markdown"></p>
<p ng-bind-html="$ctrl.widget.new_text | markdown"></p>
</div>
</div>

View File

@@ -15,6 +15,8 @@ const EditTextBoxComponent = {
this.widget = this.resolve.widget;
this.saveWidget = () => {
this.saveInProgress = true;
if (this.widget.new_text !== this.widget.existing_text) {
this.widget.text = this.widget.new_text;
this.widget.$save().then(() => {
this.close();
}).catch(() => {
@@ -22,6 +24,9 @@ const EditTextBoxComponent = {
}).finally(() => {
this.saveInProgress = false;
});
} else {
this.close();
}
};
},
};
@@ -30,6 +35,8 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
this.canViewQuery = currentUser.hasPermission('view_query');
this.editTextBox = () => {
this.widget.existing_text = this.widget.text;
this.widget.new_text = this.widget.text;
$uibModal.open({
component: 'editTextBox',
resolve: {
@@ -78,8 +85,8 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
};
if (this.widget.visualization) {
Events.record('view', 'query', this.widget.visualization.query.id);
Events.record('view', 'visualization', this.widget.visualization.id);
Events.record('view', 'query', this.widget.visualization.query.id, { dashboard: true });
Events.record('view', 'visualization', this.widget.visualization.id, { dashboard: true });
this.query = this.widget.getQuery();
this.reload(false);
@@ -92,7 +99,7 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
}
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('editTextBox', EditTextBoxComponent);
ngModule.component('dashboardWidget', {
template,

View File

@@ -1,27 +1,28 @@
<form name="dataSourceForm">
<div class="form-group">
<label for="type">Type</label>
<select name="type" class="form-control" ng-options="type.type as type.name for type in types" ng-model="target.type" autofocus></select>
</div>
<div class="form-group">
<label for="dataSourceName">Name</label>
<input type="string" class="form-control" name="dataSourceName" ng-model="target.name" required>
</div>
<div class="form-group">
<label for="type">Type</label>
<select name="type" class="form-control" ng-options="type.type as type.name for type in types" ng-model="target.type"></select>
</div>
<div class="form-group" ng-class='{"has-error": !inner.input.$valid}' ng-form="inner" ng-repeat="(name, input) in type.configuration_schema.properties">
<label ng-if="input.type !== 'checkbox'">{{input.title || name | capitalize}}</label>
<input name="input" type="{{input.type}}" class="form-control" ng-model="target.options[name]" ng-required="input.required"
ng-if="input.type !== 'file' && input.type !== 'checkbox'" accesskey="tab" placeholder="{{input.default}}">
<div class="form-group" ng-class='{"has-error": !inner.input.$valid}' ng-form="inner" ng-repeat="field in fields">
<label ng-if="field.property.type !== 'checkbox'">{{field.property.title || field.name | capitalize}}</label>
<input name="input" type="{{field.property.type}}" class="form-control" ng-model="target.options[field.name]" ng-required="field.property.required"
ng-if="field.property.type !== 'file' && field.property.type !== 'checkbox'" accesskey="tab" placeholder="{{field.property.default}}">
<label ng-if="input.type=='checkbox'">
<input name="input" type="{{input.type}}" ng-model="target.options[name]" ng-required="input.required"
ng-if="input.type !== 'file'" accesskey="tab" placeholder="{{input.default}}">
{{input.title || name | capitalize}}
<label ng-if="field.property.type=='checkbox'">
<input name="input" type="{{field.property.type}}" ng-model="target.options[field.name]" ng-required="field.property.required"
ng-if="field.property.type !== 'file'" accesskey="tab" placeholder="{{field.property.default}}">
{{field.property.title || field.name | capitalize}}
</label>
<input name="input" type="file" class="form-control" ng-model="files[name]" ng-required="input.required && !target.options[name]"
<input name="input" type="file" class="form-control" ng-model="files[field.name]" ng-required="field.property.required && !target.options[field.name]"
base-sixty-four-input
ng-if="input.type === 'file'">
ng-if="field.property.type === 'file'">
</div>
<button class="btn btn-primary" ng-disabled="!dataSourceForm.$valid" ng-click="saveChanges()">Save</button>
<span ng-repeat="action in actions">
<button class="btn"

View File

@@ -1,8 +1,22 @@
import { each, contains, find } from 'underscore';
import { isUndefined, each, contains, find } from 'underscore';
import endsWith from 'underscore.string/endsWith';
import template from './dynamic-form.html';
function DynamicForm($http, toastr, $q) {
function orderedInputs(properties, order) {
const inputs = new Array(order.length);
Object.keys(properties).forEach((key) => {
const position = order.indexOf(key);
const input = { name: key, property: properties[key] };
if (position > -1) {
inputs[position] = input;
} else {
inputs.push(input);
}
});
return inputs;
}
return {
restrict: 'E',
replace: 'true',
@@ -19,7 +33,15 @@ function DynamicForm($http, toastr, $q) {
$scope.target.type = types[0].type;
}
$scope.type = find(types, t => t.type === $scope.target.type);
const type = find(types, t => t.type === $scope.target.type);
const configurationSchema = type.configuration_schema;
$scope.fields = orderedInputs(
configurationSchema.properties,
configurationSchema.order || [],
);
return type;
}
$scope.inProgressActions = {};
@@ -35,6 +57,7 @@ function DynamicForm($http, toastr, $q) {
$scope.inProgressActions[action.name] = false;
action.name = name;
}
originalCallback(release);
};
});
@@ -80,29 +103,47 @@ function DynamicForm($http, toastr, $q) {
prop.required = contains(type.configuration_schema.required, name);
});
});
});
$scope.$watch('target.type', (current, prev) => {
if (prev !== current) {
if (prev !== undefined) {
$scope.target.options = {};
}
setType($scope.types);
const type = setType($scope.types);
if (Object.keys($scope.target.options).length === 0) {
const properties = type.configuration_schema.properties;
Object.keys(properties).forEach((property) => {
if (!isUndefined(properties[property].default)) {
$scope.target.options[property] = properties[property].default;
}
});
}
}
});
});
$scope.saveChanges = () => {
$scope.target.$save(() => {
$scope.target.$save(
() => {
toastr.success('Saved.');
$scope.dataSourceForm.$setPristine();
}, () => {
},
(error) => {
if (error.status === 400 && 'message' in error.data) {
toastr.error(error.data.message);
} else {
toastr.error('Failed saving.');
});
}
},
);
};
},
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('dynamicForm', DynamicForm);
}

View File

@@ -9,7 +9,7 @@
</thead>
<tbody>
<tr ng-repeat="row in $ctrl.rows">
<tr ng-repeat="row in $ctrl.rowsToDisplay">
<td ng-repeat="column in $ctrl.columns" ng-bind-html="$ctrl.sanitize(column.formatFunction(row[column.name]))">
</td>
</tr>

View File

@@ -15,7 +15,7 @@ function DynamicTable($sanitize) {
const first = this.count * (this.page - 1);
const last = this.count * (this.page);
this.rows = this.allRows.slice(first, last);
this.rowsToDisplay = this.rows.slice(first, last);
};
this.$onChanges = (changes) => {
@@ -24,10 +24,10 @@ function DynamicTable($sanitize) {
}
if (changes.rows) {
this.allRows = changes.rows.currentValue;
this.rows = changes.rows.currentValue;
}
this.rowsCount = this.allRows.length;
this.rowsCount = this.rows.length;
this.pageChanged();
};
@@ -41,9 +41,9 @@ function DynamicTable($sanitize) {
}
if (this.orderByField) {
this.allRows = sortBy(this.allRows, this.orderByField.name);
this.rows = sortBy(this.rows, this.orderByField.name);
if (this.orderByReverse) {
this.allRows = this.allRows.reverse();
this.rows = this.rows.reverse();
}
this.pageChanged();
}
@@ -64,7 +64,7 @@ function DynamicTable($sanitize) {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('dynamicTable', {
template,
controller: DynamicTable,

View File

@@ -33,6 +33,8 @@ function EditInPlace() {
link($scope, element) {
// Let's get a reference to the input element, as we'll want to reference it.
const inputElement = $(element.children()[2]);
const keycodeEnter = 13;
const keycodeEscape = 27;
// This directive should have a set class so we can style it.
element.addClass('edit-in-place');
@@ -74,9 +76,10 @@ function EditInPlace() {
$(inputElement).keydown((e) => {
// 'return' or 'enter' key pressed
// allow 'shift' to break lines
if (e.which === 13 && !e.shiftKey) {
if (e.which === keycodeEnter && !e.shiftKey) {
e.preventDefault();
save();
} else if (e.which === 27) {
} else if (e.which === keycodeEscape) {
$scope.value = $scope.oldValue;
$scope.$apply(() => {
$(inputElement[0]).blur();
@@ -89,6 +92,6 @@ function EditInPlace() {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('editInPlace', EditInPlace);
}

View File

@@ -2,7 +2,7 @@ function controller(clientConfig, currentUser) {
this.showMailWarning = clientConfig.mailSettingsMissing && currentUser.isAdmin;
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('emailSettingsWarning', {
bindings: {
function: '<',

View File

@@ -15,6 +15,6 @@ const ErrorMessagesComponent = {
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('errorMessages', ErrorMessagesComponent);
}

View File

@@ -1,20 +1,34 @@
<div class="container bg-white p-5" ng-show="$ctrl.filters | notEmpty">
<div class="row">
<div class="col-sm-6 m-t-5" ng-repeat="filter in $ctrl.filters">
<ui-select ng-model="filter.current" ng-if="!filter.multiple" on-select="$ctrl.filterChangeListener(filter, $model)" on-remove="$ctrl.filterChangeListener(filter, $model)">
<ui-select-match placeholder="Select value for {{filter.friendlyName}}...">{{filter.friendlyName}}: {{$select.selected | filterValue:filter}}</ui-select-match>
<label>{{filter.friendlyName}}</label>
</div>
</div>
<div class="row">
<div class="col-sm-6 m-t-5" ng-repeat="filter in $ctrl.filters">
<ui-select ng-model="filter.current" ng-if="!filter.multiple" on-select="$ctrl.filterChangeListener(filter, $model)" on-remove="$ctrl.filterChangeListener(filter, $model)"
remove-selected="false">
<ui-select-match placeholder="Select value for {{filter.friendlyName}}...">{{$select.selected | filterValue:filter}}</ui-select-match>
<ui-select-choices repeat="value in filter.values | filter: $select.search">
{{value | filterValue:filter }}
</ui-select-choices>
</ui-select>
<ui-select ng-model="filter.current" multiple ng-if="filter.multiple" on-select="$ctrl.filterChangeListener(filter, $model)" on-remove="$ctrl.filterChangeListener(filter, $model)">
<ui-select-match placeholder="Select value for {{filter.friendlyName}}...">{{filter.friendlyName}}: {{$item | filterValue:filter}}</ui-select-match>
<ui-select-choices repeat="value in filter.values | filter: $select.search">
<ui-select ng-model="filter.current" multiple ng-if="filter.multiple" on-select="$ctrl.filterChangeListener(filter, $model)"
on-remove="$ctrl.filterChangeListener(filter, $model)" remove-selected="false">
<ui-select-match placeholder="Select value for {{filter.friendlyName}}...">{{$item | filterValue:filter}}</ui-select-match>
<ui-select-choices repeat="value in filter.values | filter: $select.search" group-by="$ctrl.itemGroup">
<span ng-if="value == '*'">
Select All
</span>
<span ng-if="value == '-'">
Clear
</span>
<span ng-if="value != '*' && value != '-'">
{{value | filterValue:filter }}
</span>
</ui-select-choices>
</ui-select>
</div>
</div>
</div>

View File

@@ -12,10 +12,18 @@ const FiltersComponent = {
this.filterChangeListener = (filter, modal) => {
this.onChange({ filter, $modal: modal });
};
this.itemGroup = (item) => {
if (item === '*' || item === '-') {
return '';
}
return 'Values';
};
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('filters', FiltersComponent);
}

View File

@@ -5,7 +5,7 @@ function controller(clientConfig, currentUser) {
this.newVersionAvailable = clientConfig.newVersionAvailable && currentUser.isAdmin;
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('footer', {
template,
controller,

View File

@@ -3,7 +3,7 @@
</div>
<div class="modal-body">
<form class="form">
<input type="text" ng-model="$ctrl.group.name" placeholder="Group Name" class="form-control"/>
<input type="text" ng-model="$ctrl.group.name" placeholder="Group Name" class="form-control" autofocus/>
</form>
</div>
<div class="modal-footer">

View File

@@ -34,6 +34,6 @@ const EditGroupDialogComponent = {
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('editGroupDialog', EditGroupDialogComponent);
}

View File

@@ -15,7 +15,7 @@ function controller($window, $location, toastr, currentUser) {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('groupName', {
bindings: {
group: '<',

View File

@@ -1,20 +0,0 @@
export { default as appHeader } from './app-header';
export { default as footer } from './footer';
export { default as pageHeader } from './page-header';
export { default as tabNav } from './tab-nav';
export { default as emailSettingsWarning } from './email-settings-warning';
export { default as rdTab } from './rd-tab';
export { default as queryLink } from './query-link';
export { default as parameters } from './parameters';
export { default as permissionsEditor } from './permissions-editor';
export { default as dynamicTable } from './dynamic-table';
export { default as paginator } from './paginator';
export { default as settingsScreen } from './settings-screen';
export { default as errorMessages } from './error-messages';
export { default as editInPlace } from './edit-in-place';
export { default as dynamicForm } from './dynamic-form';
export { default as rdTimer } from './rd-timer';
export { default as rdTimeAgo } from './rd-time-ago';
export { default as overlay } from './overlay';
export { default as routeStatus } from './route-status';
export { default as filters } from './filters';

View File

@@ -11,6 +11,6 @@ const Overlay = {
transclude: true,
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('overlay', Overlay);
}

View File

@@ -4,7 +4,7 @@ function controller() {
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('pageHeader', {
template,
controller,

View File

@@ -7,7 +7,7 @@ class PaginatorCtrl {
}
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('paginator', {
template: `
<div class="text-center">

View File

@@ -13,14 +13,33 @@
<select ng-model="$ctrl.parameter.type" class="form-control">
<option value="text">Text</option>
<option value="number">Number</option>
<option value="enum">Dropdown List</option>
<option value="query">Query Based Dropdown List</option>
<option value="date">Date</option>
<option value="datetime-local">Date and Time</option>
<option value="datetime-with-seconds">Date and Time (with seconds)</option>
</select>
</div>
<div class="form-group">
<label>Global</label>
<label>
<input type="checkbox" class="form-inline" ng-model="$ctrl.parameter.global">
Global
</label>
</div>
<div class="form-group" ng-if="$ctrl.parameter.type === 'enum'">
<label>Dropdown List Values (newline delimited)</label>
<textarea class="form-control" rows="3" ng-model="$ctrl.parameter.enumOptions"></textarea>
</div>
<div class="form-group" ng-if="$ctrl.parameter.type === 'query'">
<label>Query to load dropdown values from:</label>
<ui-select ng-model="$ctrl.parameter.queryId" reset-search-input="false">
<ui-select-match placeholder="Search a query by name">{{$select.selected.name}}</ui-select-match>
<ui-select-choices repeat="q.id as q in $ctrl.queries"
refresh="$ctrl.searchQueries($select.search)"
refresh-delay="0">
<div class="form-group" ng-bind-html="$ctrl.trustAsHtml(q.name | highlight: $select.search)"></div>
</ui-select-choices>
</ui-select>
</div>
</div>
</div>

View File

@@ -1,11 +1,27 @@
<div class="form-inline bg-white p-5" ng-if="parameters | notEmpty" ui-sortable="{ 'ui-floating': true, 'disabled': !editable }" ng-model="parameters">
<div class="form-group" ng-repeat="param in parameters">
<label>{{param.title}}</label>
<button class="btn btn-default btn-xs" ng-click="showParameterSettings(param)" ng-if="editable"><i class="zmdi zmdi-settings"></i></button>
<div class="form-inline bg-white p-5"
ng-if="parameters | notEmpty"
ui-sortable="{ 'ui-floating': true, 'disabled': !editable }"
ng-model="parameters">
<div class="form-group m-l-10 m-r-10"
ng-repeat="param in parameters">
<label class="parameter-label">{{param.title}}</label>
<button class="btn btn-default btn-xs"
ng-click="showParameterSettings(param)"
ng-if="editable">
<i class="zmdi zmdi-settings"></i>
</button>
<span ng-switch="param.type">
<input ng-switch-when="datetime-with-seconds" type="datetime-local" step="1" class="form-control" ng-model="param.ngModel">
<input ng-switch-when="datetime-local" type="datetime-local" class="form-control" ng-model="param.ngModel">
<input ng-switch-when="date" type="date" class="form-control" ng-model="param.ngModel">
<span ng-switch-when="enum">
<select ng-model="param.value" class="form-control">
<option ng-repeat="option in extractEnumOptions(param.enumOptions)" value="{{option}}">{{option}}</option>
</select>
</span>
<span ng-switch-when="query">
<query-based-parameter param="param" query-id="param.queryId"></query-based-parameter>
</span>
<input ng-switch-default type="{{param.type}}" class="form-control" ng-model="param.ngModel">
</span>
</div>

View File

@@ -1,4 +1,6 @@
import { find } from 'underscore';
import template from './parameters.html';
import queryBasedParameterTemplate from './query-based-parameter.html';
import parameterSettingsTemplate from './parameter-settings.html';
const ParameterSettingsComponent = {
@@ -8,10 +10,96 @@ const ParameterSettingsComponent = {
close: '&',
dismiss: '&',
},
controller() {
controller($sce, Query) {
'ngInject';
this.trustAsHtml = html => $sce.trustAsHtml(html);
this.parameter = this.resolve.parameter;
if (this.parameter.queryId) {
Query.get({ id: this.parameter.queryId }, (query) => {
this.queries = [query];
});
}
this.searchQueries = (term) => {
if (!term || term.length < 3) {
return;
}
Query.search({ q: term }, (results) => {
this.queries = results;
});
};
},
};
function optionsFromQueryResult(queryResult) {
const columns = queryResult.data.columns;
const numColumns = columns.length;
let options = [];
// If there are multiple columns, check if there is a column
// named 'name' and column named 'value'. If name column is present
// in results, use name from name column. Similar for value column.
// Default: Use first string column for name and value.
if (numColumns > 0) {
let nameColumn = null;
let valueColumn = null;
columns.forEach((column) => {
const columnName = column.name.toLowerCase();
if (columnName === 'name') {
nameColumn = column.name;
}
if (columnName === 'value') {
valueColumn = column.name;
}
// Assign first string column as name and value column.
if (nameColumn === null) {
nameColumn = column.name;
}
if (valueColumn === null) {
valueColumn = column.name;
}
});
if (nameColumn !== null && valueColumn !== null) {
options = queryResult.data.rows.map((row) => {
const queryResultOption = {
name: row[nameColumn],
value: row[valueColumn],
};
return queryResultOption;
});
}
}
return options;
}
function updateCurrentValue(param, options) {
const found = find(options, option => option.value === param.value) !== undefined;
if (!found) {
param.value = options[0].value;
}
}
const QueryBasedParameterComponent = {
template: queryBasedParameterTemplate,
bindings: {
param: '<',
queryId: '<',
},
controller(Query) {
'ngInject';
this.$onChanges = (changes) => {
if (changes.queryId) {
Query.resultById({ id: this.queryId }, (result) => {
const queryResult = result.query_result;
this.queryResultOptions = optionsFromQueryResult(queryResult);
updateCurrentValue(this.param, this.queryResultOptions);
});
}
};
},
};
@@ -41,6 +129,14 @@ function ParametersDirective($location, $uibModal) {
}, true);
}
// These are input as newline delimited values,
// so we split them here.
scope.extractEnumOptions = (enumOptions) => {
if (enumOptions) {
return enumOptions.split('\n');
}
return [];
};
scope.showParameterSettings = (param) => {
$uibModal.open({
component: 'parameterSettings',
@@ -53,7 +149,8 @@ function ParametersDirective($location, $uibModal) {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('parameters', ParametersDirective);
ngModule.component('queryBasedParameter', QueryBasedParameterComponent);
ngModule.component('parameterSettings', ParameterSettingsComponent);
}

View File

@@ -59,7 +59,8 @@ const PermissionsEditorComponent = {
// Remove user from grantees list
this.removeGrantee = (user) => {
const body = { access_type: 'modify', user_id: user.id };
$http({ url: this.aclUrl,
$http({
url: this.aclUrl,
method: 'DELETE',
data: body,
headers: { 'Content-Type': 'application/json' },
@@ -74,6 +75,6 @@ const PermissionsEditorComponent = {
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('permissionsEditor', PermissionsEditorComponent);
}

View File

@@ -32,6 +32,6 @@ function alertUnsavedChanges($window) {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('alertUnsavedChanges', alertUnsavedChanges);
}

View File

@@ -0,0 +1,37 @@
const ApiKeyDialog = {
template: `<div class="modal-header">
<button type="button" class="close" aria-label="Close" ng-click="$ctrl.close()"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body">
<h5>API Key</h5>
<pre>{{$ctrl.apiKey}}</pre>
<h5>Example API Calls:</h5>
<div>
Results in CSV format:
<pre>{{$ctrl.csvUrl}}</pre>
Results in JSON format:
<pre>{{$ctrl.jsonUrl}}</pre>
</div>
</div>`,
controller(clientConfig) {
'ngInject';
this.apiKey = this.resolve.query.api_key;
this.csvUrl = `${clientConfig.basePath}api/queries/${this.resolve.query.id}/results.csv?api_key=${this.apiKey}`;
this.jsonUrl = `${clientConfig.basePath}api/queries/${this.resolve.query.id}/results.json?api_key=${this.apiKey}`;
},
bindings: {
resolve: '<',
close: '&',
dismiss: '&',
},
};
export default function init(ngModule) {
ngModule.component('apiKeyDialog', ApiKeyDialog);
}

View File

@@ -0,0 +1,17 @@
<div class="modal-header">
<button type="button" class="close" aria-label="Close" ng-click="$ctrl.close()"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Embed Code</h4>
</div>
<div class="modal-body">
<h5>IFrame Embed</h5>
<div>
<code>&lt;iframe src="{{ $ctrl.embedUrl }}" width="720" height="391"&gt;&lt;/iframe&gt;</code>
</div>
<span class="text-muted">(height should be adjusted)</span>
<div ng-if="$ctrl.snapshotUrl">
<h5>Image Embed</h5>
<div>
<code style="overflow-wrap:break-word;">{{$ctrl.snapshotUrl}}</code>
</div>
</div>
</div>

View File

@@ -20,6 +20,6 @@ const EmbedCodeDialog = {
template,
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('embedCodeDialog', EmbedCodeDialog);
}

View File

@@ -118,8 +118,7 @@ function queryEditor(QuerySnippet) {
value: k,
score: 0,
meta: v,
})
);
}));
}
callback(null, $scope.schema.keywords);
},
@@ -134,6 +133,6 @@ function queryEditor(QuerySnippet) {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('queryEditor', queryEditor);
}

View File

@@ -21,6 +21,6 @@ function queryResultLink() {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('queryResultLink', queryResultLink);
}

View File

@@ -143,7 +143,7 @@ const ScheduleForm = {
template,
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('queryTimePicker', queryTimePicker);
ngModule.directive('queryRefreshSelect', queryRefreshSelect);
ngModule.component('scheduleDialog', ScheduleForm);

View File

@@ -28,6 +28,6 @@ const SchemaBrowser = {
template,
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('schemaBrowser', SchemaBrowser);
}

View File

@@ -2,7 +2,7 @@
<div class="t-heading p-10">
<h3 class="th-title">
<p>
<img src="{{$ctrl.logoUrl}}" style="height: 24px;"/>
<img ng-src="{{$ctrl.logoUrl}}" style="height: 24px;"/>
{{$ctrl.query.name}}
<small><visualization-name visualization="$ctrl.visualization"/></small>
</p>

View File

@@ -1,6 +1,6 @@
import { find } from 'underscore';
import logoUrl from '@/assets/images/redash_icon_small.png';
import template from './visualization-embed.html';
import logoUrl from '../../assets/images/redash_icon_small.png';
const VisualizationEmbed = {
template,
@@ -22,7 +22,7 @@ const VisualizationEmbed = {
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('visualizationEmbed', VisualizationEmbed);
function session($http, $route, Auth) {
@@ -36,8 +36,8 @@ export default function (ngModule) {
function loadData($http, $route, $q, Auth) {
return session($http, $route, Auth).then(() => {
const queryId = $route.current.params.queryId;
const query = $http.get(`/api/queries/${queryId}`).then(response => response.data);
const queryResult = $http.get(`/api/queries/${queryId}/results.json`).then(response => response.data);
const query = $http.get(`api/queries/${queryId}`).then(response => response.data);
const queryResult = $http.get(`api/queries/${queryId}/results.json${location.search}`).then(response => response.data);
return $q.all([query, queryResult]);
});
}

View File

@@ -0,0 +1,2 @@
<select ng-model="$ctrl.param.value" class="form-control" ng-options="option.value as option.name for option in $ctrl.queryResultOptions">
</select>

View File

@@ -13,7 +13,7 @@ function QueryLinkController() {
this.link = this.query.getUrl(false, hash);
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('queryLink', {
bindings: {
query: '<',

View File

@@ -11,15 +11,14 @@ function rdTab($location) {
replace: true,
link(scope) {
scope.basePath = scope.basePath || $location.path().substring(1);
scope.$watch(() =>
scope.$parent.selectedTab
, (tab) => {
scope.selectedTab = tab;
});
scope.$watch(
() => scope.$parent.selectedTab,
(tab) => { scope.selectedTab = tab; },
);
},
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('rdTab', rdTab);
}

View File

@@ -10,6 +10,6 @@ const RdTimeAgo = {
'</span>',
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('rdTimeAgo', RdTimeAgo);
}

View File

@@ -25,6 +25,6 @@ function rdTimer() {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.directive('rdTimer', rdTimer);
}

View File

@@ -1,4 +1,4 @@
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('routeStatus', {
template: '<overlay ng-if="$ctrl.permissionDenied">You do not have permission to load this page.',

View File

@@ -8,7 +8,7 @@
<li ng-class="{'active': usersPage }" ng-if="showUsersLink"><a href="users">Users</a></li>
<li ng-class="{'active': groupsPage }" ng-if="showGroupsLink"><a href="groups">Groups</a></li>
<li ng-class="{'active': destinationsPage }" ng-if="showDestinationsLink"><a href="destinations">Alert Destinations</a></li>
<li ng-class="{'active': snippetsPage }"><a href="query_snippets">Query Snippets</a></li>
<li ng-class="{'active': snippetsPage }" ng-if="showQuerySnippetsLink"><a href="query_snippets">Query Snippets</a></li>
</ul>
<div ng-transclude>

View File

@@ -1,9 +1,8 @@
import startsWith from 'underscore.string/startsWith';
import template from './settings-screen.html';
export default function (ngModule) {
ngModule.directive('settingsScreen', $location =>
({
export default function init(ngModule) {
ngModule.directive('settingsScreen', $location => ({
restrict: 'E',
transclude: true,
template,
@@ -18,7 +17,7 @@ export default function (ngModule) {
$scope.showUsersLink = currentUser.hasPermission('list_users');
$scope.showDsLink = currentUser.hasPermission('admin');
$scope.showDestinationsLink = currentUser.hasPermission('admin');
$scope.showQuerySnippetsLink = currentUser.hasPermission('create_query');
},
})
);
}));
}

View File

@@ -0,0 +1,26 @@
export default function init(ngModule) {
ngModule.component('sortIcon', {
template: '<span ng-if="$ctrl.showIcon"><i class="fa fa-sort-{{$ctrl.icon}}"></i></span>',
bindings: {
column: '<',
sortColumn: '<',
reverse: '<',
},
controller() {
this.$onChanges = (changes) => {
['column', 'sortColumn', 'reverse'].forEach((v) => {
if (v in changes) {
this[v] = changes[v].currentValue;
}
});
this.showIcon = false;
if (this.column === this.sortColumn) {
this.showIcon = true;
this.icon = this.reverse ? 'desc' : 'asc';
}
};
},
});
}

View File

@@ -10,7 +10,7 @@ function controller($location) {
});
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('tabNav', {
template: '<ul class="tab-nav bg-white">' +
'<li ng-repeat="tab in $ctrl.tabs" ng-class="{\'active\': tab.active }"><a ng-href="{{tab.path}}">{{tab.name}}</a></li>' +

View File

@@ -1,4 +1,4 @@
function VisualizationName(Visualization) {
export default function VisualizationName(Visualization) {
return {
restrict: 'E',
scope: {
@@ -7,7 +7,10 @@ function VisualizationName(Visualization) {
template: '{{name}}',
replace: false,
link(scope) {
if (Visualization.visualizations[scope.visualization.type].name !== scope.visualization.name) {
const currentType = scope.visualization.type;
const nameByType = Visualization.visualizations[currentType].name;
const currentName = scope.visualization.name;
if (nameByType !== currentName) {
scope.name = scope.visualization.name;
}
},

111
client/app/config/index.js Normal file
View File

@@ -0,0 +1,111 @@
// This polyfill is needed to support PhantomJS which we use to generate PNGs from embeds.
import 'core-js/fn/typed/array-buffer';
import 'pace-progress';
import debug from 'debug';
import angular from 'angular';
import ngSanitize from 'angular-sanitize';
import ngRoute from 'angular-route';
import ngResource from 'angular-resource';
import uiBootstrap from 'angular-ui-bootstrap';
import uiSelect from 'ui-select';
import ngMessages from 'angular-messages';
import toastr from 'angular-toastr';
import ngUpload from 'angular-base64-upload';
import vsRepeat from 'angular-vs-repeat';
import 'angular-moment';
import 'brace';
import 'angular-ui-ace';
import 'angular-resizable';
import ngGridster from 'angular-gridster';
import { each } from 'underscore';
import '@/lib/sortable';
import * as filters from '@/filters';
import registerDirectives from '@/directives';
import markdownFilter from '@/filters/markdown';
import dateTimeFilter from '@/filters/datetime';
const logger = debug('redash:config');
const requirements = [
ngRoute,
ngResource,
ngSanitize,
uiBootstrap,
ngMessages,
uiSelect,
'angularMoment',
toastr,
'ui.ace',
ngUpload,
'angularResizable',
vsRepeat,
'ui.sortable',
ngGridster.name,
];
const ngModule = angular.module('app', requirements);
function registerAll(context) {
const modules = context
.keys()
.map(context)
.map(module => module.default);
return modules.map(f => f(ngModule));
}
function registerComponents() {
// We repeat this code in other register functions, because if we don't use a literal for the path
// Webpack won't be able to statcily analyze our imports.
const context = require.context('@/components', true, /^((?![\\/]test[\\/]).)*\.js$/);
registerAll(context);
}
function registerServices() {
const context = require.context('@/services', true, /^((?![\\/]test[\\/]).)*\.js$/);
registerAll(context);
}
function registerVisualizations() {
const context = require.context('@/visualizations', true, /^((?![\\/]test[\\/]).)*\.js$/);
registerAll(context);
}
function registerPages() {
const context = require.context('@/pages', true, /^((?![\\/]test[\\/]).)*\.js$/);
const routesCollection = registerAll(context);
routesCollection.forEach((routes) => {
ngModule.config(($routeProvider) => {
each(routes, (route, path) => {
logger('Registering route: %s', path);
// This is a workaround, to make sure app-header and footer are loaded only
// for the authenticated routes.
// We should look into switching to ui-router, that has built in support for
// such things.
route.template = `<app-header></app-header><route-status></route-status>${route.template}<footer></footer>`;
route.authenticated = true;
$routeProvider.when(path, route);
});
});
});
}
function registerFilters() {
each(filters, (filter, name) => {
ngModule.filter(name, () => filter);
});
}
registerDirectives(ngModule);
registerServices();
registerFilters();
markdownFilter(ngModule);
dateTimeFilter(ngModule);
registerComponents();
registerPages();
registerVisualizations(ngModule);
export default ngModule;

View File

@@ -0,0 +1,11 @@
import 'material-design-iconic-font/dist/css/material-design-iconic-font.css';
import 'font-awesome/css/font-awesome.css';
import 'ui-select/dist/select.css';
import 'angular-toastr/dist/angular-toastr.css';
import 'angular-resizable/src/angular-resizable.css';
import 'angular-gridster/dist/angular-gridster.css';
import 'pace-progress/themes/blue/pace-theme-minimal.css';
import '@/assets/css/superflat_redash.css';
import '@/assets/css/redash.css';
import '@/assets/css/main.scss';

View File

@@ -70,7 +70,7 @@ function title($rootScope, Title) {
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.factory('Title', TitleService);
ngModule.directive('title', title);
ngModule.directive('compareTo', compareTo);

View File

@@ -1,6 +1,6 @@
import moment from 'moment';
export default function (ngModule) {
export default function init(ngModule) {
ngModule.filter('toMilliseconds', () => value => value * 1000.0);
ngModule.filter('dateTime', clientConfig =>
@@ -10,6 +10,5 @@ export default function (ngModule) {
}
return moment(value).format(clientConfig.dateTimeFormat);
}
);
});
}

View File

@@ -45,8 +45,7 @@ export function scheduleHumanize(schedule) {
export function toHuman(text) {
return text.replace(/_/g, ' ').replace(/(?:^|\s)\S/g, a =>
a.toUpperCase()
);
a.toUpperCase());
}
export function colWidth(widgetWidth) {

View File

@@ -1,18 +1,17 @@
import marked from 'marked';
import { markdown } from 'markdown';
export default function (ngModule) {
export default function init(ngModule) {
ngModule.filter('markdown', ($sce, clientConfig) =>
function markdown(text) {
function parseMarkdown(text) {
if (!text) {
return '';
}
let html = marked(String(text));
let html = markdown.toHTML(String(text));
if (clientConfig.allowScriptsInUserInput) {
html = $sce.trustAsHtml(html);
}
return html;
}
);
});
}

View File

@@ -1,14 +1,14 @@
<!DOCTYPE html>
<html ng-app="app">
<html ng-app="app" ng-strict-di>
<head lang="en">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
<base href="/">
<title>Redash</title>
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="./assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
</head>
<body>

View File

@@ -1,102 +1,8 @@
import 'material-design-iconic-font/dist/css/material-design-iconic-font.css';
import 'font-awesome/css/font-awesome.css';
import 'ui-select/dist/select.css';
import 'angular-toastr/dist/angular-toastr.css';
import 'angular-resizable/src/angular-resizable.css';
import 'angular-gridster/dist/angular-gridster.css';
import 'pace-progress/themes/blue/pace-theme-minimal.css';
import '@/config/styles';
import ngModule from '@/config';
import 'pace-progress';
import debug from 'debug';
import angular from 'angular';
import ngSanitize from 'angular-sanitize';
import ngRoute from 'angular-route';
import ngResource from 'angular-resource';
import uiBootstrap from 'angular-ui-bootstrap';
import uiSelect from 'ui-select';
import ngMessages from 'angular-messages';
import toastr from 'angular-toastr';
import ngUpload from 'angular-base64-upload';
import vsRepeat from 'angular-vs-repeat';
import 'angular-moment';
import 'brace';
import 'angular-ui-ace';
import 'angular-resizable';
import ngGridster from 'angular-gridster';
import { each } from 'underscore';
import './sortable';
import './assets/css/superflat_redash.css';
import './assets/css/redash.css';
import './assets/css/main.scss';
import * as pages from './pages';
import * as components from './components';
import * as filters from './filters';
import * as services from './services';
import registerDirectives from './directives';
import registerVisualizations from './visualizations';
import markdownFilter from './filters/markdown';
import dateTimeFilter from './filters/datetime';
const logger = debug('redash');
const requirements = [
ngRoute, ngResource, ngSanitize, uiBootstrap, ngMessages, uiSelect, 'angularMoment', toastr, 'ui.ace',
ngUpload, 'angularResizable', vsRepeat, 'ui.sortable', ngGridster.name,
];
const ngModule = angular.module('app', requirements);
function registerComponents() {
each(components, (register) => {
register(ngModule);
});
}
function registerServices() {
each(services, (register) => {
register(ngModule);
});
}
function registerPages() {
each(pages, (registerPage) => {
const routes = registerPage(ngModule);
ngModule.config(($routeProvider) => {
each(routes, (route, path) => {
logger('Route: ', path);
// This is a workaround, to make sure app-header and footer are loaded only
// for the authenticated routes.
// We should look into switching to ui-router, that has built in support for
// such things.
route.template = `<app-header></app-header><route-status></route-status>${route.template}<footer></footer>`;
route.authenticated = true;
$routeProvider.when(path, route);
});
});
});
}
function registerFilters() {
each(filters, (filter, name) => {
ngModule.filter(name, () => filter);
});
}
registerDirectives(ngModule);
registerServices();
registerFilters();
markdownFilter(ngModule);
dateTimeFilter(ngModule);
registerComponents();
registerPages();
registerVisualizations(ngModule);
ngModule.config(($routeProvider, $locationProvider, $compileProvider,
uiSelectConfig, toastrConfig) => {
ngModule.config(($locationProvider, $compileProvider, uiSelectConfig, toastrConfig) => {
$compileProvider.debugInfoEnabled(false);
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|http|data):/);
$locationProvider.html5Mode(true);
uiSelectConfig.theme = 'bootstrap';
@@ -108,7 +14,8 @@ ngModule.config(($routeProvider, $locationProvider, $compileProvider,
});
// Update ui-select's template to use Font-Awesome instead of glyphicon.
ngModule.run(($templateCache, OfflineListener) => { // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-unused-vars
ngModule.run(($templateCache, OfflineListener) => {
const templateName = 'bootstrap/match.tpl.html';
let template = $templateCache.get(templateName);
template = template.replace('glyphicon glyphicon-remove', 'fa fa-remove');

View File

@@ -1,8 +1,12 @@
import { sortBy } from 'underscore';
export default class Paginator {
constructor(rows, { page = 1, itemsPerPage = 20, totalCount = undefined } = {}) {
this.page = page;
this.itemsPerPage = itemsPerPage;
this.updateRows(rows, totalCount);
this.orderByField = undefined;
this.orderByReverse = false;
}
setPage(page) {
@@ -24,4 +28,20 @@ export default class Paginator {
this.totalCount = 0;
}
}
orderBy(column) {
if (column === this.orderByField) {
this.orderByReverse = !this.orderByReverse;
} else {
this.orderByField = column;
this.orderByReverse = false;
}
if (this.orderByField) {
this.rows = sortBy(this.rows, this.orderByField);
if (this.orderByReverse) {
this.rows = this.rows.reverse();
}
}
}
}

328
client/app/lib/visualizations/d3box.js vendored Normal file
View File

@@ -0,0 +1,328 @@
/* eslint-disable */
// Inspired by http://informationandvisualization.de/blog/box-plot
function box() {
let width = 1,
height = 1,
duration = 0,
domain = null,
value = Number,
whiskers = boxWhiskers,
quartiles = boxQuartiles,
tickFormat = null;
// For each small multiple…
function box(g) {
g.each(function(d, i) {
d = d.map(value).sort(d3.ascending);
let g = d3.select(this),
n = d.length,
min = d[0],
max = d[n - 1];
// Compute quartiles. Must return exactly 3 elements.
const quartileData = (d.quartiles = quartiles(d));
// Compute whiskers. Must return exactly 2 elements, or null.
let whiskerIndices = whiskers && whiskers.call(this, d, i),
whiskerData = whiskerIndices && whiskerIndices.map(i => d[i]);
// Compute outliers. If no whiskers are specified, all data are "outliers".
// We compute the outliers as indices, so that we can join across transitions!
const outlierIndices = whiskerIndices
? d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n))
: d3.range(n);
// Compute the new x-scale.
const x1 = d3.scale
.linear()
.domain((domain && domain.call(this, d, i)) || [min, max])
.range([height, 0]);
// Retrieve the old x-scale, if this is an update.
const x0 =
this.__chart__ ||
d3.scale
.linear()
.domain([0, Infinity])
.range(x1.range());
// Stash the new scale.
this.__chart__ = x1;
// Note: the box, median, and box tick elements are fixed in number,
// so we only have to handle enter and update. In contrast, the outliers
// and other elements are variable, so we need to exit them! Variable
// elements also fade in and out.
// Update center line: the vertical line spanning the whiskers.
const center = g.selectAll('line.center').data(whiskerData ? [whiskerData] : []);
center
.enter()
.insert('line', 'rect')
.attr('class', 'center')
.attr('x1', width / 2)
.attr('y1', d => x0(d[0]))
.attr('x2', width / 2)
.attr('y2', d => x0(d[1]))
.style('opacity', 1e-6)
.transition()
.duration(duration)
.style('opacity', 1)
.attr('y1', d => x1(d[0]))
.attr('y2', d => x1(d[1]));
center
.transition()
.duration(duration)
.style('opacity', 1)
.attr('y1', d => x1(d[0]))
.attr('y2', d => x1(d[1]));
center
.exit()
.transition()
.duration(duration)
.style('opacity', 1e-6)
.attr('y1', d => x1(d[0]))
.attr('y2', d => x1(d[1]))
.remove();
// Update innerquartile box.
const box = g.selectAll('rect.box').data([quartileData]);
box
.enter()
.append('rect')
.attr('class', 'box')
.attr('x', 0)
.attr('y', d => x0(d[2]))
.attr('width', width)
.attr('height', d => x0(d[0]) - x0(d[2]))
.transition()
.duration(duration)
.attr('y', d => x1(d[2]))
.attr('height', d => x1(d[0]) - x1(d[2]));
box
.transition()
.duration(duration)
.attr('y', d => x1(d[2]))
.attr('height', d => x1(d[0]) - x1(d[2]));
box.exit().remove();
// Update median line.
const medianLine = g.selectAll('line.median').data([quartileData[1]]);
medianLine
.enter()
.append('line')
.attr('class', 'median')
.attr('x1', 0)
.attr('y1', x0)
.attr('x2', width)
.attr('y2', x0)
.transition()
.duration(duration)
.attr('y1', x1)
.attr('y2', x1);
medianLine
.transition()
.duration(duration)
.attr('y1', x1)
.attr('y2', x1);
medianLine.exit().remove();
// Update whiskers.
const whisker = g.selectAll('line.whisker').data(whiskerData || []);
whisker
.enter()
.insert('line', 'circle, text')
.attr('class', 'whisker')
.attr('x1', 0)
.attr('y1', x0)
.attr('x2', width)
.attr('y2', x0)
.style('opacity', 1e-6)
.transition()
.duration(duration)
.attr('y1', x1)
.attr('y2', x1)
.style('opacity', 1);
whisker
.transition()
.duration(duration)
.attr('y1', x1)
.attr('y2', x1)
.style('opacity', 1);
whisker
.exit()
.transition()
.duration(duration)
.attr('y1', x1)
.attr('y2', x1)
.style('opacity', 1e-6)
.remove();
// Update outliers.
const outlier = g.selectAll('circle.outlier').data(outlierIndices, Number);
outlier
.enter()
.insert('circle', 'text')
.attr('class', 'outlier')
.attr('r', 5)
.attr('cx', width / 2)
.attr('cy', i => x0(d[i]))
.style('opacity', 1e-6)
.transition()
.duration(duration)
.attr('cy', i => x1(d[i]))
.style('opacity', 1);
outlier
.transition()
.duration(duration)
.attr('cy', i => x1(d[i]))
.style('opacity', 1);
outlier
.exit()
.transition()
.duration(duration)
.attr('cy', i => x1(d[i]))
.style('opacity', 1e-6)
.remove();
// Compute the tick format.
const format = tickFormat || x1.tickFormat(8);
// Update box ticks.
const boxTick = g.selectAll('text.box').data(quartileData);
boxTick
.enter()
.append('text')
.attr('class', 'box')
.attr('dy', '.3em')
.attr('dx', (d, i) => (i & 1 ? 6 : -6))
.attr('x', (d, i) => (i & 1 ? width : 0))
.attr('y', x0)
.attr('text-anchor', (d, i) => (i & 1 ? 'start' : 'end'))
.text(format)
.transition()
.duration(duration)
.attr('y', x1);
boxTick
.transition()
.duration(duration)
.text(format)
.attr('y', x1);
boxTick.exit().remove();
// Update whisker ticks. These are handled separately from the box
// ticks because they may or may not exist, and we want don't want
// to join box ticks pre-transition with whisker ticks post-.
const whiskerTick = g.selectAll('text.whisker').data(whiskerData || []);
whiskerTick
.enter()
.append('text')
.attr('class', 'whisker')
.attr('dy', '.3em')
.attr('dx', 6)
.attr('x', width)
.attr('y', x0)
.text(format)
.style('opacity', 1e-6)
.transition()
.duration(duration)
.attr('y', x1)
.style('opacity', 1);
whiskerTick
.transition()
.duration(duration)
.text(format)
.attr('y', x1)
.style('opacity', 1);
whiskerTick
.exit()
.transition()
.duration(duration)
.attr('y', x1)
.style('opacity', 1e-6)
.remove();
});
d3.timer.flush();
}
box.width = function(x) {
if (!arguments.length) return width;
width = x;
return box;
};
box.height = function(x) {
if (!arguments.length) return height;
height = x;
return box;
};
box.tickFormat = function(x) {
if (!arguments.length) return tickFormat;
tickFormat = x;
return box;
};
box.duration = function(x) {
if (!arguments.length) return duration;
duration = x;
return box;
};
box.domain = function(x) {
if (!arguments.length) return domain;
domain = x == null ? x : d3.functor(x);
return box;
};
box.value = function(x) {
if (!arguments.length) return value;
value = x;
return box;
};
box.whiskers = function(x) {
if (!arguments.length) return whiskers;
whiskers = x;
return box;
};
box.quartiles = function(x) {
if (!arguments.length) return quartiles;
quartiles = x;
return box;
};
return box;
}
function boxWhiskers(d) {
return [0, d.length - 1];
}
function boxQuartiles(d) {
return [d3.quantile(d, 0.25), d3.quantile(d, 0.5), d3.quantile(d, 0.75)];
}
export default box;

View File

@@ -10,7 +10,7 @@ function value(link) {
return link.value;
}
export default function() {
function Sankey() {
const sankey = {};
let nodeWidth = 24;
let nodePadding = 8;
@@ -21,11 +21,11 @@ export default function() {
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks() {
nodes.forEach((node) => {
nodes.forEach(node => {
node.sourceLinks = [];
node.targetLinks = [];
});
links.forEach((link) => {
links.forEach(link => {
let source = link.source;
let target = link.target;
if (typeof source === 'number') source = link.source = nodes[link.source];
@@ -37,16 +37,13 @@ export default function() {
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues() {
nodes.forEach((node) => {
node.value = Math.max(
d3.sum(node.sourceLinks, value),
d3.sum(node.targetLinks, value)
);
nodes.forEach(node => {
node.value = Math.max(d3.sum(node.sourceLinks, value), d3.sum(node.targetLinks, value));
});
}
function moveSinksRight(x) {
nodes.forEach((node) => {
nodes.forEach(node => {
if (!node.sourceLinks.length) {
node.x = x - 1;
}
@@ -54,7 +51,7 @@ export default function() {
}
function scaleNodeBreadths(kx) {
nodes.forEach((node) => {
nodes.forEach(node => {
node.x *= kx;
});
}
@@ -71,7 +68,7 @@ export default function() {
function assignBreadth(node) {
node.x = x;
node.dx = nodeWidth;
node.sourceLinks.forEach((link) => {
node.sourceLinks.forEach(link => {
if (nextNodes.indexOf(link.target) < 0) {
nextNodes.push(link.target);
}
@@ -91,7 +88,7 @@ export default function() {
}
function moveSourcesRight() {
nodes.forEach((node) => {
nodes.forEach(node => {
if (!node.targetLinks.length) {
node.x = d3.min(node.sourceLinks, d => d.target.x) - 1;
}
@@ -99,25 +96,27 @@ export default function() {
}
function computeNodeDepths(iterations) {
const nodesByBreadth = d3.nest()
const nodesByBreadth = d3
.nest()
.key(d => d.x)
.sortKeys(d3.ascending)
.entries(nodes)
.map(d => d.values);
function initializeNodeDepth() {
const ky = d3.min(nodesByBreadth, n =>
(size[1] - (n.length - 1) * nodePadding) / d3.sum(n, value)
const ky = d3.min(
nodesByBreadth,
n => (size[1] - (n.length - 1) * nodePadding) / d3.sum(n, value),
);
nodesByBreadth.forEach((n) => {
nodesByBreadth.forEach(n => {
n.forEach((node, i) => {
node.y = i;
node.dy = node.value * ky;
});
});
links.forEach((link) => {
links.forEach(link => {
link.dy = link.value * ky;
});
}
@@ -127,8 +126,8 @@ export default function() {
return center(link.source) * link.value;
}
nodesByBreadth.forEach((n) => {
n.forEach((node) => {
nodesByBreadth.forEach(n => {
n.forEach(node => {
if (node.targetLinks.length) {
const y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
node.y += (y - center(node)) * alpha;
@@ -138,7 +137,7 @@ export default function() {
}
function resolveCollisions() {
nodesByBreadth.forEach((nodes) => {
nodesByBreadth.forEach(nodes => {
const n = nodes.length;
let node;
let dy;
@@ -171,7 +170,7 @@ export default function() {
}
function resolveCollisions() {
nodesByBreadth.forEach((nodes) => {
nodesByBreadth.forEach(nodes => {
let node,
dy,
y0 = 0,
@@ -203,20 +202,22 @@ export default function() {
});
}
initializeNodeDepth();
resolveCollisions();
for (let alpha = 1; iterations > 0; iterations -= 1) {
relaxRightToLeft(alpha *= 0.99);
relaxRightToLeft((alpha *= 0.99));
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function relaxRightToLeft(alpha) {
nodesByBreadth.slice().reverse().forEach((nodes) => {
nodes.forEach((node) => {
nodesByBreadth
.slice()
.reverse()
.forEach(nodes => {
nodes.forEach(node => {
if (node.sourceLinks.length) {
const y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
node.y += (y - center(node)) * alpha;
@@ -235,18 +236,18 @@ export default function() {
}
function computeLinkDepths() {
nodes.forEach((node) => {
nodes.forEach(node => {
node.sourceLinks.sort(ascendingTargetDepth);
node.targetLinks.sort(ascendingSourceDepth);
});
nodes.forEach((node) => {
nodes.forEach(node => {
let sy = 0,
ty = 0;
node.sourceLinks.forEach((link) => {
node.sourceLinks.forEach(link => {
link.sy = sy;
sy += link.dy;
});
node.targetLinks.forEach((link) => {
node.targetLinks.forEach(link => {
link.ty = ty;
ty += link.dy;
});
@@ -317,13 +318,10 @@ export default function() {
const y0 = d.source.y + d.sy + d.dy / 2;
const y1 = d.target.y + d.ty + d.dy / 2;
return `M${x0},${y0
}C${x2},${y0
} ${x3},${y1
} ${x1},${y1}`;
return `M${x0},${y0}C${x2},${y0} ${x3},${y1} ${x1},${y1}`;
}
link.curvature = (_) => {
link.curvature = _ => {
if (!arguments.length) return curvature;
curvature = +_;
return link;
@@ -332,6 +330,7 @@ export default function() {
return link;
};
return sankey;
};
}
export default Sankey;

View File

@@ -1,4 +1,4 @@
import d3 from 'd3';
import * as d3 from 'd3';
import _ from 'underscore';
import angular from 'angular';
@@ -10,7 +10,6 @@ function colorMap(d) {
return colors(d.name);
}
// Return array of ancestors of nodes, highest first, but excluding the root.
function getAncestors(node) {
const path = [];
@@ -24,7 +23,7 @@ function getAncestors(node) {
}
// The following is based on @chrisrzhou's example from: http://bl.ocks.org/chrisrzhou/d5bdd8546f64ca0e4366.
export default function Sunburst(scope, element) {
function Sunburst(scope, element) {
this.element = element;
this.watches = [];
@@ -60,27 +59,18 @@ export default function Sunburst(scope, element) {
let totalSize = 0;
// create d3.layout.partition
const partition = d3.layout.partition()
const partition = d3.layout
.partition()
.size([2 * Math.PI, radius * radius])
.value(d =>
d.size
);
.value(d => d.size);
// create arcs for drawing D3 paths
const arc = d3.svg.arc()
.startAngle(d =>
d.x
)
.endAngle(d =>
d.x + d.dx
)
.innerRadius(d =>
Math.sqrt(d.y)
)
.outerRadius(d =>
Math.sqrt(d.y + d.dy)
);
const arc = d3.svg
.arc()
.startAngle(d => d.x)
.endAngle(d => d.x + d.dx)
.innerRadius(d => Math.sqrt(d.y))
.outerRadius(d => Math.sqrt(d.y + d.dy));
/**
* Define and initialize D3 select references and div-containers
@@ -88,15 +78,18 @@ export default function Sunburst(scope, element) {
* e.g. vis, breadcrumbs, lastCrumb, summary, sunburst, legend
*/
// create main vis selection
const vis = d3.select(element[0])
.append('div').classed('vis-container', true)
const vis = d3
.select(element[0])
.append('div')
.classed('vis-container', true)
.style('position', 'relative')
.style('margin-top', '5px')
.style('height', `${height + 2 * b.h}px`);
// create and position breadcrumbs container and svg
const breadcrumbs = vis
.append('div').classed('breadcrumbs-container', true)
.append('div')
.classed('breadcrumbs-container', true)
.append('svg')
.attr('width', width)
.attr('height', b.h)
@@ -107,7 +100,8 @@ export default function Sunburst(scope, element) {
// create and position SVG
const sunburst = vis
.append('div').classed('sunburst-container', true)
.append('div')
.classed('sunburst-container', true)
.style('z-index', '2')
// .style("margin-left", marginLeft + "px")
.style('left', `${marginLeft}px`)
@@ -123,9 +117,10 @@ export default function Sunburst(scope, element) {
// create and position summary container
const summary = vis
.append('div').classed('summary-container', true)
.append('div')
.classed('summary-container', true)
.style('position', 'absolute')
.style('top', `${b.h + radius * 0.80}px`)
.style('top', `${b.h + radius * 0.8}px`)
.style('left', `${marginLeft + radius / 2}px`)
.style('width', `${radius}px`)
.style('height', `${radius}px`)
@@ -143,7 +138,8 @@ export default function Sunburst(scope, element) {
points.push(`${b.w},${b.h}`);
points.push(`0,${b.h}`);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
if (i > 0) {
// Leftmost breadcrumb; don't include 6th vertex.
points.push(`${b.t},${b.h / 2}`);
}
return points.join(' ');
@@ -152,34 +148,29 @@ export default function Sunburst(scope, element) {
// Update the breadcrumb breadcrumbs to show the current sequence and percentage.
function updateBreadcrumbs(ancestors, percentageString) {
// Data join, where primary key = name + depth.
const g = breadcrumbs.selectAll('g')
.data(ancestors, d =>
d.name + d.depth
);
const g = breadcrumbs.selectAll('g').data(ancestors, d => d.name + d.depth);
// Add breadcrumb and label for entering nodes.
const breadcrumb = g.enter().append('g');
breadcrumb
.append('polygon').classed('breadcrumbs-shape', true)
.append('polygon')
.classed('breadcrumbs-shape', true)
.attr('points', breadcrumbPoints)
.attr('fill', colorMap);
breadcrumb
.append('text').classed('breadcrumbs-text', true)
.append('text')
.classed('breadcrumbs-text', true)
.attr('x', (b.w + b.t) / 2)
.attr('y', b.h / 2)
.attr('dy', '0.35em')
.attr('font-size', '10px')
.attr('text-anchor', 'middle')
.text(d =>
d.name
);
.text(d => d.name);
// Set position for entering and updating nodes.
g.attr('transform', (d, i) =>
`translate(${i * (b.w + b.s)}, 0)`
);
g.attr('transform', (d, i) => `translate(${i * (b.w + b.s)}, 0)`);
// Remove exiting nodes.
g.exit().remove();
@@ -210,32 +201,27 @@ export default function Sunburst(scope, element) {
updateBreadcrumbs(ancestors, percentageString);
// update sunburst (Fade all the segments and highlight only ancestors of current segment)
sunburst.selectAll('path')
.attr('opacity', 0.3);
sunburst.selectAll('path')
.filter(node =>
(ancestors.indexOf(node) >= 0)
)
sunburst.selectAll('path').attr('opacity', 0.3);
sunburst
.selectAll('path')
.filter(node => ancestors.indexOf(node) >= 0)
.attr('opacity', 1);
// update summary
summary.html(
`Stage: ${d.depth}<br />` +
`<span class='percentage' style='font-size: 2em;'>${percentageString}</span><br />${
d.value} of ${totalSize}<br />`
);
summary.html(`Stage: ${d.depth}<br />` +
`<span class='percentage' style='font-size: 2em;'>${percentageString}</span><br />${d.value} of ${totalSize}<br />`);
// display summary and breadcrumbs if hidden
summary.style('visibility', '');
breadcrumbs.style('visibility', '');
}
// helper function click to handle mouseleave events/animations
function click() {
// Deactivate all segments then retransition each segment to full opacity.
sunburst.selectAll('path').on('mouseover', null);
sunburst.selectAll('path')
sunburst
.selectAll('path')
.transition()
.duration(1000)
.attr('opacity', 1)
@@ -251,10 +237,8 @@ export default function Sunburst(scope, element) {
// helper function to draw the sunburst and breadcrumbs
function drawSunburst(json) {
// Build only nodes of a threshold "visible" sizes to improve efficiency
const nodes = partition.nodes(json)
.filter(d =>
(d.dx > 0.005) && d.name !== exitNode // 0.005 radians = 0.29 degrees
);
// 0.005 radians = 0.29 degrees
const nodes = partition.nodes(json).filter(d => d.dx > 0.005 && d.name !== exitNode);
// this section is required to update the colors.domain() every time the data updates
const uniqueNames = (function uniqueNames(a) {
@@ -267,8 +251,11 @@ export default function Sunburst(scope, element) {
colors.domain(uniqueNames); // update domain colors
// create path based on nodes
const path = sunburst.data([json]).selectAll('path')
.data(nodes).enter()
const path = sunburst
.data([json])
.selectAll('path')
.data(nodes)
.enter()
.append('path')
.classed('nodePath', true)
.attr('display', d => (d.depth ? null : 'none'))
@@ -278,7 +265,6 @@ export default function Sunburst(scope, element) {
.attr('stroke', 'white')
.on('mouseover', mouseover);
// // trigger mouse click over sunburst to reset visualization summary
vis.on('click', click);
@@ -299,7 +285,12 @@ export default function Sunburst(scope, element) {
function buildNodes(raw) {
let values;
if (_.has(raw[0], 'sequence') && _.has(raw[0], 'stage') && _.has(raw[0], 'node') && _.has(raw[0], 'value')) {
if (
_.has(raw[0], 'sequence') &&
_.has(raw[0], 'stage') &&
_.has(raw[0], 'node') &&
_.has(raw[0], 'value')
) {
const grouped = _.groupBy(raw, 'sequence');
values = _.map(grouped, (value) => {
@@ -314,13 +305,11 @@ export default function Sunburst(scope, element) {
const validKey = key => key !== 'value' && key.indexOf('$$') !== 0;
const keys = _.sortBy(_.filter(_.keys(raw[0]), validKey), _.identity);
values = _.map(raw, (row, sequence) =>
({
values = _.map(raw, (row, sequence) => ({
size: row.value,
sequence,
nodes: _.compact(_.map(keys, key => row[key])),
})
);
}));
}
return values;
@@ -346,7 +335,6 @@ export default function Sunburst(scope, element) {
const nodeName = nodes[j];
const isLeaf = j + 1 === nodes.length;
if (!children) {
currentNode.children = children = [];
children.push({
@@ -403,6 +391,10 @@ export default function Sunburst(scope, element) {
}
Sunburst.prototype.remove = function remove() {
this.watches.forEach((unregister) => { unregister(); });
this.watches.forEach((unregister) => {
unregister();
});
angular.element(this.element[0]).empty('.vis-container');
};
export default Sunburst;

19
client/app/multi_org.html Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html ng-app="app" ng-strict-di>
<head lang="en">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
<base href="{{base_href}}">
<title>Redash</title>
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
</head>
<body>
<section>
<div ng-view></div>
</section>
</body>
</html>

View File

@@ -1,10 +0,0 @@
import registerStatusPage from './status';
import registerOutdatedQueriesPage from './outdated-queries';
import registerTasksPage from './tasks';
export default function (ngModule) {
const routes = Object.assign({}, registerStatusPage(ngModule),
registerOutdatedQueriesPage(ngModule),
registerTasksPage(ngModule));
return routes;
}

View File

@@ -1,6 +1,6 @@
import moment from 'moment';
import { Paginator } from '../../../utils';
import { Paginator } from '@/lib/pagination';
import template from './outdated-queries.html';
function OutdatedQueriesCtrl($scope, Events, $http, $timeout) {
@@ -30,7 +30,7 @@ function OutdatedQueriesCtrl($scope, Events, $http, $timeout) {
refresh();
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('outdatedQueriesPage', {
template,
controller: OutdatedQueriesCtrl,

View File

@@ -25,7 +25,7 @@ function AdminStatusCtrl($scope, $http, $timeout, currentUser, Events) {
refresh();
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('statusPage', {
template,
controller: AdminStatusCtrl,

View File

@@ -1,8 +1,7 @@
import moment from 'moment';
import { Paginator } from '../../../utils';
import { Paginator } from '@/lib/pagination';
import template from './tasks.html';
import registerCancelQueryButton from './cancel-query-button';
function TasksCtrl($scope, $location, $http, $timeout, Events) {
Events.record('view', 'page', 'admin/tasks');
@@ -46,14 +45,12 @@ function TasksCtrl($scope, $location, $http, $timeout, Events) {
refresh();
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('tasksPage', {
template,
controller: TasksCtrl,
});
registerCancelQueryButton(ngModule);
return {
'/admin/queries/tasks': {
template: '<tasks-page></tasks-page>',

View File

@@ -35,14 +35,14 @@
<td>{{row.data_source_id}}</td>
<td>{{row.username}}</td>
<td>{{row.state}} <span ng-if="row.state === 'failed'" uib-popover="{{row.error}}" popover-trigger="mouseenter" class="zmdi zmdi-help"></span></td>
<td>{{row.query_id}}</td>
<td><a href="queries/{{row.query_id}}">{{row.query_id}}</a></td>
<td>{{row.query_hash}}</td>
<td>{{row.run_time | durationHumanize}}</td>
<td>{{row.created_at | toMilliseconds | dateTime }}</td>
<td>{{row.started_at | toMilliseconds | dateTime }}</td>
<td>{{row.updated_at | toMilliseconds | dateTime }}</td>
<td ng-if="selectedTab === 'in_progress'">
<cancel-query-button query-id="dataRow.query_id" task-id="dataRow.task_id"></cancel-query-button>
<cancel-query-button query-id="row.query_id" task-id="row.task_id"></cancel-query-button>
</td>
</tr>
</tbody>

View File

@@ -1,102 +0,0 @@
import { contains, without, compact } from 'underscore';
import template from './alert-subscriptions.html';
function controller($scope, $q, $sce, currentUser, AlertSubscription, Destination, toastr) {
'ngInject';
$scope.newSubscription = {};
$scope.subscribers = [];
$scope.destinations = [];
$scope.currentUser = currentUser;
$q.all([Destination.query().$promise,
AlertSubscription.query({ alertId: $scope.alertId }).$promise]).then((responses) => {
const destinations = responses[0];
const subscribers = responses[1];
const subscribedDestinations =
compact(subscribers.map(s => s.destination && s.destination.id));
const subscribedUsers =
compact(subscribers.map(s => !s.destination && s.user.id));
$scope.destinations = destinations.filter(d => !contains(subscribedDestinations, d.id));
if (!contains(subscribedUsers, currentUser.id)) {
$scope.destinations.unshift({ user: { name: currentUser.name } });
}
$scope.newSubscription.destination = $scope.destinations[0];
$scope.subscribers = subscribers;
});
$scope.destinationsDisplay = (d) => {
if (!d) {
return '';
}
let destination = d;
if (d.destination) {
destination = destination.destination;
} else if (destination.user) {
destination = {
name: `${d.user.name} (Email)`,
icon: 'fa-envelope',
type: 'user',
};
}
return $sce.trustAsHtml(`<i class="fa ${destination.icon}"></i>&nbsp;${destination.name}`);
};
$scope.saveSubscriber = () => {
const sub = new AlertSubscription({ alert_id: $scope.alertId });
if ($scope.newSubscription.destination.id) {
sub.destination_id = $scope.newSubscription.destination.id;
}
sub.$save(() => {
toastr.success('Subscribed.');
$scope.subscribers.push(sub);
$scope.destinations = without($scope.destinations, $scope.newSubscription.destination);
if ($scope.destinations.length > 0) {
$scope.newSubscription.destination = $scope.destinations[0];
} else {
$scope.newSubscription.destination = undefined;
}
}, () => {
toastr.error('Failed saving subscription.');
});
};
$scope.unsubscribe = (subscriber) => {
const destination = subscriber.destination;
const user = subscriber.user;
subscriber.$delete(() => {
toastr.success('Unsubscribed');
$scope.subscribers = without($scope.subscribers, subscriber);
if (destination) {
$scope.destinations.push(destination);
} else if (user.id === currentUser.id) {
$scope.destinations.push({ user: { name: currentUser.name } });
}
if ($scope.destinations.length === 1) {
$scope.newSubscription.destination = $scope.destinations[0];
}
}, () => {
toastr.error('Failed unsubscribing.');
});
};
}
export default () => ({
restrict: 'E',
replace: true,
scope: {
alertId: '=',
},
template,
controller,
});

View File

@@ -1,6 +1,5 @@
import { template as templateBuilder } from 'underscore';
import template from './alert.html';
import alertSubscriptions from './alert-subscriptions';
function AlertCtrl($routeParams, $location, $sce, toastr, currentUser, Query, Events, Alert) {
this.alertId = $routeParams.alertId;
@@ -60,34 +59,38 @@ function AlertCtrl($routeParams, $location, $sce, toastr, currentUser, Query, Ev
if (this.alert.rearm === '' || this.alert.rearm === 0) {
this.alert.rearm = null;
}
this.alert.$save((alert) => {
this.alert.$save(
(alert) => {
toastr.success('Saved.');
if (this.alertId === 'new') {
$location.path(`/alerts/${alert.id}`).replace();
}
}, () => {
},
() => {
toastr.error('Failed saving alert.');
});
},
);
};
this.delete = () => {
this.alert.$delete(() => {
this.alert.$delete(
() => {
$location.path('/alerts');
toastr.success('Alert deleted.');
}, () => {
},
() => {
toastr.error('Failed deleting alert.');
});
},
);
};
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('alertPage', {
template,
controller: AlertCtrl,
});
ngModule.directive('alertSubscriptions', alertSubscriptions);
return {
'/alerts/:alertId': {
template: '<alert-page></alert-page>',

View File

@@ -4,20 +4,20 @@
</page-header>
<div class="container">
<div class="container bg-white">
<div class="bg-white">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>Created By</th>
<th>State</th>
<th>Created At</th>
<th class="sortable-column" ng-click="$ctrl.alerts.orderBy('name')">Name <sort-icon column="'name'" sort-column="$ctrl.alerts.orderByField" reverse="$ctrl.alerts.orderByReverse"></sort-icon></th>
<th class="sortable-column" ng-click="$ctrl.alerts.orderBy('created_by')">Created By <sort-icon column="'created_by'" sort-column="$ctrl.alerts.orderByField" reverse="$ctrl.alerts.orderByReverse"></sort-icon></th>
<th class="sortable-column" ng-click="$ctrl.alerts.orderBy('state')">State <sort-icon column="'state'" sort-column="$ctrl.alerts.orderByField" reverse="$ctrl.alerts.orderByReverse"></sort-icon></th>
<th class="sortable-column" ng-click="$ctrl.alerts.orderBy('created_at')">Created By <sort-icon column="'created_at'" sort-column="$ctrl.alerts.orderByField" reverse="$ctrl.alerts.orderByReverse"></sort-icon></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in $ctrl.alerts.getPageRows()">
<td><a href="alerts/{{row.id}}">{{row.name}}</a></td>
<td>{{row.user.name}}</td>
<td>{{row.created_by}}</td>
<td><span ng-class="row.class">{{row.state | uppercase}}</span> since <span am-time-ago="row.updated_at"></span></td>
<td><span am-time-ago="row.created_at"></span></td>
</tr>

View File

@@ -1,29 +1,32 @@
import { Paginator } from '../../utils';
import { Paginator } from '@/lib/pagination';
import template from './alerts-list.html';
class AlertsListCtrl {
constructor(Events, Alert) {
Events.record('view', 'page', 'alerts');
this.alerts = new Paginator([], { itemsPerPage: 20 });
Alert.query((alerts) => {
const stateClass = {
ok: 'label label-success',
triggered: 'label label-danger',
unknown: 'label label-warning',
};
alerts.forEach((alert) => {
alert.class = stateClass[alert.state];
});
class AlertsListCtrl {
constructor(Events, Alert) {
Events.record('view', 'page', 'alerts');
this.alerts.updateRows(alerts);
this.alerts = new Paginator([], { itemsPerPage: 20 });
Alert.query((alerts) => {
this.alerts.updateRows(alerts.map(alert => ({
id: alert.id,
name: alert.name,
state: alert.state,
class: stateClass[alert.state],
created_by: alert.user.name,
created_at: alert.created_at,
updated_at: alert.updated_at,
})));
});
}
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('alertsListPage', {
template,
controller: AlertsListCtrl,

View File

@@ -2,7 +2,7 @@
<page-header title="Dashboards"></page-header>
<div class="col-lg-3">
<input type='text' class='form-control' placeholder="Search Dashboards..."
ng-change="$ctrl.update()" ng-model="$ctrl.searchText"/>
ng-change="$ctrl.update()" ng-model="$ctrl.searchText" autofocus/>
<div class='list-group m-t-20 tags-list'>
<a ng-repeat='tag in $ctrl.allTags' ng-class='{"active": $ctrl.tagIsSelected(tag)}'
class='list-group-item' ng-click='$ctrl.toggleTag($event, tag)'>

View File

@@ -1,12 +1,12 @@
import _ from 'underscore';
import { Paginator } from '../../utils';
import { Paginator } from '@/lib/pagination';
import template from './dashboard-list.html';
import './dashboard-list.css';
function DashboardListCtrl(Dashboard, $location, clientConfig) {
const TAGS_REGEX = /(^[\w\s]+):|(#[\w-]+)/ig;
const TAGS_REGEX = /(^([\w\s]|[^\u0000-\u007F])+):|(#([\w-]|[^\u0000-\u007F])+)/ig;
this.logoUrl = clientConfig.logoUrl;
const page = parseInt($location.search().page || 1, 10);
@@ -46,6 +46,7 @@ function DashboardListCtrl(Dashboard, $location, clientConfig) {
this.update = () => {
this.dashboards.$promise.then((data) => {
data = _.sortBy(data, 'name');
const filteredDashboards = data.map((dashboard) => {
dashboard.tags = (dashboard.name.match(TAGS_REGEX) || []).map(tag => tag.replace(/:$/, ''));
dashboard.untagged_name = dashboard.name.replace(TAGS_REGEX, '').trim();
@@ -74,7 +75,7 @@ function DashboardListCtrl(Dashboard, $location, clientConfig) {
this.update();
}
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('pageDashboardList', {
template,
controller: DashboardListCtrl,

View File

@@ -2,8 +2,10 @@ import * as _ from 'underscore';
import template from './dashboard.html';
import shareDashboardTemplate from './share-dashboard.html';
function DashboardCtrl($rootScope, $routeParams, $location, $timeout, $q, $uibModal,
Title, AlertDialog, Dashboard, currentUser, clientConfig, Events) {
function DashboardCtrl(
$rootScope, $routeParams, $location, $timeout, $q, $uibModal,
Title, AlertDialog, Dashboard, currentUser, clientConfig, Events,
) {
this.isFullscreen = false;
this.refreshRate = null;
this.showPermissionsControl = clientConfig.showPermissionsControl;
@@ -17,6 +19,8 @@ function DashboardCtrl($rootScope, $routeParams, $location, $timeout, $q, $uibMo
{ name: '10 minutes', rate: 60 * 10 },
{ name: '30 minutes', rate: 60 * 30 },
{ name: '1 hour', rate: 60 * 60 },
{ name: '12 hour', rate: 12 * 60 * 60 },
{ name: '24 hour', rate: 24 * 60 * 60 },
];
this.setRefreshRate = (rate) => {
@@ -40,8 +44,7 @@ function DashboardCtrl($rootScope, $routeParams, $location, $timeout, $q, $uibMo
globalParams[param.name].locals.push(param);
});
}
})
);
}));
this.globalParameters = _.values(globalParams);
};
@@ -66,8 +69,7 @@ function DashboardCtrl($rootScope, $routeParams, $location, $timeout, $q, $uibMo
promises.push(queryResult.toPromise());
}
}
})
);
}));
this.extractGlobalParameters();
@@ -126,8 +128,7 @@ function DashboardCtrl($rootScope, $routeParams, $location, $timeout, $q, $uibMo
this.autoRefresh = () => {
$timeout(() => {
this.loadDashboard(true);
}, this.refreshRate.rate * 1000
).then(() => this.autoRefresh());
}, this.refreshRate.rate * 1000).then(() => this.autoRefresh());
};
this.archiveDashboard = () => {
@@ -155,12 +156,20 @@ function DashboardCtrl($rootScope, $routeParams, $location, $timeout, $q, $uibMo
};
this.editDashboard = () => {
const previousFiltersState = this.dashboard.dashboard_filters_enabled;
$uibModal.open({
component: 'editDashboardDialog',
resolve: {
dashboard: () => this.dashboard,
},
}).result.then((dashboard) => { this.dashboard = dashboard; });
}).result.then((dashboard) => {
const shouldRenderDashboard = !previousFiltersState && dashboard.dashboard_filters_enabled;
this.dashboard = dashboard;
if (shouldRenderDashboard) {
renderDashboard(this.dashboard);
}
});
};
this.addWidget = () => {
@@ -250,7 +259,7 @@ const ShareDashboardComponent = {
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('shareDashboard', ShareDashboardComponent);
ngModule.component('dashboardPage', {
template,

View File

@@ -1,14 +0,0 @@
import dashboardPage from './dashboard';
import dashboardList from './dashboard-list';
import widgetComponent from './widget';
import addWidgetDialog from './add-widget-dialog';
import registerEditDashboardDialog from './edit-dashboard-dialog';
import publicDashboardPage from './public-dashboard-page';
export default function (ngModule) {
addWidgetDialog(ngModule);
widgetComponent(ngModule);
publicDashboardPage(ngModule);
registerEditDashboardDialog(ngModule);
return Object.assign({}, dashboardPage(ngModule), dashboardList(ngModule));
}

View File

@@ -1,5 +1,5 @@
import logoUrl from '@/assets/images/redash_icon_small.png';
import template from './public-dashboard-page.html';
import logoUrl from '../../assets/images/redash_icon_small.png';
const PublicDashboardPage = {
template,
@@ -17,23 +17,18 @@ const PublicDashboardPage = {
}
this.public = true;
this.dashboard.widgets = this.dashboard.widgets.map(row =>
row.map(widget =>
new Widget(widget)
)
);
row.map(widget => new Widget(widget)));
},
};
export default function (ngModule) {
export default function init(ngModule) {
ngModule.component('publicDashboardPage', PublicDashboardPage);
function loadPublicDashboard($http, $route) {
'ngInject';
const token = $route.current.params.token;
return $http.get(`/api/dashboards/public/${token}`).then(response =>
response.data
);
return $http.get(`api/dashboards/public/${token}`).then(response => response.data);
}
function session($http, $route, Auth) {
@@ -52,4 +47,6 @@ export default function (ngModule) {
},
});
});
return [];
}

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