Compare commits

...

209 Commits

Author SHA1 Message Date
Arik Fraimovich
4780bd9c5e Merge pull request #3196 from getredash/master
Sync release/6.0.x branch with master
2018-12-16 20:27:43 +02:00
Arik Fraimovich
ef66da7d94 V6 release (#3195)
* V6 release

* Remove "-beta"

* Update package.json

* Update package-lock.json

* Update CHANGELOG.md
2018-12-16 16:01:20 +02:00
Arik Fraimovich
57c8fbe14e README updates
1. Remove link to Slack channel.
2. Update number of data sources.
2018-12-16 09:55:34 +02:00
Arik Fraimovich
938a20e7c0 Use multiple issue templates instead of a single one (#3194)
* Switch to issue templates

* Apply a label to non bugs
2018-12-16 09:42:32 +02:00
Arik Fraimovich
f5dbdf245a Safely create_app in Celery code (try to fetch current_app first). (#3187)
Closes #3181.
2018-12-13 15:10:31 +02:00
Gabriel Dutra
8481dacff4 Fix eslint issues on user.js (#3186) 2018-12-12 23:32:12 +02:00
Omer Lachish
e23a07af03 Remove missing coverage from pytest terminal output (#3180)
* Remove missing coverage from pytest terminal output

* move coverage reporting to CI
2018-12-12 08:30:46 +02:00
Arik Fraimovich
52434a837f Make refresh_queries less noisey in logs (#3183) 2018-12-11 15:03:29 +02:00
Arik Fraimovich
230ad33f02 [Redshift] Fix: support for schema names with dots. (#3182) 2018-12-11 14:57:42 +02:00
Gabriel Dutra
cfe12c5a5d Add DB Seed to Cypress and setup Percy (#3155)
* Update Cypress element selectors

* Add seed data function to Cypress

* Change Cypress setup to be part of db-seed

* Add DatabaseSource selector to Create Data Source spec

* Add getElement command to Cypress

* Fix eslint issues

* Change Cypress getElement to getByTestId

* Add Percy and test it with the CI

* Change Percy dependency for CI to Cypress' Dockerfile

* Change Percy's execution to the docker container
- add --no-save to avoid errors on Dockerfile.cypress
 - pass PERCY_TOKEN from the CI to docker container

* Fix missed char on CircleCI config file

* Move Percy execution back to host on the CI

* Test adding PERCY_TOKEN to frontend-e2e-tests on CI config

* Undo add PERCY_TOKEN to config.yml

* Add Percy token and .git folder to Cypress

* Remove Percy install from config.yml

* Ignore .git folder again and use Percy env vars instead

* Update PERCY_PULL_REQUEST to be CIRCLE_PR_NUMBER

* Update cypress-server.js to handle other cypress commands
- cypress-server.js -> cypress.js
- new commands added to cypress.js
- CircleCI config updated accordingly
- added a Homepage screenshot

* Remove trailing spaces

* Add Create Query spec

* Disable Cypress videos

* Update run browser to Chrome

* Add missing --browser chrome
2018-12-10 22:29:36 +02:00
Omer Lachish
38ed046c9f Fix disable error message (#3175)
* display correct error message when attempting to disable yourself
* 403 (Forbidden) feels like a better status code than 400 (Bad Request)
* fix broken test
* remove redundant error title
2018-12-10 14:02:50 +02:00
Takuya Arita
1acf063755 FIX: Reject empty query name (#3171) 2018-12-10 12:35:25 +02:00
Omer Lachish
76321937d7 Remove API permissions for users who have been disabled (#3162) 2018-12-05 12:21:18 +02:00
Arik Fraimovich
c9ca2b99f6 Fix: Alert.evaluate failing when the column is missing. (#3167) 2018-12-05 11:28:05 +02:00
Arik Fraimovich
d42f0b2d40 Directly using record_event task requires timestamp (#3166)
Using the helper instead.
2018-12-05 10:11:08 +02:00
Vladislav Denisov
e530c23d4c clickhouse: fixed int() conversion error (#3161) 2018-12-05 09:29:47 +02:00
Omer Lachish
0973ee8abb Include correct version in production builds (#3163)
* take the first 8 characters for frontend version, not backend version

* run `npm run build` after version has been updated in CI

* `pack` should run last
2018-12-04 22:14:32 +02:00
Arik Fraimovich
3ee7537a6c WIP: Update CHANGELOG (#3159) 2018-12-03 22:07:20 +02:00
Arik Fraimovich
9c12b04578 json_dumps: add support for serializing buffer objects. (#3156) 2018-12-03 10:57:36 +02:00
Omer Lachish
9579f12a83 Protect against SQL injections by using tree comparisons (#3109)
* add SQLQuery class with tests for safe queries and non-safe tautology attacks

* add test for union query injections

* split .apply calls to newline

* add tests for comment attacks

* remove double underscore

* extract complex children check to variable

* inherit from object because I'm not a lamer

Co-Authored-By: rauchy <omer@rauchy.net>

* simplify cognitive complexity

* check that additional columns are not injected

* detect appended queries

* inline .apply calls

* move SQLQuery to it's own module

* move SQLQuery tests to their own module

* serialize SQLQuery instances

* raise an exception when attempting to serialize an unsafe query

* queries without parameters are safe

* remove redundant parentheses

* use cached properties

* rename SQLInjectionException to SQLInjectionError

* support multiple word params and param negations

* refactor out methods that don't involve any state

* don't cache text()

* reduce cognitive complexity
2018-12-02 21:51:06 +02:00
Zsolt Kocsmárszky
463d4ce518 Fix label positioning on no found screen (#3148)
* Fix label positioning on no found screen

* Use TagsControl component on not found screen
2018-12-02 10:42:30 +02:00
Zsolt Kocsmárszky
2e4d196452 Add and improve recent db logos that didn't fit in size properly (#3147)
* Add and improve recent db logos that didn't fit in size properly

* Update mongodb logo

* Remove Athena Direct as requested
2018-12-02 10:27:01 +02:00
Zsolt Kocsmárszky
4078af2996 Update, replace and fix new alert destination logos so it fits better (#3146) 2018-11-29 21:04:35 +02:00
Zsolt Kocsmárszky
73825ea266 Improve tag link colors and fix group tags on Users page (#3149) 2018-11-29 21:02:54 +02:00
Zsolt Kocsmárszky
b2a0d61844 Better manage permissions modal (#3139)
* Improved design for manage permissions

* Show Owner on Permission modal, remove owner from dropdown options

* Pass owner to Permissions modal
2018-11-29 15:40:44 +02:00
Omer Lachish
1774edabc0 take the first 8 characters for frontend version, not backend version (#3145) 2018-11-29 15:20:30 +02:00
Omer Lachish
54b8e7c136 Merge pull request #3144 from getredash/display-frontend-version
Display only the first 8 characters of frontend version
2018-11-29 15:17:14 +02:00
Omer Lachish
54f09f73db take the first 8 characters for frontend version 2018-11-29 15:03:37 +02:00
Omer Lachish
35aca1d4cf Merge branch 'master' into display-frontend-version 2018-11-29 15:03:11 +02:00
Levko Kravets
757333c2d6 When editing a dashboard title results in the visualizations being replaced by the loading markers (#3142)
* getredash/redash#3015 When editing a dashboard title results in the visualizations being replaced by the loading markers

* CR1

Co-Authored-By: kravets-levko <levko.ne@gmail.com>
2018-11-29 14:48:21 +02:00
Omer Lachish
92728de04c Display frontend version (#3105)
* add git-revision-webpack-plugin

* configure git-revision-webpack-plugin

* add commit to footer

* rename version and commit to backendVersion and frontendVersion

* rename version and commit to backendVersion and frontendVersion

* disable lint error due to use of globals

* fix snapshot test

* read frontend version from VERSION file instead latest git revision

* directly require from version.json file instead of going through WebPack's DefinePlugin

* run snapshots
2018-11-29 14:24:23 +02:00
Omer Lachish
407f14ffca run snapshots 2018-11-29 13:07:04 +02:00
Arik Fraimovich
ecb8a5c244 Hive/Databricks related improvements (#3143)
* Hive: fix issues in building options.

* Hive: add missing import.

* Split Hive into two query runners: one for http and a regular one.

* Upgrade pyhive to support http.

* Specific implementation for databricks:

* Different schema loader, because column names are different (or Hive's schema loader is broken).
* Simpler configuration.

* Simplify Databricks setup even more by removing username.
2018-11-29 13:01:23 +02:00
Omer Lachish
0e8fab4872 directly require from version.json file instead of going through WebPack's DefinePlugin 2018-11-29 13:00:13 +02:00
Omer Lachish
c15fa0c592 read frontend version from VERSION file instead latest git revision 2018-11-29 11:57:53 +02:00
Levko Kravets
09ab00e360 Migrate all tags components to React (#3138) 2018-11-29 11:35:17 +02:00
Truong Hoang Phuoc, Robert
1728f924cf Fix schema refresh to work on MySQL 8 (#3141) 2018-11-29 09:19:36 +02:00
Omer Lachish
8dc10fbd9a Merge branch 'master' into display-frontend-version 2018-11-29 09:13:41 +02:00
Jannis Leidel
a16170e701 Fix tag counts for dashboards and queries. (#3120)
* Fix tag counts for dashboards to be distinct.

This also makes use of the Dashboard.all base query.

Fix #3108.

* Use Query.all_queries as the base query for Query.all_tags.

* Add test case for Dashboard.all_tags.
2018-11-28 14:06:15 +02:00
Arik Fraimovich
07c0bba568 Delete now.json (#3134) 2018-11-28 10:36:33 +02:00
Arik Fraimovich
d36d18f85b Support unicode in Postgres/Redshift schema (#3124) 2018-11-27 09:06:44 +02:00
Arik Fraimovich
bd20ce12ac Don't allow updating user's email to blacklisted domain. (#3127) 2018-11-26 21:22:14 +02:00
Arik Fraimovich
1cdfcfaa3c Vertica: update driver & add support for connection timeout (#3125)
* Update Vertica driver version

* Vertica: add support for connection timeout
2018-11-26 21:21:49 +02:00
Arik Fraimovich
2fdace518a Use lower timeout for the first 5 seconds when polling for results. (#3128) 2018-11-26 21:21:07 +02:00
Igor Canadi
3516e4ef45 Add Rockset query runner (#3068)
* Add Rockset query runner

Per REST API documented here: https://docs.rockset.com/rest/

* Update rockset.py

* Add Rockset logo

* Refactor Rockset qury runner:

* More idomatic names for configuration.
* Move API code to separate class to make it easier removing it when we
  switch to official library.
* Make Test Connection work.

* apply autopep8 to rockset.py
2018-11-26 21:20:37 +02:00
Arik Fraimovich
d842968142 MongoDB: add support for sorting columns. (#3126) 2018-11-26 16:08:04 +02:00
Arik Fraimovich
600741620a Remove (Beta) from Query Results query runner name (#3123) 2018-11-26 15:31:39 +02:00
YOSHIDA Katsuhiko
45f4277eb4 Fix forked query is opened in the same tab (#3121) 2018-11-26 14:23:55 +02:00
Jannis Leidel
bcf3041c91 Show menu divider only if query is archived. (#3122)
Fix #3117.
2018-11-26 14:02:52 +02:00
Zsolt Kocsmárszky
da423340ec Fix mobile padding issues on Query results (#3111) 2018-11-26 13:41:07 +02:00
Arik Fraimovich
4003d4f1aa Add event tracking to autocomplete toggle & trackEvent helper function (#3114)
* Add non Angular version of Events.

* Add event tracking for autocomplete toggle

* Fix lint error in QueryEditor
2018-11-26 09:58:39 +02:00
YOSHIDA Katsuhiko
a6b782e0ce Add get_current_user() into Python Data Source (#3088) 2018-11-25 14:11:27 +02:00
YOSHIDA Katsuhiko
5648de9ba8 Open new tab when forking a query (#3089)
* Open new tab when forking a query

* Delete old fork menu item and add icon
2018-11-25 14:09:52 +02:00
Jannis Leidel
13eb365f7b Update changelog for v6.0.0-beta. (#3112)
* Add v6.0.0-beta changelog entry.

* Update CHANGELOG.md
2018-11-25 14:08:25 +02:00
Udomomo
8257d9d037 Add permissions to the result of "manage.py groups list" command (#3007)
* Add permissions to the result of "groups list" command

* added permissions to test case

* removed setting for debug
2018-11-25 13:47:25 +02:00
Allen Short
babbeb79f0 keep query text in local state for now (#3107) (#3110)
* keep query text in local state for now (#3107)

This will be unnecessary once the queryText prop isn't managed by Angular.

* Fix: make formatQuery work
2018-11-23 15:28:30 +02:00
Omer Lachish
8028397f27 Merge branch 'master' into display-frontend-version 2018-11-22 12:52:43 +02:00
Subhi Al Hasan
e05c8e6060 Fix collection fields retreival bug when Views are present in MONGO DB (#3097)
* Fix collection fields retreival bug when Views are present in MongoDB

* fixing _is_collection_a_view function

* Update redash/query_runner/mongodb.py

Co-Authored-By: jodevsa <jodevsa@gmail.com>
2018-11-21 17:13:12 +02:00
Omer Lachish
fae2b70866 Merge branch 'display-frontend-version' of github.com:getredash/redash into display-frontend-version 2018-11-21 10:49:29 +02:00
Omer Lachish
1119fce44c Merge branch 'master' into display-frontend-version 2018-11-21 10:48:03 +02:00
Omer Lachish
bfb7edc0eb fix snapshot test 2018-11-21 10:47:54 +02:00
Omer Lachish
a39a739473 disable lint error due to use of globals 2018-11-21 10:44:34 +02:00
Omer Lachish
c9dfac5b1d rename version and commit to backendVersion and frontendVersion 2018-11-21 10:44:18 +02:00
Omer Lachish
1b66fff3be rename version and commit to backendVersion and frontendVersion 2018-11-20 23:34:39 +02:00
Arik Fraimovich
0fe1b5f9d4 Fix: registerAll fails after minification (#3106) 2018-11-20 23:34:37 +02:00
Levko Kravets
143db90a50 Fix query page header (#3046)
* getredash/redash#3017 Improve query page header

* getredash/redash#3017 Resolve conflicts

* getredash/redash#3017 CR1 (fix margins/paddings)
2018-11-20 18:48:41 +02:00
Arik Fraimovich
bac90db3ee Autocomplete toggle improvements (#3091)
* Autocomplete toggle improvements:

* Refactor to its own component.
* Show state in tooltip (enabled/disabled).
* Disable the toggle if autocomplete is not possible (no schema/too many tokens).

* Remove unsued code.

* Custom icons font (currently has only two icons).

Generated with Icomoon. If we extend its use, we should probably automate this and move to its own package.

* Don't disable live autocomplete for data sources without schema.

It can still be useful to autocomplete from local keywords.

* Differentiate between autocomplete toggle states with an icon.

Also added explicit message for the disabled state.

* Remember thes state of autocomplete.

* Only auto register init functions.
2018-11-20 18:45:33 +02:00
Omer Lachish
649d46de89 add commit to footer 2018-11-20 15:25:07 +02:00
Omer Lachish
0163e85eda configure git-revision-webpack-plugin 2018-11-20 15:24:55 +02:00
Omer Lachish
f25beb3fb7 add git-revision-webpack-plugin 2018-11-20 15:24:41 +02:00
Omer Lachish
c66f63d7a5 Prevent Query's updated_at from changing when it is linked to new query results (#3082)
* avoid Query's updated_at from changing when it is linked to new query results

* move comment to previous line

* move QueryResult tests to their own module

* add test which verifies that updated_at is not changed on query data
updates

* tests were false positives - they compared HH:MM:SS, but that never
changed because the original time was 1 week behind.

* remove redundant constructor

* remove hack and use a proper event to prevent updated_at from changing

* use self.assertEqual instead of assert
2018-11-20 12:22:15 +02:00
Levko Kravets
16ae0aa3d8 getredash/redash#2901 Fix docs links (#3102) 2018-11-20 11:06:09 +02:00
Arik Fraimovich
68ada7b590 UI for the feature flag of the share edit permissions feature (#3077)
* Remove unused settings.

* Add: UI feature flag for sharing permissions

* Revise feature flag message
2018-11-20 10:04:42 +02:00
San
9e745ef648 Delete redundant regex segment (#3100) 2018-11-20 09:48:24 +02:00
Allen Short
ee0d7f5ec9 force angular to update query editor properly (re #3098) (#3099) 2018-11-20 08:06:32 +02:00
Levko Kravets
e36853ca84 Tags autocomplete: some tags not shown on search (#3094)
* getredash/redash#3052 Tags autocomplete: some tags not shown on search

* getredash/redash#3052 CR1
2018-11-19 12:33:05 +02:00
YOSHIDA Katsuhiko
d43b35ba6f Change Standard SQL as the default (#3085) 2018-11-18 11:21:00 +02:00
Topher Cyll
6e4f0ccee8 Bubble chart marker size override was clobbering seriesColor. (#3063)
Colors can now be set for bubble charts in UI.
2018-11-18 11:05:18 +02:00
Vladislav Denisov
0ce7772aa3 clickhouse: added WITH TOTALS option support (#3083) 2018-11-15 22:11:43 +02:00
San
f6ef38479c support tel, sms, mailto links in the query result (#3084) 2018-11-15 22:08:02 +02:00
Arik Fraimovich
bf85ddaaff Always use basic autocomplete. (#3079) 2018-11-15 08:58:30 +02:00
Arik Fraimovich
8bb96c8c91 Fix: URL data source shouldn't require URL. (#3078)
Closes #2919.
2018-11-14 15:43:34 +02:00
Filipe Veloso
42b05cee00 Update docker-compose.yml (#2905)
* Update docker-compose.yml

jut updating docker-compose dev to version 3, any special reason to keep redis on 3? and pg on 9.5? I could also add a volume to pg, any reason not to do so?

* rollback to redis 3 and pg 9.5 due to consistency in project defaults

* Configure volume directly in worker service.
2018-11-14 14:27:19 +02:00
Anton Burnashev
d0fd02123a Add white-space padding to separators in the footer (#3076) (#3076)
Closes #3075
2018-11-14 10:44:38 +02:00
Takuya Arita
e34203dac3 Remove only Redash containers (#3073) 2018-11-13 16:45:40 +02:00
koooge
c2bd8518a6 Makefile: Add make targets for test (#3032) 2018-11-12 09:06:25 +01:00
Levko Kravets
46363ccc70 Table visualization horizontal scrollbar should not be always visible (#3061) 2018-11-12 07:50:44 +01:00
Levko Kravets
5e1512e777 Mustache: don't html-escape query parameters values (#3058) 2018-11-08 21:54:08 +01:00
Arik Fraimovich
188c045fdb Add Kylin logo (#3054) 2018-11-08 11:20:05 +01:00
Omer Lachish
57d921dc2b Druid query runner (#3047)
* add Druid query runner skeleton

* enable Druid only if package is available

* add Druid

* remove redundant  override

* correct configuration schema

* implements run_query

* implement get_schema

* remove username and password

* fix small lint issues

* proper indentation

* add correct type mapping
2018-11-08 10:48:45 +01:00
Xin Bai
df0804c8fd Add Kylin plugin for SQL query (#2936) 2018-11-08 10:24:05 +01:00
Alexander Leibzon
c289dde806 Google analytics fix fixes #2965 (#3008)
* add PagerDuty as an Alert Destination

* remove comments

* add unknown state handling

* fixes

* revert setup.sh

* Remove test method.

* resolves #2965

* more elegant, as per Arik's suggestion

* Add missing whitespace.
2018-11-08 10:18:33 +01:00
Levko Kravets
b7cadca3b7 Edit-in-place component ignored isEditable flag and didn't work on Groups page (#3049) 2018-11-08 10:17:18 +01:00
Sami Jaktholm
43f8200707 feat(redshift): hide tables the configured user cannot access (#2866)
The SVV_COLUMNS table used to determine the tables of a
Redshift database includes all tables, even those the
current user cannot access, by default. These tables clutter
the schema browser and make it harder for users to determine
which tables they should have access to.

These changes modify the Redshift query runner so that
tables the configured user cannot access are filtered
out from the database schema. The checks are two-fold:

* First, the query uses HAS_SCHEMA_PRIVILEGE to check if the
  current user has USAGE rights on a schema the given table
  belongs to. This privilege is required to access any of
  the tables in a schema.

* Second, the query tries to determine if the current user
  has SELECT access to the given table. Two cases need to
  be considered here:

  * First, we need to check if the table is part of an
    external schema. Access to tables in external schema
    is controlled at schema level - you cannot grant or
    revoke access to specific external tables. Additionally,
    the HAS_TABLE_PRIVILEGE returns an error if it is asked
    to give a verdict for an external table.

    Hence, the query checks if the schema a specific table
    belongs to is an external schema and if it is, the table
    is included in the list (if we got here the user already
    had USAGE on the given schema). This check short-circuits
    the table-level access check for external tables which
    means they are never passed to HAS_TABLE_PRIVILEGE().

  * Then, if the table was not part of an external schema,
    the HAS_TABLE_PRIVILEGE() function is used to determine
    if the user has SELECT access to the given table. The
    table is included in the schema if this check passes.

Together this condition ensures that tables the user definitely
cannot access are not included in the schema browser.

These changes have been tested to work in an environment that
includes normal and external schemas, normal and late-binding
views, and normal and external tables.
2018-11-08 09:53:04 +01:00
Arik Fraimovich
a1b580bba6 Fix Docker Compose version number in Cypress config (#3051)
* Update Docker compose version

* Experiment with Docker-Composeless CI build

* Switch back to Docker compose based tests
2018-11-07 17:21:31 +01:00
Zsolt Kocsmárszky
19d0313ea2 Fixing tag issues (#3006)
* Add title="" for tags

* Fix lines

* Fix long tag and overlaying tag issues on queries
2018-11-07 16:58:15 +01:00
Arik Fraimovich
667fe43e23 Revert "address tag display on query list page" (#3050)
* Revert "remove pytest_watch (#3048)"

This reverts commit 096eba3876.

* Revert "address tag display on query list page (#2803)"

This reverts commit 99115a12e6.
2018-11-07 16:57:37 +01:00
Omer Lachish
096eba3876 remove pytest_watch (#3048) 2018-11-07 16:49:41 +01:00
Alison
99115a12e6 address tag display on query list page (#2803)
* address tag display on query list page

* character limit tags in css

* updates to tags on levko's feedback
2018-11-07 16:12:31 +01:00
Gabriel Dutra
7d601cbbc9 Cypress based E2E tests (#3019) 2018-11-07 14:37:08 +01:00
GitSumito
bf6a09c5aa CLI sort (#3041) 2018-11-06 16:45:39 +01:00
Yossi-a
99967e720f Sort columns with undefined values (#2745)
* sort should treat undefine value as the minimal value

* explicit undefined check
2018-11-05 04:47:42 +02:00
Arik Fraimovich
27f489de20 Build docker image on master branch. (#3039) 2018-11-04 12:12:13 +02:00
yoavbls
46941d3aa1 Update Flask-Admin to 1.5.2 (#3036)
I don't know if someone uses flask-admin but I upgraded it because of this bug:
https://github.com/flask-admin/flask-admin/issues/1588
In 1.4.2 you cant add and edit records/
2018-11-01 15:58:50 +02:00
Levko Kravets
60c230add7 getredash/redash#3034 Postgres query runner: handle NaN/Infinity values (#3035) 2018-11-01 15:57:39 +02:00
Takuya Arita
0784a0c6f5 Add some tests for Query Results (#3031) 2018-10-31 11:05:17 +02:00
Zsolt Kocsmárszky
9288d89248 Fix query result section (#2980) 2018-10-29 22:09:49 +02:00
Levko Kravets
391fbe130b getredash/redash#2998 Charts lose responsive features after refreshing the dashboard (#3024) 2018-10-29 22:08:35 +02:00
Levko Kravets
e25c8c4145 getredash/redash#3022 Toolbox covers part of the chart (#3023) 2018-10-29 11:38:29 +02:00
Arik Fraimovich
57353d1b40 Add -beta to version 2018-10-28 15:58:20 +02:00
Arik Fraimovich
7f4e08154f Bump version 2018-10-28 15:56:32 +02:00
Arik Fraimovich
500c82815b Add netlify config (#2999) 2018-10-28 15:31:27 +02:00
Arik Fraimovich
4a846f04e9 Add settings to import 2018-10-28 11:11:11 +02:00
Takuya Arita
b1e9d87e2a Apply query format options from settings (#2342)
* Apply query format options from settings

* Apply sqlparse format options via env-vars
2018-10-28 10:40:07 +02:00
Arik Fraimovich
ab6ed7da34 Fix: setup.sh fails when run as root. (#2996)
Closes #2979
2018-10-23 09:49:24 +03:00
GitSumito
2e6883c527 Add "Users" users are belong to into groups list (#2991) 2018-10-21 11:40:07 +03:00
YOSHIDA Katsuhiko
4c44999b2c Fix an invalid prop type warning (#2992) 2018-10-21 11:39:37 +03:00
deecay
34c118cf83 Add: Heatmap chart visualization by Plotly (#2080) 2018-10-21 11:39:06 +03:00
Arik Fraimovich
38a89b9783 Table visualization: change default size to 25 and add more size options. (#2982) 2018-10-21 11:38:47 +03:00
YOSHIDA Katsuhiko
6e836795b2 Fix url scheme (#2994) 2018-10-21 11:38:11 +03:00
YOSHIDA Katsuhiko
719fc41dd1 Add page size settings (#2993)
Add page size settings.

| name | default | description |
| :---- | :------ | :---------- |
| `REDASH_PAGE_SIZE` | 20 | How many items are displayed in one page as default. |
| `REDASH_PAGE_SIZE_OPTIONS` | 5,10,20,50,100 | How many steps as page_size. |

This feature has requested at the meetup in Japan.

https://redash-meetup.connpass.com/event/101420/
2018-10-20 14:33:31 +02:00
Arik Fraimovich
467ec201da Add Jest based tests to our stack (#2985)
* Add Jest packages
* Add first test
* Install eslint rules for jest & move deps to dev
* Configure cirlce to run jest
* package.json: Remove dev command
* package.json: clean command
* Don't autoload test files.
* Fix: webpack-dev-server was recompiling all the files on every change
* Update CircleCI step names
2018-10-19 19:04:02 +03:00
koooge
5ab143de41 Rearrange make target (#2989) 2018-10-19 11:25:24 +02:00
Arik Fraimovich
284e497483 Databricks updates: logo, name and enable by default (#2983)
* Add Databricsk logo.
* Enable it by default.
* Rename to Databricks.
2018-10-19 10:00:33 +02:00
dmonego
c5613dddf1 Chnage: switch to Webpack 4 (#2933) 2018-10-18 21:21:47 +03:00
Arik Fraimovich
34fb3ac79f Change: add timeout to destination requests. (#2960) 2018-10-18 17:33:58 +03:00
Arik Fraimovich
5f58c328f1 MongoDB: skip system collections when loading schema. (#2961) 2018-10-18 17:33:24 +03:00
Arik Fraimovich
7d1dbb87db Change: update MongoDB requirements to support srv. (#2962) 2018-10-18 17:32:43 +03:00
GitSumito
45f4d46245 Add "Groups" users are belong to into users list (#2967) 2018-10-18 17:32:21 +03:00
Arik Fraimovich
44d05c35ac Presto query runner improvements (#2968)
* Presto: support for setting protocol (http/https).

* Presto: safe loading error message.
2018-10-18 17:31:48 +03:00
Arik Fraimovich
edd2cb85f7 Update CHANGELOG.md 2018-10-18 14:59:00 +03:00
Hiroka Zaitsu
6c364369bb Fix: TreasureData data source - deduplicate column names (#2867)
* Fix: TreasureData data source - deduplicate column names

* Maping types
2018-10-18 09:43:28 +03:00
YOSHIDA Katsuhiko
869841b2ac Preventing open redirection (#2906)
* Prevent open redirection attack

* Add redirection url after logging in test

* Sanitize url just before redirecting it

* Consider when next parameter is None
2018-10-17 21:55:58 +03:00
Arik Fraimovich
c71f722552 Query Results query runner improvements: (#2969)
- Show meaningful error when failing to create table.
- Quote column names to allow more characters types.
2018-10-16 15:23:00 +03:00
Jannis Leidel
af3a1e00c6 Fix #2757 - Use full text search ranking when searching in list views. (#2798)
This applies to the queries, dashboard and users views.
2018-10-16 10:38:37 +02:00
Gabriel Dutra
5b2ec81e65 Fix no tags shown when having empty set (#2964) 2018-10-15 23:08:18 +03:00
Vladislav Denisov
0008e5803b clickhouse: move timeout to params (#2956)
* clickhouse: timeout moved to params

* clickhouse: use get() method for timeout
2018-10-15 20:06:16 +03:00
Arik Fraimovich
e1c1f67abb Add: option to auto reload widget data in shared dashboards. (#2959) 2018-10-15 20:05:40 +03:00
Marina Samuel
30283235a4 Fix tarball build failure. (#2963) 2018-10-15 20:05:07 +03:00
Levko Kravets
845e33b396 Query page layout improvements for small screens (#2922)
* getredash/redash#2796 Make entire page scrollable on small screens; improve metadata block

* getredash/redash#2796 Improve query page header layout; fix small bugs (margins, etc.)
2018-10-15 19:59:05 +03:00
Arik Fraimovich
17baa66188 Show "Add description" only after saving the query. (#2958)
Closes #2897
2018-10-15 17:45:22 +03:00
Arik Fraimovich
5df7bd12c9 Fix: apply missing CSS classes to EditInPlace component. (#2957) 2018-10-15 17:38:22 +03:00
Nicolas Ferrandini
e14c8b61a0 Add DB2 as a data source using ibm-db python package (#2848)
* Add DB2 as a data source using ibm-db python package

* fix some codeclimate issue

* fix long line and missing white space

* Manage case of bad import

* Add DB2 query_runner as default query runner

* Fixed minor PEP8 rules
2018-10-15 17:13:39 +03:00
Arik Fraimovich
a8a3ec66fd Bring back fix to Box plot hover. (#2941) 2018-10-15 16:01:38 +03:00
GitSumito
a4b9c2da12 fixed https://github.com/getredash/redash/issues/2950 (#2951)
* fixed https://github.com/getredash/redash/issues/2950

* fixed test code

* Effective -> Active. thank you @kravets-levko
2018-10-15 15:57:51 +03:00
Vladislav Denisov
e6146dae0f Clickhouse fixes (#2953)
* clickhouse: avoid last line with comment in query

* clickhouse: add request timeout
2018-10-15 15:03:02 +03:00
Levko Kravets
bd3fe880a4 Add missing default "extensions" directory (webpack fails to build without it) (#2952) 2018-10-15 13:51:13 +02:00
Marina Samuel
02e919c39b Closes #2565: Add frontend extension capability. (#2799) 2018-10-14 15:53:39 +03:00
Arik Fraimovich
99c73aef2d Update snowflake_connector_python version (#2946) 2018-10-14 14:39:52 +03:00
Ralphilius
be377b5f59 Add Counter label (#2900)
* Add Counter label

* Update index.js

* Added visualization name as placeholder

* Backward-compatible for visualization name as label
2018-10-14 11:23:20 +03:00
Zsolt Kocsmárszky
6b11ae4312 Design refinements (#2927)
* Fix search size on smaller tablet size

* Less prominent tag counter

* Fix hiding logo

* Add missing space between icon and button text

* Different embed icon

* Revert embed icon to its original

* Better edit source icon + markup cleanup
2018-10-14 11:00:27 +03:00
YOSHIDA Katsuhiko
9021977a54 Fix admin api recording (#2937) 2018-10-14 10:49:11 +03:00
Sami Jaktholm
9c8d06578a feat: add support for expanding dashboard visualizations (#2824)
* feat: add support for expanding dashboard visualizations

These changes implement support for expanding a dashboard visualization
into a larger modal dialog.

This is useful if you have a dashboard with lots of small widgets and
want to inspect one of the widgets more closely. In the past, this
would've required you to navigate to the query page to see a larger
version of the visualization. With these changes, visualizations can
be expanded right from the dashboard.

The implementation is simple as it just renders the visualization into
a modal dialog. Other parts of the widget (e.g. parameters) are not
included in this dialog.

* chore(widget-dialog): use query-link widget to render title

This reduces code duplication a bit. The link is made read-only
as navigation doesn't close the modal dialog.

* fix: make ui-select dropdown z-index > modal dialog z-index in dashboard page

Otherwise the dropdown renders behind the modal dialog if filter value
is changed from the modal view of a widget.
2018-10-14 10:42:31 +03:00
YOSHIDA Katsuhiko
114beb2480 Auto focus tag input (#2938) 2018-10-14 10:39:08 +03:00
Alexander Leibzon
e97a5cbb29 add PagerDuty as an Alert Destination (#2903) 2018-10-14 10:35:39 +03:00
Alexander Leibzon
e87efc8bc3 fixes #2924 (#2931)
Google Spreadsheets: support for open by url
2018-10-11 21:07:20 +03:00
Arik Fraimovich
be7f601d21 Speed up builds by skipping installing requirements_all_ds.txt in CI unit tests (#2928)
* Speed up builds by skipping requirements_all_ds.txt

* Update docker compose file version

* Start services before running commands

* Add boto and Athena dependencies to requirements_dev.txt
2018-10-11 14:12:28 +03:00
Levko Kravets
9b59d10677 Use Plotly's function to clean y-values (x may be category or date/time) (#2872) 2018-10-11 12:27:28 +03:00
Levko Kravets
a40669e07f getredash/redash#2875 Update plotly.js; some cleanup; fix chart legend issue in horizontal mode (#2902) 2018-10-11 11:23:40 +03:00
Levko Kravets
0bcf5d4be7 Merge pull request #2929 from combineads/fix-date-filter
Fix: date value in a filter is duplicated.
2018-10-11 09:08:31 +03:00
combineads
8bc96764a6 Fix: date value in a filter is duplicated. 2018-10-11 14:56:57 +09:00
Niko Eckerskorn
6ea03e58b4 Address edgecase when retrieving Glue schemas for Athena query runner. (#2868)
Fixes getredash/redash#2858
2018-10-10 19:13:48 +03:00
Gabriel Dutra
94801665ab Fix output file name not changing after rename query (#2917) 2018-10-10 15:07:11 +03:00
Gabriel Dutra
aa12151e19 Fix export query results output file name (#2916)
- regexp `/ /g` will seek for all space ocurrences, not only the first
2018-10-10 14:56:31 +03:00
Jannis Leidel
c2429e92d2 Consistently use simplejson to loading and dumping JSON. (#2817)
* Consistently use simplejson to loading and dumping JSON.

This introduces the new functions redash.utils.json_dumps and redash.utils.json_loads and simplifies the custom encoder setup.

UUIDs are now handled by the default encoder, too.

Fixes #2807.

Use string comparison in parse_boolean instead of the (simple)json module.
2018-10-09 15:38:06 +02:00
Jannis Leidel
5ffc85c066 Extend menu item text a bit for visual consistency. 2018-10-08 20:08:07 +02:00
Jannis Leidel
fad757c878 Don’t show “Add to dashboard” in dropdown to unsaved queries. 2018-10-08 20:08:07 +02:00
Jannis Leidel
3351a281ee Fix webpack build error about BigMessage. (#2910) 2018-10-08 12:03:54 +02:00
Arik Fraimovich
1f0053f531 MySQL: hide sys tables (#2909) 2018-10-08 10:05:16 +03:00
Arik Fraimovich
935dc38360 Update setup files: (#2908)
* Remove use of newgrp
* Updated packer configuration
2018-10-08 09:41:15 +03:00
Arik Fraimovich
bfef7fae93 Remove unused dependencies. (#2907)
Closes #2782
2018-10-08 09:39:57 +03:00
cclauss
da6d456f6f CircleCI: Flake8 tests passing on Legacy Python and Python 3 (#2881) 2018-10-05 13:48:01 +03:00
Arik Fraimovich
c19199c2fb Add margin between format and autocomplete buttons (#2899) 2018-10-04 12:34:03 +03:00
Arik Fraimovich
1e78861f85 Move Ant styles into a central location. (#2898)
This is to ensure that we include our override after loading Ant's styles.
2018-10-04 12:27:36 +03:00
Arik Fraimovich
10bc5a0bf6 Remove misplaced bracket. (#2894) 2018-10-04 12:01:38 +03:00
Marina Samuel
313af904df Add ability for extensions to add periodic tasks. (#2740) 2018-10-04 09:07:58 +03:00
Allen Short
8c478087a9 Rewrite query editor to React (#2636) 2018-10-03 22:25:19 +03:00
Arik Fraimovich
ccac41c6d4 Fix: JS build breaks because registerAll tries to run EditInPlace component (#2886) 2018-10-02 16:22:47 +03:00
Jannis Leidel
69635f2c40 Rename Yandex Metrika to Metrica. (#2884)
Fix #2874.
2018-10-02 16:11:01 +03:00
yoavbls
1867ea50bb Add option to query cached results (#2855) 2018-10-02 16:05:41 +03:00
Jannis Leidel
c64d5ef6c0 Fix #2876 - Remove accidental query-result-link component from 'Add to dashboard link.' (#2883) 2018-10-01 22:29:20 +03:00
Alison
e3a63899d3 Add ability to search table column names in schema browser (#2681) 2018-10-01 11:11:32 +02:00
Jannis Leidel
4685887fe5 Fix name of edit-in-place React component. 2018-09-29 10:14:48 +02:00
Arik Fraimovich
f103357e60 Fix: wrong reference (EditInPlaceText -> EditInPlace) 2018-09-28 21:51:36 +02:00
Jannis Leidel
11738f73ac Removed redundant exception handling since execute_query task handles that. 2018-09-28 21:31:49 +02:00
Allen Short
d07c4f969b Support authentication for the URL query runner.
Adds a new BaseHTTPQueryRunner class and tests.
2018-09-28 21:31:49 +02:00
Allen Short
505aafbce3 Convert edit-in-place component to React (#2637) 2018-09-28 22:30:10 +03:00
Marina Samuel
b765693879 Upgrade Celery to 4.2.1. (#2773) 2018-09-28 21:28:30 +02:00
Jannis Leidel
4620fed0cf Use server side sort order for tag list and show count of tagged items. (#2833)
* Use tag list ordering from backend.

This basically stops resorting the tag list on the client side and use the already existing (and correct) descending sort order bu tag usage count from the backend.

* Show count of tagged items in tag list.
2018-09-28 17:24:13 +03:00
Arik Fraimovich
48ad1d2dce Add v5.0.1 to the CHANGELOG. 2018-09-28 17:18:41 +03:00
Arik Fraimovich
f2c323a089 Disable integration tests
The integration tests are currently not failing and providing false positives when running them. I'm disabling this until the test suite is more stable.

@jrbenny35 @jezdez
2018-09-28 11:52:14 +03:00
Levko Kravets
ec17cc7eab getredash/redash#2854 Widget titles wrong rendering on public dashboards (#2870) 2018-09-28 11:26:37 +03:00
Arik Fraimovich
6c7bbe9041 Merge branch 'master' of github.com:getredash/redash 2018-09-27 21:47:56 +03:00
Arik Fraimovich
551b0222c4 Cleanup packer configuration 2018-09-27 21:47:45 +03:00
Jannis Leidel
2b0e6e9e79 Refactor list based controllers. (#2790)
This reduces the code duplication between the dashboard, user and queries list pages and normalizes many of the APIs between them.

This also:
- allows sorting query favorites
- adds a pagination size select to the dashboard list
- fixes a bunch of UI inconsistencies between the queries and dashboards list (e.g. margins)

The new ListCtrl class is subclassed in the various specific page controllers and extended as needed. New list pages can make use of the same pattern in the future.

This also adds some missing event recordings from 34e39eda4a.
2018-09-27 17:06:26 +02:00
Levko Kravets
4727c19253 getredash/redash#2796 Improve counter text scaling (#2840) 2018-09-27 13:27:10 +03:00
Arik Fraimovich
2ff4d07e83 Change placement (right/bottom) of chart legend depending on chart width (#2852)
Closes getredash/redash#2796.
2018-09-27 12:34:28 +03:00
Arik Fraimovich
1997f53f40 Fix CircleCI setup for release branches. (#2859) 2018-09-27 10:36:48 +03:00
Jannis Leidel
c03b5d51b7 Simplified data source resource. Refs #2856. 2018-09-27 09:20:07 +02:00
yoavbls
197665bb6a Fix bug in DataSourceResource load (#2857) 2018-09-27 09:15:36 +02:00
Levko Kravets
28fbc2ae62 getredash/redash#2796 Change placement (right/bottom) of chart legend depending on chart width 2018-09-27 10:14:58 +03:00
Alexey Korobkov
ea1c4ca85c Add auth via JWT providers (#2768)
* authentication via JWT providers
* add support for IAP JWT auth
* remove jwt_auth Blueprint and /headers endpoint
* fix pep8: imports
2018-09-26 21:17:48 +03:00
Alison
588e0cce43 Add autocomplete toggle (#2780) 2018-09-26 17:32:38 +02:00
Alison
8a50351520 Add ability to add viz to dashboard from query edit page (#2767) 2018-09-26 17:29:38 +02:00
Alison
34e39eda4a Port moving events serverside (#2766) 2018-09-26 17:17:46 +02:00
Alison
28a8525ce3 Add databricks query runner (#2747)
Fixes #2685.
2018-09-26 17:07:03 +02:00
Arik Fraimovich
5e70f9c04a Update README.md 2018-09-25 21:08:46 +03:00
Arik Fraimovich
a05b5ba68d Docker based setup scripts (#2850) 2018-09-25 21:08:24 +03:00
Dan VerWeire
40ba66c58e Fix invalid reference to alert.to_dict() in webhook (#2849) 2018-09-25 19:12:04 +03:00
371 changed files with 13462 additions and 6845 deletions

View File

@@ -1,6 +1,19 @@
version: 2.0
flake8-steps: &steps
- checkout
- run: sudo pip install flake8
- run: ./bin/flake8_tests.sh
jobs:
unit-tests:
python-flake8-tests:
docker:
- image: circleci/python:3.7.0
steps: *steps
legacy-python-flake8-tests:
docker:
- image: circleci/python:2.7.15
steps: *steps
backend-unit-tests:
environment:
COMPOSE_FILE: .circleci/docker-compose.circle.yml
COMPOSE_PROJECT_NAME: redash
@@ -13,6 +26,7 @@ jobs:
name: Build Docker Images
command: |
set -x
docker-compose build --build-arg skip_ds_deps=true
docker-compose up -d
sleep 10
- run:
@@ -20,7 +34,7 @@ jobs:
command: docker-compose run --rm postgres psql -h postgres -U postgres -c "create database tests;"
- run:
name: Run Tests
command: docker-compose run --name tests redash tests --junitxml=junit.xml tests/
command: docker-compose run --name tests redash tests --junitxml=junit.xml --cov-report xml --cov=redash --cov-config .coveragerc tests/
- run:
name: Copy Test Results
command: |
@@ -31,14 +45,47 @@ jobs:
path: /tmp/test-results
- store_artifacts:
path: coverage.xml
frontend-unit-tests:
docker:
- image: circleci/node:8
steps:
- checkout
- run: sudo apt install python-pip
- run: npm install
- run: npm run bundle
- run: npm test
frontend-e2e-tests:
environment:
COMPOSE_FILE: .circleci/docker-compose.cypress.yml
COMPOSE_PROJECT_NAME: cypress
PERCY_TOKEN_ENCODED: MWM3OGUzNzk4ZWQ2NTE4YTBhMDAwZDNiNWE1Nzc4ZjEzZjYyMzY1MjE0NjY0NDRiOGE5ODc5ZGYzYTU4ZmE4NQ==
docker:
- image: circleci/node:8
steps:
- setup_remote_docker
- checkout
- run:
name: Install npm dependencies
command: |
npm install
- run:
name: Setup Redash server
command: |
npm run cypress start
docker-compose run cypress node ./cypress/cypress.js db-seed
- run:
name: Execute Cypress tests
command: npm run cypress run-ci
build-tarball:
docker:
- image: circleci/node:8
steps:
- checkout
- run: sudo apt install python-pip
- run: npm install
- run: npm run build
- run: .circleci/update_version
- run: npm run bundle
- run: npm run build
- run: .circleci/pack
- store_artifacts:
path: /tmp/artifacts/
@@ -52,74 +99,31 @@ jobs:
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
- run: docker build -t redash/redash:$(.circleci/docker_tag) .
- run: docker push redash/redash:$(.circleci/docker_tag)
integration-tests:
working_directory: ~/redash
machine: true
environment:
REDASH_SERVER_URL : "http://127.0.0.1:5000/"
DOCKER_IMAGE: mozilla/redash-ui-tests
steps:
- checkout
- run:
name: Install Docker Compose
command: |
set -x
pip install --upgrade pip
pip install docker-compose>=1.18
docker-compose --version
- run:
name: Pull redash images
command: |
set -x
docker-compose -f docker-compose.yml up --no-start
sleep 10
- run:
name: Pull redash-ui-tests
command: docker pull "${DOCKER_IMAGE}":latest
- run:
name: Setup redash instance
command: |
set -x
docker-compose run --rm --user root server create_db
docker-compose run --rm postgres psql -h postgres -U postgres -c "create database tests"
docker-compose run --rm --user root server /app/manage.py users create_root root@example.com "rootuser" --password "IAMROOT" --org default
docker-compose run --rm --user root server /app/manage.py ds new "ui-tests" --type "url" --options '{"title": "uitests"}'
docker-compose run -d -p 5000:5000 --user root server
docker-compose start postgres
docker-compose run --rm --user root server npm install
docker-compose run --rm --user root server npm run build
- run:
name: Run tests
command: |
set -x
docker run --net="host" --env REDASH_SERVER_URL="${REDASH_SERVER_URL}" "${DOCKER_IMAGE}"
- store_artifacts:
path: report.html
workflows:
version: 2
integration_tests:
jobs:
- integration-tests:
filters:
branches:
only: master
build:
jobs:
- unit-tests
- python-flake8-tests
- legacy-python-flake8-tests
- backend-unit-tests
- frontend-unit-tests
- frontend-e2e-tests
- build-tarball:
requires:
- unit-tests
- backend-unit-tests
filters:
tags:
only: /v[0-9]+(\.[0-9\-a-z]+)*/
branches:
only:
- master
- release/.*
- /release\/.*/
- build-docker-image:
requires:
- unit-tests
- backend-unit-tests
filters:
branches:
only:
- release/.*
- master
- preview-build
- /release\/.*/

View File

@@ -1,4 +1,4 @@
version: '2'
version: '3'
services:
redash:
build: ../

View File

@@ -0,0 +1,47 @@
version: '3'
services:
server:
build: ../
command: dev_server
depends_on:
- postgres
- redis
ports:
- "5000:5000"
environment:
PYTHONUNBUFFERED: 0
REDASH_LOG_LEVEL: "INFO"
REDASH_REDIS_URL: "redis://redis:6379/0"
REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
worker:
build: ../
command: scheduler
depends_on:
- server
environment:
PYTHONUNBUFFERED: 0
REDASH_LOG_LEVEL: "INFO"
REDASH_REDIS_URL: "redis://redis:6379/0"
REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
QUEUES: "queries,scheduled_queries,celery"
WORKERS_COUNT: 2
cypress:
build:
context: ../
dockerfile: Dockerfile.cypress
depends_on:
- server
- worker
environment:
CYPRESS_baseUrl: "http://server:5000"
PERCY_TOKEN: ${PERCY_TOKEN}
PERCY_BRANCH: ${CIRCLE_BRANCH}
PERCY_COMMIT: ${CIRCLE_SHA1}
PERCY_PULL_REQUEST: ${CIRCLE_PR_NUMBER}
redis:
image: redis:3.0-alpine
restart: unless-stopped
postgres:
image: postgres:9.5.6-alpine
command: "postgres -c fsync=off -c full_page_writes=off -c synchronous_commit=OFF"
restart: unless-stopped

View File

@@ -1,5 +1,10 @@
#!/bin/bash
if [ $CIRCLE_BRANCH = master ] || [ $CIRCLE_BRANCH = preview-build ]
then
FULL_VERSION='preview'
else
VERSION=$(jq -r .version package.json)
FULL_VERSION=$VERSION.b$CIRCLE_BUILD_NUM
fi
echo $FULL_VERSION

View File

@@ -3,3 +3,4 @@ VERSION=$(jq -r .version package.json)
FULL_VERSION=$VERSION+b$CIRCLE_BUILD_NUM
sed -ri "s/^__version__ = '([A-Za-z0-9.-]*)'/__version__ = '$FULL_VERSION'/" redash/__init__.py
sed -i "s/dev/$CIRCLE_SHA1/" client/app/version.json

View File

@@ -1,4 +1,6 @@
client/.tmp/
client/dist/
node_modules/
.tmp/
.venv/
.git/

View File

@@ -1,34 +0,0 @@
<!--
#####################################################################
#
# Need support? USE THE FORUM! https://discuss.redash.io/c/support.
#
# Don't have steps to reproduce and actually not sure it's a bug?
# Use the forum! https://discuss.redash.io/c/support.
#
#####################################################################
**Got an idea for a new feature?** Check if it isn't on the roadmap already: http://bit.ly/redash-roadmap and start a new discussion in the features category: https://discuss.redash.io/c/feature-requests 🌟.
Found a bug? Please fill out the sections below... thank you 👍
Found a security vulnerability? Please email security@redash.io to report any security vulnerabilities. We will acknowledge receipt of your vulnerability and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. If you want to encrypt your disclosure email, you can use this PGP key.
-->
### Issue Summary
A summary of the issue and the browser/OS environment in which it occurs.
### Steps to Reproduce
1. This is the first step
2. This is the second step, etc.
Any other info e.g. Why do you consider this to be a bug? What did you expect to happen instead?
### Technical details:
* Redash Version:
* Browser/OS:
* How did you install Redash:

34
.github/ISSUE_TEMPLATE/---bug_report.md vendored Normal file
View File

@@ -0,0 +1,34 @@
---
name: "\U0001F41B Bug report"
about: Report reproducible software issues so we can improve
---
<!--
We use GitHub only for bug reports 🐛
Anything else should be posted to https://discuss.redash.io 👫
🚨For support, help & questions use https://discuss.redash.io/c/support
💡For feature requests & ideas use https://discuss.redash.io/c/feature-requests
**Found a security vulnerability?** Please email security@redash.io to report any security vulnerabilities. We will acknowledge receipt of your vulnerability and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. If you want to encrypt your disclosure email, you can use this PGP key.
-->
### Issue Summary
A summary of the issue and the browser/OS environment in which it occurs.
### Steps to Reproduce
1. This is the first step
2. This is the second step, etc.
Any other info e.g. Why do you consider this to be a bug? What did you expect to happen instead?
### Technical details:
* Redash Version:
* Browser/OS:
* How did you install Redash:

View File

@@ -0,0 +1,17 @@
---
name: "\U0001F4A1Anything else"
about: "For help, support, features & ideas - please use https://discuss.redash.io \U0001F46B "
labels: "Support Question"
---
We use GitHub only for bug reports 🐛
Anything else should be posted to https://discuss.redash.io 👫
🚨For support, help & questions use https://discuss.redash.io/c/support
💡For feature requests & ideas use https://discuss.redash.io/c/feature-requests
Alternatively, check out these resources below. Thanks! 😁.
- [Forum](https://disucss.redash.io)
- [Knowledge Base](https://redash.io/help)

2
.gitignore vendored
View File

@@ -24,3 +24,5 @@ node_modules
.sass-cache
npm-debug.log
cypress/screenshots
cypress/videos

View File

@@ -1,5 +1,231 @@
# Change Log
## v6.0.0 - 2018-12-16
v6.0.0 release version. Mainly includes fixes for regressions from the beta version.
This release had contributions from 5 people: @rauchy, @denisov-vlad, @arikfr, @ariarijp, and @gabrieldutra. Thank you, everyone 🙏
### Changed
* #3183 Make refresh_queries less noisey in logs. @arikfr
### Fixed
* #3163 Include correct version in production builds. @rauchy
* #3161 Clickhouse: fix int() conversion error. @denisov-vlad
* #3166 Directly using record_event task requires timestamp. @arikfr
* #3167 Alert.evaluate failing when the column is missing. @arikfr
* ##3162 Remove API permissions for users who have been disabled. @rauchy
* #3171 Reject empty query name. @ariarijp
* #3175, #3186 Fix disable error message. @rauchy, @gabrieldutra
* #3182 [Redshift] support for schema names with dots. @arikfr
* #3187 Safely create_app in Celery code (try to fetch current_app first). @arikfr
### Other
* #3155 Add DB Seed to Cypress and setup Percy. @gabrieldutra
* #3180 Remove coverage from pytest terminal output. @rauchy
## v6.0.0-beta - 2018-12-03
This release was 2 months in the making and it is full with good stuff!
* We have 5 new data sources: Databricks, IBM DB2, Kylin, Druid and Rockset. ⌗
* There are fixes and improvements to 11 existing data sources (MySQL, Redshift, Postgres, MongoDB, Google BigQuery, Vertica, TreasureData, Presto, ClickHouse, Google Sheets and Google Analytics).
* The Query Results data source can now load cached results, just use the `cached_query_` prefix instead of `query_`.
* On the visualizations front we added a Heatmap visualization and did updated the table and counter visualizations.
* Alerts got some fixes and a new destination: PagerDuty.
* If the live autocomplete in the code editor annoys you, you can disable it now (although we're working to make it better, see #3092).
* Fast queries will now load faster. 🏃‍♂️
* We improved the layout of visualizations and content on smaller screen sizes. 📱
* For those of you who like sharing, you can now enable the ability to share ownership of queries and dashboards and let others to edit them. Check the Settings page to enable this feature.
There were also important changes to the code and infrastructure:
* More components moved to React.
* We switched to Webpack 4 with the help of @dmonego.
* We upgraded to Celery 4 with the help of @emtwo, @jezdez, @mashrikt and @atharvai.
* We started moving towards Python 3 for our backend. The first step was to make sure our code pass basic sanity tests with Flake 8, which was implemented by @cclauss.
* We improved our testing on the frontend by adding setup for Jest tests and E2E testing using Cypress (@gabrieldutra).
* Each pull request now gets a deploy preview using Netlify to easily test frontend changes.
This is just a summary, you're welcome to review the full list below. ⬇
This release had contributions from 38 people: @arikfr, @kravets-levko, @jezdez, @kyoshidajp, @kocsmy, @alison985, @gabrieldutra, @washort, @GitSumito, @emtwo, @rauchy, @alexanderlz, @denisov-vlad, @ariarijp, @yoavbls, @zhujunsan, @sjakthol, @koooge, @SakuradaJun, @dmonego, @Udomomo, @cclauss, @combineads, @zaimy, @Trigl, @ralphilius, @jodevsa, @deecay, @igorcanadi, @pashaxp, @hoangphuoc25, @toph, @burnash, @wankdanker, @Yossi-a, @Rovel, @kadrach, and @nicof38. Thank you, everyone 🙏
### Added
* #2747, #3143 Add a new Databricks query runner. @alison985, @jezdez, @arikfr
* #2767 Add ability to add viz to dashboard from query edit page. @alison985, @jezdez
* #2780 Add a query autocomplete toggle. @alison985, @jezdez, @arikfr
* #2768 Add authentication via JWT providers. @SakuradaJun
* #2790 Add the ability to sort favorited queries, paginate the dashboard list and improve UI inconsistencies. @jezdez
* #2681 Add ability to search table column names in schema browser. @alison985
* #2855 Add option to query cached results. @yoavbls
* #2740 Add ability for extensions to add periodic tasks. @emtwo
* #2924 Google Spreadsheets: Add support for opening by URL. @alexanderlz
* #2903 Add PagerDuty as an Alert Destination. @alexanderlz
* #2824 Add support for expanding dashboard visualizations. @sjakthol
* #2900 Add ability to specify a counter label. @ralphilius
* #2565 Add frontend extension capabilities. @emtwo
* #2848 Add IBM Db2 as a data source using the ibm-db Python package. @nicof38
* #2959 Add option to auto reload widget data in shared dashboards. @arikfr
* #2993 Add page size settings. @kyoshidajp
* #2080 New Heatmap chart visualization with Plotly. @deecay
* #2991 Show users in CLI group list. @GitSumito
* #2342 New SQLPARSE_FORMAT_OPTIONS setting to configure query formatter. @ariarijp
* #3031 Add some tests for Query Results. @ariarijp
* #2936 Add Kylin data source. @Trigl
* #3047 Add Druid data source. @rauchy
* #3077 New user interface for the feature flag of the share edit permissions feature. @arikfr
* #3007 Add permissions to the result of "manage.py groups list" command. @Udomomo
* #3088 Add get_current_user() fuction for the Python query runner. @kyoshidajp
* #3114 Add event tracking to autocomplete toggle. @arikfr
* #3068 Add Rockset query runner. @igorcanadi, @arikfr
* #3105 Display frontend version. @rauchy
### Changed
* #2636 Rewrite query editor with React. @washort, @arikfr
* #2637 Convert edit-in-place component to React. @washort, @arikfr
* #2766 Suitable events are now being recorded server side instead of in the frontend. @alison985, @jezdez
* #2796 Change placement (right/bottom) of chart legend depending on chart width. @kravets-levko
* #2833 Uses server side sort order for tag list and show count of tagged items. @jezdez
* #2318 Support authentication for the URL data source. @jezdez
* #2884 Rename Yandex Metrika to Metrica. @jezdez
* #2909 MySQL: hide sys tables. @arikfr
* #2817 Consistently use simplejson for loading and dumping JSON. @jezdez
* #2872 Use Plotly's function to clean y-values (x may be category or date/time). @kravets-levko
* #2938 Auto focus tag input. @kyoshidajp
* #2927 Design refinements for queries pages. @kocsmy
* #2950 Show activity status in CLI user list. @GitSumito
* #2968 Presto data source: setting protocol (http/https), safe loading of error messages. @arikfr
* #2967 Show groups in CLI user list. @GitSumito
* #2603 MongoDB: Update requirements to support srv. @arikfr
* #2961 MongoDB: Skip system collections when loading schema. @arikfr
* #2960 Add timeout to various HTTP requests. @arikfr
* #2983 Databricks: New logo, updated name and enabled by default. @arikfr
* #2982 Table visualization: change default size to 25 and add more size options. @arikfr
* #2866 Redshift: Hide tables the configured user cannot access. @sjakthol
* #3058 Mustache: don't html-escape query parameters values. @kravets-levko
* #3079 Always use basic autocomplete, as well as the live autocomplete. @arikfr
* #3084 Support tel://, sms://, mailto:// links in query results. @zhujunsan
* #3083 Clickhouse: Add WITH TOTALS option support. @denisov-vlad
* #3063 Allow setting colors for bubble charts. @toph
* #3085 BigQuery: Switch to Standard SQL as the default. @kyoshidajp
* #3094 Tags autocomplete: Show note when creating a new label. @kravets-levko
* #2984 Autocomplete toggle improvements. @arikfr
* #3089 Open new tab when forking a query. @kyoshidajp
* #3126 MongoDB: add support for sorting columns. @arikfr
* #3128 Improve backoff algorithm of query results polling to speed it up. @arikfr
* #3125 Vertica: update driver & add support for connection timeout. @arikfr
* #3124 Support unicode in Postgres/Redshift schema. @arikfr
* #3138 Migrate all tags components to React. @kravets-levko
* #3139 Better manage permissions modal. @kocsmy
* #3149 Improve tag link colors and fix group tags on Users page. @kocsmy
* #3146 Update, replace and fix new alert destination logos so it fits better. @kocsmy
* #3147 Add and improve recent db logos that didn't fit in size properly. @kocsmy
* #3148 Fix label positioning on no found screen. @kocsmy
* #3156 json_dumps: add support for serializing buffer objects. @arikfr
### Fixed
* #2849 Fix invalid reference to alert.to_dict() in webhook. @wankdanker
* #2840 Improve counter visualization text scaling. @kravets-levko
* #2854 Widget titles are no longer rendered wrong on public dashboards. @kravets-levko
* #2318 Removed redundant exception handling in data sources since that's handled in the query backend. @jezdez
* #2886 Fix Javascript build that broke because registerAll tried to run EditInPlace component. @arikfr
* #2911 Dont show “Add to dashboard” in dropdown to unsaved queries. @jezdez
* #2916 Fix export query results output file name. @gabrieldutra
* #2917 Fix output file name not changing after rename query. @gabrieldutra
* #2868 Address edge case when retrieving Glue schemas for Athena data source. @kadrach
* #2929 Fix: date value in a filter is duplicated. @combineads
* #2875 Unbreak charts with long legend break in horizontal mode. Update plotly.js. @kravets-levko
* #2937 Fix event recording in admin API backend. @kyoshidajp
* #2953 Minor fixes for the Clickhouse data source. @denisov-vlad
* #2941 Bring back fix to Box plot hover. @arikfr
* #2957 Apply missing CSS classes to EditInPlace component. @arikfr
* #2897 Show "Add description" only after saving the query. @arikfr
* #2922 Query page layout improvements for small screens. @kravets-levko
* #2956 Clickhouse: move timeout to params. @denisov-vlad
* #2964 Fix no tags shown when having empty set. @gabrieldutra
* #2757 Use full text search ranking when searching in list views. @jezdez
* #2969 Query Results data source: improved errors, quoted column names. @arikfr
* #2906 Preventing open redirection in loging process. @kyoshidajp
* #2867 TreasureData: Deduplicate column names. @zaimy
* #2994 Fix scheme of various URLs from http to https. @kyoshidajp
* #2992 Fix an invalid prop type warning in new version notifier. @kyoshidajp
* #3022 Fix Toolbox covering part of a chart. @kravets-levko
* #2998 Fix charts losing responsive features after refreshing the dashboard. @kravets-levko
* #3034 Postgres: handle NaN/Infinity values. @kravets-levko
* #2745 Sort columns with undefined values. @Yossi-a
* #3041 Sort CLI output of lists. @GitSumito
* #2803, #3006 Address various tag display issues on query list page. @kocsmy, @alison985
* #3049 Fix edit-in-place component which ignored isEditable flag and didn't work on Groups page. @kravets-levko
* #2965 Google Analytics: Fix crash when no results are returned. @alexanderlz
* #3061 Fix table visualization so that the horizontal scrollbar is not be always visible. @kravets-levko
* #3076 Add white-space padding to separators in the footer. @burnash
* #2919 Fix URL data source to not require a URL. @arikfr
* #3098 Force AngularJS to update query editor properly. @washort
* #3100 Delete redundant regex segment in query result frontend. @zhujunsan
* #2978 Prevent the query update timestamp from changing when it is linked to new query results. @rauchy
* #3046 Fix query page header. @kravets-levko
* #3097 Mongo: Fix collection fields retreival bug when Views are present. @jodevsa
* #3107 Keep query text in local state for now. @washort
* #3111 Fix mobile padding issues on Query results. @kocsmy
* #3122 Show menu divider only if query is archived. @jezdez
* #3120 Fix tag counts for dashboards and queries. @jezdez
* #3141 Fix schema refresh to work on MySQL 8. @hoangphuoc25
* #3142 Fix: editing dashboard title results in the visualizations being replaced by the loading markers. @kravets-levko
### Other
* #2850 The setup scripts are now based on Ubuntu 18.04 LTS and Docker. @pashaxp, @arikfr
* #2985 Add Jest based tests to our stack. @arikfr
* #2999 Add netlify configuration. @arikfr
* #3000 Initial Cypress based E2E test infrastructure. @gabrieldutra
* #2898 Move Ant styles into a central location. @arikfr
* #2910 Fix webpack build error about BigMessage. @jezdez
* #2928 Speed up builds by skipping installing requirements_all_ds.txt in CI unit tests. @arikfr
* #2963 Fix tarball build failure. @emtwo
* #2996 Fix setup.sh failures when run as root. @arikfr
* #2989 Rearrange make targets. @koooge
* #3036 Update Flask-Admin to 1.5.2. @yoavbls
* #2901 Fix documentation links. @kravets-levko
* #3073 Remove only Redash containers in clean Make task. @ariarijp
* #3048 Remove pytest-watch dependency to workaround an issue with watchdog. @rauchy
* #2905 Update development docker-compose.yml file to use latest Redis and Postgres servers and specify working volume explictly. @Rovel
* #3032 Makefile: Add make targets for test. @koooge
* #2933 Switch to Webpack 4. @dmonego
* #2908 Update setup files. @arikfr
* #2946 Update snowflake_connector_python version. @arikfr
* #2773 Upgrade to Celery 4.2.1. @emtwo, @jezdez
* #2881 CircleCI: Make flake8 tests pass on Legacy Python and Python 3. @cclauss
* #2907 Remove unused dependencies (honcho, wsgiref). @arikfr
* #3039 Build docker image on master branch. @arikfr
* #3106 Fix registerAll failures after minification. @arikfr
## v5.0.2 - 2018-10-18
### Security
* Fix: prevent Open Redirect vulnerability.
## v5.0.1 - 2018-09-27
### Added
* Added support for JWT authentication (for services like Cloudflare Access or Google IAP).
### Changed
* Upgraded Celery version to 3.1.26 to make upgrade to Celery 4 easier.
## v5.0.0 - 2018-09-21
Final release for V5. Most of the changes were already in the beta release of V5, but this includes several fixes along

View File

@@ -9,7 +9,7 @@ The following is a set of guidelines for contributing to Redash. These are guide
- [Feature Roadmap](https://trello.com/b/b2LUHU7A/redash-roadmap)
- [Feature Requests](https://discuss.redash.io/c/feature-requests)
- [Documentation](https://redash.io/help/)
- [Blog](http://blog.redash.io/)
- [Blog](https://blog.redash.io/)
- [Twitter](https://twitter.com/getredash)
---
@@ -67,7 +67,7 @@ The project's documentation can be found at [https://redash.io/help/](https://re
### Release Method
We publish a stable release every ~2 months, although the goal is to get to a stable release every month. You can see the change log on [GitHub releases page](http://github.com/getredash/redash/releases).
We publish a stable release every ~2 months, although the goal is to get to a stable release every month. You can see the change log on [GitHub releases page](https://github.com/getredash/redash/releases).
Every build of the master branch updates the latest *RC release*. These releases are usually stable, but might contain regressions and therefore recommended for "advanced users" only.
@@ -75,4 +75,4 @@ When we release a new stable release, we also update the *latest* Docker image t
## Code of Conduct
This project adheres to the Contributor Covenant [code of conduct](http://redash.io/community/code_of_conduct). By participating, you are expected to uphold this code. Please report unacceptable behavior to team@redash.io.
This project adheres to the Contributor Covenant [code of conduct](https://redash.io/community/code_of_conduct). By participating, you are expected to uphold this code. Please report unacceptable behavior to team@redash.io.

View File

@@ -1,12 +1,16 @@
FROM redash/base:latest
# Controls whether to install extra dependencies needed for all data sources.
ARG skip_ds_deps
# We first copy only the requirements file, to avoid rebuilding on every file
# change.
COPY requirements.txt requirements_dev.txt requirements_all_ds.txt ./
RUN pip install -r requirements.txt -r requirements_dev.txt -r requirements_all_ds.txt
RUN pip install -r requirements.txt -r requirements_dev.txt
RUN if [ "x$skip_ds_deps" = "x" ] ; then pip install -r requirements_all_ds.txt ; else echo "Skipping pip install -r requirements_all_ds.txt" ; fi
COPY . ./
RUN npm install && npm run build && rm -rf node_modules
RUN npm install && npm run bundle && npm run build && rm -rf node_modules
RUN chown -R redash /app
USER redash

11
Dockerfile.cypress Normal file
View File

@@ -0,0 +1,11 @@
FROM cypress/browsers:chrome67
ENV APP /usr/src/app
WORKDIR $APP
RUN npm install --no-save cypress @percy/cypress > /dev/null
COPY cypress $APP/cypress
COPY cypress.json $APP/cypress.json
RUN ./node_modules/.bin/cypress verify

51
Makefile Normal file
View File

@@ -0,0 +1,51 @@
.PHONY: compose_build up test_db create_database clean down bundle tests lint backend-unit-tests frontend-unit-tests test build watch start
compose_build:
docker-compose build
up:
docker-compose up -d --build
test_db:
@for i in `seq 1 5`; do \
if (docker-compose exec postgres sh -c 'psql -U postgres -c "select 1;"' 2>&1 > /dev/null) then break; \
else echo "postgres initializing..."; sleep 5; fi \
done
docker-compose exec postgres sh -c 'psql -U postgres -c "drop database if exists tests;" && psql -U postgres -c "create database tests;"'
create_database:
docker-compose run server create_db
clean:
docker-compose down && docker-compose rm
down:
docker-compose down
bundle:
docker-compose run server bin/bundle-extensions
tests:
docker-compose run server tests
lint:
./bin/flake8_tests.sh
backend-unit-tests: up test_db
docker-compose run --rm --name tests server tests
frontend-unit-tests: bundle
npm install
npm run bundle
npm test
test: lint backend-unit-tests frontend-unit-tests
build: bundle
npm run build
watch: bundle
npm run watch
start: bundle
npm run start

View File

@@ -16,7 +16,7 @@ Today **_Redash_** has support for querying multiple databases, including: Redsh
**_Redash_** consists of two parts:
1. **Query Editor**: think of [JS Fiddle](http://jsfiddle.net) for SQL queries. It's your way to share data in the organization in an open way, by sharing both the dataset and the query that generated it. This way everyone can peer review not only the resulting dataset but also the process that generated it. Also it's possible to fork it and generate new datasets and reach new insights.
1. **Query Editor**: think of [JS Fiddle](https://jsfiddle.net) for SQL queries. It's your way to share data in the organization in an open way, by sharing both the dataset and the query that generated it. This way everyone can peer review not only the resulting dataset but also the process that generated it. Also it's possible to fork it and generate new datasets and reach new insights.
2. **Visualizations and Dashboards**: once you have a dataset, you can create different visualizations out of it, and then combine several visualizations into a single dashboard. Currently Redash supports charts, pivot table, cohorts and [more](https://redash.io/help/user-guide/visualizations/visualization-types).
<img src="https://raw.githubusercontent.com/getredash/website/8e820cd02c73a8ddf4f946a9d293c54fd3fb08b9/website/_assets/images/redash-anim.gif" width="80%"/>
@@ -28,13 +28,12 @@ Today **_Redash_** has support for querying multiple databases, including: Redsh
## Supported Data Sources
Redash supports more than 25 [data sources](https://redash.io/help/data-sources/supported-data-sources).
Redash supports more than 35 [data sources](https://redash.io/help/data-sources/supported-data-sources).
## Getting Help
* Issues: https://github.com/getredash/redash/issues
* Discussion Forum: https://discuss.redash.io/
* Slack: http://slack.redash.io/
## Reporting Bugs and Contributing Code

39
bin/bundle-extensions Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python
import os
from subprocess import call
from distutils.dir_util import copy_tree
from pkg_resources import iter_entry_points, resource_filename, resource_isdir
# Make a directory for extensions and set it as an environment variable
# to be picked up by webpack.
EXTENSIONS_RELATIVE_PATH = os.path.join('client', 'app', 'extensions')
EXTENSIONS_DIRECTORY = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
EXTENSIONS_RELATIVE_PATH)
if not os.path.exists(EXTENSIONS_DIRECTORY):
os.makedirs(EXTENSIONS_DIRECTORY)
os.environ["EXTENSIONS_DIRECTORY"] = EXTENSIONS_RELATIVE_PATH
for entry_point in iter_entry_points('redash.extensions'):
# This is where the frontend code for an extension lives
# inside of its package.
content_folder_relative = os.path.join(
entry_point.name, 'bundle')
(root_module, _) = os.path.splitext(entry_point.module_name)
if not resource_isdir(root_module, content_folder_relative):
continue
content_folder = resource_filename(root_module, content_folder_relative)
# This is where we place our extensions folder.
destination = os.path.join(
EXTENSIONS_DIRECTORY,
entry_point.name)
copy_tree(content_folder, destination)

7
bin/flake8_tests.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
flake8 --version ; pip --version
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

View File

@@ -1,4 +1,5 @@
#!/bin/env python
from __future__ import print_function
import sys
import re
import subprocess
@@ -32,4 +33,4 @@ if __name__ == '__main__':
changes = get_change_log(previous_sha)
for change in changes:
print change
print(change)

View File

@@ -1,10 +1,10 @@
from __future__ import print_function
import os
import sys
import json
import re
import subprocess
import requests
import simplejson
github_token = os.environ['GITHUB_TOKEN']
auth = (github_token, 'x-oauth-basic')
@@ -17,7 +17,7 @@ def _github_request(method, path, params=None, headers={}):
url = path
if params is not None:
params = json.dumps(params)
params = simplejson.dumps(params)
response = requests.request(method, url, data=params, auth=auth)
return response

View File

@@ -1,11 +1,14 @@
module.exports = {
root: true,
extends: "airbnb",
extends: ["airbnb", "plugin:jest/recommended"],
plugins: ["jest", "cypress"],
settings: {
"import/resolver": "webpack"
},
parser: "babel-eslint",
env: {
"jest/globals": true,
"cypress/globals": true,
"browser": true,
"node": true
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -40,3 +40,10 @@
vertical-align: top;
margin-left: 0;
}
// Hide URLs next to links when printing (override `bootstrap` rules)
@media print {
a[href]:after {
content: none !important;
}
}

View File

@@ -15,15 +15,6 @@
border-radius: @redash-radius;
}
.edit-in-place input,
.edit-in-place textarea {
display: none;
}
.edit-in-place.active span {
display: none;
}
.edit-in-place.active input,
.edit-in-place.active textarea {
display: inline-block;

View File

@@ -17,6 +17,24 @@
}
}
tags-list {
a {
line-height: 1.1;
}
}
.tags-list__name {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
width: 88%;
line-height: 1.3;
}
.max-character {
.text-overflow();
}
.list-group-item {
&.active {
button {

View File

@@ -3,52 +3,43 @@ counter-renderer {
text-align: center;
padding: 15px 10px;
overflow: hidden;
}
counter-renderer counter {
counter {
margin: 0;
padding: 0;
display: block;
font-size: 80px;
line-height: normal;
overflow: hidden;
height: 200px;
}
display: flex;
align-items: center;
justify-content: center;
counter-renderer value,
counter-renderer counter-name,
counter-renderer counter-target {
value,
counter-target {
font-size: 1em;
line-height: 1em;
display: block;
}
counter-renderer value .ruler,
counter-renderer counter-name .ruler,
counter-renderer counter-target .ruler {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font: inherit;
line-height: inherit;
margin: 0;
padding: 0;
}
counter-renderer counter-target {
color: #ccc;
}
counter-renderer counter.positive value {
color: #5cb85c;
}
counter-renderer counter.negative value {
color: #d9534f;
margin-right: 15px;
}
counter-renderer counter-name {
counter-name {
font-size: 0.5em;
display: block;
}
&.positive value {
color: #5cb85c;
}
&.negative value {
color: #d9534f;
}
}
counter-target {
color: #ccc;
}
counter-name {
font-size: 0.5em;
display: block;
}
}

View File

@@ -1,6 +1,3 @@
@import '~antd/lib/style/core/iconfont.less';
@import '~antd/lib/input/style/index.less';
@import '~antd/lib/date-picker/style/index.less';
@import 'redash/ant';
/** LESS Plugins **/
@@ -14,8 +11,8 @@
@import '~ui-select/dist/select.css';
@import '~angular-toastr/src/toastr';
@import '~angular-resizable/src/angular-resizable.css';
@import '~pace-progress/themes/blue/pace-theme-minimal.css';
@import '~material-design-iconic-font/dist/css/material-design-iconic-font.css';
@import '~pace-progress/themes/blue/pace-theme-minimal.css';
@import 'inc/angular';
@import 'inc/variables';
@@ -81,6 +78,7 @@
@import 'redash/redash-newstyle';
@import 'redash/redash-table';
@import 'redash/query';
@import 'redash/tags-control';

View File

@@ -1,3 +1,10 @@
@import '~antd/lib/style/core/iconfont.less';
@import '~antd/lib/style/core/motion.less';
@import '~antd/lib/input/style/index.less';
@import '~antd/lib/date-picker/style/index.less';
@import '~antd/lib/tooltip/style/index.less';
@import '~antd/lib/select/style/index.less';
// Overwritting Ant Design defaults to fit into Redash current style
@font-family-no-number : @redash-font;
@font-family : @redash-font;
@@ -7,3 +14,25 @@
@border-color-base : #e8e8e8;
@primary-color : @blue;
// Fix for disabled button styles inside Tooltip component.
// Tooltip wraps disabled buttons with `<span>` and moves all styles
// and classes to that `<span>`. This resets all button styles and
// turns it into simple inline element (because now it's wrapper is a button)
.btn {
button[disabled] {
-moz-appearance: none !important;
-webkit-appearance: none !important;
appearance: none !important;
border: 0 !important;
outline: none !important;
background: transparent !important;
margin: 0 !important;
padding: 0 !important;
}
}
// Fix for Ant dropdowns when they are used in Boootstrap modals
.ant-dropdown-in-bootstrap-modal {
z-index: 1050;
}

View File

@@ -95,10 +95,6 @@ edit-in-place p.editable:hover {
margin-bottom: 10px;
}
.source-control {
}
.ace_editor.ace_autocomplete .ace_completion-highlight {
text-shadow: none !important;
background: #ffff005e;
@@ -225,26 +221,29 @@ edit-in-place p.editable:hover {
}
.page-header--new {
.label-default {
.query-tags,
.query-tags__mobile {
.label-default,
.label-warning {
margin-right: 3px;
}
}
}
.page-header--query {
display: flex;
align-items: center;
h3 {
display: inline-block;
.page-title {
display: block;
margin-left: 15px;
margin-right: 15px;
}
}
a.label-tag {
background: fade(@redash-gray, 15%);
color: #333;
color: darken(@redash-gray, 15%);
&:hover {
color: #333;
color: darken(@redash-gray, 15%);
background: fade(@redash-gray, 25%);
}
}
@@ -527,9 +526,59 @@ nav .rg-bottom {
}
}
.query-tags {
display: inline-block;
vertical-align: middle;
margin-top: -3px; // padding-top of tags
}
.query-tags__mobile {
display: none;
margin: -5px 0 0 0;
padding: 0 0 0 23px;
}
.table--permission {
.profile__image {
margin-right: 0;
}
}
.mp__permission-type {
text-transform: capitalize;
}
// Smaller screens
@media (max-width: 880px) {
.page-header--query {
.page-title {
margin-left: 0;
margin-right: 0;
}
}
.query-tags:not(.query-tags__empty) {
display: none;
}
.query-tags__mobile:not(.query-tags__empty) {
display: block;
}
.btn--showhide,
.query-actions-menu .dropdown-toggle {
margin-bottom: 5px;
}
.btn-publish {
display: none;
}
.tab-nav .tab-new-vis {
display: none;
}
.query-fullscreen {
flex-direction: column;
overflow: hidden;
@@ -541,45 +590,26 @@ nav .rg-bottom {
.schema-container {
display: none;
}
}
.query-page-wrapper {
.container {
margin-left: 0;
margin-right: 0;
}
}
.query-fullscreen .query-metadata__mobile {
display: block;
.query-metadata__mobile {
border-bottom: 1px solid #efefef;
padding: 10px 0;
min-height: 0 !important;
flex-shrink: 0;
padding: 10px 15px;
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
align-items: center;
.profile__image_thumb {
margin: 0 5px 0 0;
}
a.navbar-brand {
display: none;
}
.page-header--query {
padding-left: 0px !important;
h3 {
line-height: 1.25;
.query-metadata__property {
white-space: nowrap;
}
}
.query-fullscreen .content {
height: 100%;
}
.datasource-small {
visibility: visible;
}
.query-fullscreen {
main {
flex-direction: column-reverse;
@@ -600,18 +630,31 @@ nav .rg-bottom {
.content {
width: 100%;
}
height: 100%;
.static-position__mobile {
position: static !important;
}
}
@media (max-width: 438px) {
.btn--showhide {
margin-bottom: 5px;
.bottom-controller-container {
z-index: 9;
}
}
.btn-publish {
margin-bottom: 5px;
.query-page-wrapper {
.container {
margin-left: 0;
margin-right: 0;
}
}
a.navbar-brand {
display: none;
}
.datasource-small {
visibility: visible;
}
}
@@ -629,6 +672,6 @@ nav .rg-bottom {
}
.btn-edit-visualisation {
display: none;
}
}

View File

@@ -1,3 +1,5 @@
@import (reference, less) '~bootstrap/less/labels.less';
// Variables
@redash-gray: rgba(102, 136, 153, 1);
@redash-orange: rgba(255, 120, 100, 1);
@@ -366,8 +368,8 @@ body {
page-header, .page-header--new {
h3 {
margin: 0;
line-height: 1.75;
margin: 0.2em 0;
line-height: 1.3;
font-weight: 500;
}
}
@@ -449,10 +451,27 @@ page-header, .page-header--new {
background: fade(@redash-gray, 85%);
}
.label-tag-unpublished {
background: fade(@redash-gray, 85%);
}
.label-tag-archived {
.label-warning();
}
.label-tag {
background: fade(@redash-gray, 10%);
color: fade(@redash-gray, 75%);
}
.label-tag-unpublished,
.label-tag-archived,
.label-tag {
margin-right: 3px;
display: inline-block;
margin-top: 2px;
max-width: 24ch;
.text-overflow();
}
.tab-nav > li > a {
@@ -732,6 +751,14 @@ page-header, .page-header--new {
margin-top: 2px;
}
.tags-list {
.badge-light {
background: fade(@redash-gray, 10%);
color: fade(@redash-gray, 75%);
}
}
.dropdown-menu--profile {
li {
width: 200px;
@@ -805,10 +832,6 @@ text.slicetext {
}
.query-page-wrapper {
.page-header--query {
padding-bottom: 5px !important;
}
h3 {
font-size: 18px;
}
@@ -860,6 +883,16 @@ text.slicetext {
}
}
@media (min-width: 768px) and (max-width: 850px) {
.menu-search {
width: 175px;
}
a.navbar-brand {
display: none !important;
}
}
@media (max-width: 1084px) {
.dropdown--profile__username {
display: none;

View File

@@ -0,0 +1,13 @@
.tags-control {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: stretch;
justify-content: flex-start;
line-height: 1em;
&.inline-tags-control {
display: inline-block;
vertical-align: middle;
}
}

View File

@@ -0,0 +1,43 @@
import React from 'react';
import Tooltip from 'antd/lib/tooltip';
import PropTypes from 'prop-types';
import '@/redash-font/style.less';
import recordEvent from '@/lib/recordEvent';
export default function AutocompleteToggle({ state, disabled, onToggle }) {
let tooltipMessage = 'Live Autocomplete Enabled';
let icon = 'icon-flash';
if (!state) {
tooltipMessage = 'Live Autocomplete Disabled';
icon = 'icon-flash-off';
}
if (disabled) {
tooltipMessage = 'Live Autocomplete Not Available (Use Ctrl+Space to Trigger)';
icon = 'icon-flash-off';
}
const toggle = (newState) => {
recordEvent('toggle_autocomplete', 'screen', 'query_editor', { state: newState });
onToggle(newState);
};
return (
<Tooltip placement="top" title={tooltipMessage}>
<button
type="button"
className={'btn btn-default m-r-5' + (disabled ? ' disabled' : '')}
onClick={() => toggle(!state)}
disabled={disabled}
>
<i className={'icon ' + icon} />
</button>
</Tooltip>
);
}
AutocompleteToggle.propTypes = {
state: PropTypes.bool.isRequired,
disabled: PropTypes.bool.isRequired,
onToggle: PropTypes.func.isRequired,
};

View File

@@ -29,3 +29,5 @@ BigMessage.defaultProps = {
export default function init(ngModule) {
ngModule.component('bigMessage', react2angular(BigMessage));
}
init.init = true;

View File

@@ -45,3 +45,4 @@ export default function init(ngModule) {
ngModule.component('dateInput', react2angular(DateInput, null, ['clientConfig']));
}
init.init = true;

View File

@@ -4,9 +4,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
import { RangePicker } from 'antd/lib/date-picker';
import 'antd/lib/style/core/iconfont.less';
import 'antd/lib/input/style/index.less';
import 'antd/lib/date-picker/style/index.less';
function DateRangeInput({
value,
@@ -53,3 +50,4 @@ export default function init(ngModule) {
ngModule.component('dateRangeInput', react2angular(DateRangeInput, null, ['clientConfig']));
}
init.init = true;

View File

@@ -50,3 +50,4 @@ export default function init(ngModule) {
ngModule.component('dateTimeInput', react2angular(DateTimeInput, null, ['clientConfig']));
}
init.init = true;

View File

@@ -4,9 +4,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
import { RangePicker } from 'antd/lib/date-picker';
import 'antd/lib/style/core/iconfont.less';
import 'antd/lib/input/style/index.less';
import 'antd/lib/date-picker/style/index.less';
function DateTimeRangeInput({
value,
@@ -58,3 +55,5 @@ export default function init(ngModule) {
ngModule.component('dateTimeRangeInput', react2angular(DateTimeRangeInput, null, ['clientConfig']));
}
init.init = true;

View File

@@ -0,0 +1,92 @@
import React from 'react';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
export class EditInPlace extends React.Component {
static propTypes = {
ignoreBlanks: PropTypes.bool,
isEditable: PropTypes.bool,
editor: PropTypes.string.isRequired,
placeholder: PropTypes.string,
value: PropTypes.string,
onDone: PropTypes.func.isRequired,
};
static defaultProps = {
ignoreBlanks: false,
isEditable: true,
placeholder: '',
value: '',
};
constructor(props) {
super(props);
this.state = {
editing: false,
};
this.inputRef = React.createRef();
const self = this;
this.componentDidUpdate = (prevProps, prevState) => {
if (self.state.editing && !prevState.editing) {
self.inputRef.current.focus();
}
};
}
startEditing = () => {
if (this.props.isEditable) {
this.setState({ editing: true });
}
};
stopEditing = () => {
const newValue = this.inputRef.current.value;
const ignorableBlank = this.props.ignoreBlanks && newValue === '';
if (!ignorableBlank && newValue !== this.props.value) {
this.props.onDone(newValue);
}
this.setState({ editing: false });
};
keyDown = (event) => {
if (event.keyCode === 13 && !event.shiftKey) {
event.preventDefault();
this.stopEditing();
} else if (event.keyCode === 27) {
this.setState({ editing: false });
}
};
renderNormal = () => (
<span
role="presentation"
onFocus={this.startEditing}
onClick={this.startEditing}
className={this.props.isEditable ? 'editable' : ''}
>
{this.props.value || this.props.placeholder}
</span>
);
renderEdit = () =>
React.createElement(this.props.editor, {
ref: this.inputRef,
className: 'rd-form-control',
defaultValue: this.props.value,
onBlur: this.stopEditing,
onKeyDown: this.keyDown,
});
render() {
return (
<span className={'edit-in-place' + (this.state.editing ? ' active' : '')}>
{this.state.editing ? this.renderEdit() : this.renderNormal()}
</span>
);
}
}
export default function init(ngModule) {
ngModule.component('editInPlace', react2angular(EditInPlace));
}
init.init = true;

View File

@@ -3,9 +3,12 @@ import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
function Footer({ clientConfig, currentUser }) {
const version = clientConfig.version;
import frontendVersion from '../version.json';
export function Footer({ clientConfig, currentUser }) {
const backendVersion = clientConfig.version;
const newVersionAvailable = clientConfig.newVersionAvailable && currentUser.isAdmin;
const separator = ' \u2022 ';
let newVersionString = '';
if (newVersionAvailable) {
@@ -18,12 +21,12 @@ function Footer({ clientConfig, currentUser }) {
return (
<div id="footer">
<a href="http://redash.io">Redash</a> {version}
<a href="https://redash.io">Redash</a> {backendVersion} ({frontendVersion.substring(0, 8)})
{newVersionString}
&#8226;
{separator}
<a href="https://redash.io/help/">Documentation</a>
&#8226;
<a href="http://github.com/getredash/redash">Contribute</a>
{separator}
<a href="https://github.com/getredash/redash">Contribute</a>
</div>
);
}
@@ -41,3 +44,5 @@ Footer.propTypes = {
export default function init(ngModule) {
ngModule.component('footer', react2angular(Footer, [], ['clientConfig', 'currentUser']));
}
init.init = true;

View File

@@ -0,0 +1,16 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { Footer } from './Footer';
test('Footer renders', () => {
const clientConfig = {
version: '5.0.1',
newVersionAvailable: true,
};
const currentUser = {
isAdmin: true,
};
const component = renderer.create(<Footer clientConfig={clientConfig} currentUser={currentUser} />);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});

View File

@@ -1,17 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { react2angular } from 'react2angular';
import { BigMessage } from './BigMessage.jsx';
import { BigMessage } from '@/components/BigMessage';
import TagsControl from '@/components/tags-control/TagsControl';
function NoTaggedObjectsFound({ objectType, tags }) {
return (
<BigMessage icon="fa-tags">
No {objectType} found tagged with
{Array.from(tags).map(tag => (
<span className="label label-tag" key={tag}>
{tag}
</span>
))}.
No {objectType} found tagged with&nbsp;<TagsControl className="inline-tags-control" tags={Array.from(tags)} />.
</BigMessage>
);
}
@@ -24,3 +20,5 @@ NoTaggedObjectsFound.propTypes = {
export default function init(ngModule) {
ngModule.component('noTaggedObjectsFound', react2angular(NoTaggedObjectsFound));
}
init.init = true;

View File

@@ -0,0 +1,283 @@
import React from 'react';
import PropTypes from 'prop-types';
import { map } from 'lodash';
import Tooltip from 'antd/lib/tooltip';
import { react2angular } from 'react2angular';
import AceEditor from 'react-ace';
import ace from 'brace';
import toastr from 'angular-toastr';
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'brace/mode/python';
import 'brace/mode/sql';
import 'brace/theme/textmate';
import 'brace/ext/searchbox';
import localOptions from '@/lib/localOptions';
import AutocompleteToggle from '@/components/AutocompleteToggle';
import { DataSource, Schema } from './proptypes';
const langTools = ace.acequire('ace/ext/language_tools');
const snippetsModule = ace.acequire('ace/snippets');
// By default Ace will try to load snippet files for the different modes and fail.
// We don't need them, so we use these placeholders until we define our own.
function defineDummySnippets(mode) {
ace.define(`ace/snippets/${mode}`, ['require', 'exports', 'module'], (require, exports) => {
exports.snippetText = '';
exports.scope = mode;
});
}
defineDummySnippets('python');
defineDummySnippets('sql');
defineDummySnippets('json');
function buildKeywordsFromSchema(schema) {
const keywords = {};
schema.forEach((table) => {
keywords[table.name] = 'Table';
table.columns.forEach((c) => {
keywords[c] = 'Column';
keywords[`${table.name}.${c}`] = 'Column';
});
});
return map(keywords, (v, k) => ({
name: k,
value: k,
score: 0,
meta: v,
}));
}
class QueryEditor extends React.Component {
static propTypes = {
queryText: PropTypes.string.isRequired,
schema: Schema, // eslint-disable-line react/no-unused-prop-types
addNewParameter: PropTypes.func.isRequired,
dataSources: PropTypes.arrayOf(DataSource),
dataSource: DataSource,
canEdit: PropTypes.bool.isRequired,
isDirty: PropTypes.bool.isRequired,
isQueryOwner: PropTypes.bool.isRequired,
updateDataSource: PropTypes.func.isRequired,
canExecuteQuery: PropTypes.func.isRequired,
executeQuery: PropTypes.func.isRequired,
queryExecuting: PropTypes.bool.isRequired,
saveQuery: PropTypes.func.isRequired,
updateQuery: PropTypes.func.isRequired,
listenForResize: PropTypes.func.isRequired,
listenForEditorCommand: PropTypes.func.isRequired,
};
static defaultProps = {
schema: null,
dataSource: {},
dataSources: [],
};
constructor(props) {
super(props);
this.state = {
schema: null, // eslint-disable-line react/no-unused-state
keywords: [], // eslint-disable-line react/no-unused-state
autocompleteQuery: localOptions.get('liveAutocomplete', true),
liveAutocompleteDisabled: false,
// XXX temporary while interfacing with angular
queryText: props.queryText,
};
langTools.addCompleter({
getCompletions: (state, session, pos, prefix, callback) => {
if (prefix.length === 0) {
callback(null, []);
return;
}
callback(null, this.state.keywords);
},
});
this.onLoad = (editor) => {
// Release Cmd/Ctrl+L to the browser
editor.commands.bindKey('Cmd+L', null);
editor.commands.bindKey('Ctrl+P', null);
editor.commands.bindKey('Ctrl+L', null);
// eslint-disable-next-line react/prop-types
this.props.QuerySnippet.query((snippets) => {
const snippetManager = snippetsModule.snippetManager;
const m = {
snippetText: '',
};
m.snippets = snippetManager.parseSnippetFile(m.snippetText);
snippets.forEach((snippet) => {
m.snippets.push(snippet.getSnippet());
});
snippetManager.register(m.snippets || [], m.scope);
});
editor.focus();
this.props.listenForResize(() => editor.resize());
this.props.listenForEditorCommand((e, command, ...args) => {
switch (command) {
case 'focus': {
editor.focus();
break;
}
case 'paste': {
const [text] = args;
editor.session.doc.replace(editor.selection.getRange(), text);
const range = editor.selection.getRange();
this.props.updateQuery(editor.session.getValue());
editor.selection.setRange(range);
break;
}
default:
break;
}
});
};
this.formatQuery = () => {
// eslint-disable-next-line react/prop-types
const format = this.props.Query.format;
format(this.props.dataSource.syntax || 'sql', this.props.queryText)
.then(this.updateQuery)
.catch(error => toastr.error(error));
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if (!nextProps.schema) {
return { keywords: [], liveAutocompleteDisabled: false };
} else if (nextProps.schema !== prevState.schema) {
const tokensCount = nextProps.schema.reduce((totalLength, table) => totalLength + table.columns.length, 0);
return {
schema: nextProps.schema,
keywords: buildKeywordsFromSchema(nextProps.schema),
liveAutocompleteDisabled: tokensCount > 5000,
};
}
return null;
}
updateQuery = (queryText) => {
this.props.updateQuery(queryText);
this.setState({ queryText });
};
toggleAutocomplete = (state) => {
this.setState({ autocompleteQuery: state });
localOptions.set('liveAutocomplete', state);
}
render() {
// eslint-disable-next-line react/prop-types
const modKey = this.props.KeyboardShortcuts.modKey;
const isExecuteDisabled = this.props.queryExecuting || !this.props.canExecuteQuery();
return (
<section style={{ height: '100%' }} data-test="QueryEditor">
<div className="container p-15 m-b-10" style={{ height: '100%' }}>
<div style={{ height: 'calc(100% - 40px)', marginBottom: '0px' }} className="editor__container">
<AceEditor
ref={this.refEditor}
theme="textmate"
mode={this.props.dataSource.syntax || 'sql'}
value={this.state.queryText}
editorProps={{ $blockScrolling: Infinity }}
width="100%"
height="100%"
setOptions={{
behavioursEnabled: true,
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: !this.state.liveAutocompleteDisabled && this.state.autocompleteQuery,
autoScrollEditorIntoView: true,
}}
showPrintMargin={false}
wrapEnabled={false}
onLoad={this.onLoad}
onPaste={this.onPaste}
onChange={this.updateQuery}
/>
</div>
<div className="editor__control">
<div className="form-inline d-flex">
<Tooltip
placement="top"
title={
<span>
Add New Parameter (<i>{modKey} + P</i>)
</span>
}
>
<button type="button" className="btn btn-default m-r-5" onClick={this.props.addNewParameter}>
&#123;&#123;&nbsp;&#125;&#125;
</button>
</Tooltip>
<Tooltip placement="top" title="Format Query">
<button type="button" className="btn btn-default m-r-5" onClick={this.formatQuery}>
<span className="zmdi zmdi-format-indent-increase" />
</button>
</Tooltip>
<AutocompleteToggle
state={this.state.autocompleteQuery}
onToggle={this.toggleAutocomplete}
disabled={this.state.liveAutocompleteDisabled}
/>
<select
className="form-control datasource-small flex-fill w-100"
onChange={this.props.updateDataSource}
disabled={!this.props.isQueryOwner}
>
{this.props.dataSources.map(ds => (
<option label={ds.name} value={ds.id} key={`ds-option-${ds.id}`}>
{ds.name}
</option>
))}
</select>
{this.props.canEdit ? (
<Tooltip placement="top" title={modKey + ' + S'}>
<button className="btn btn-default m-l-5" onClick={this.props.saveQuery} title="Save">
<span className="fa fa-floppy-o" />
<span className="hidden-xs m-l-5">Save</span>
{this.props.isDirty ? '*' : null}
</button>
</Tooltip>
) : null}
<Tooltip placement="top" title={modKey + ' + Enter'}>
{/*
Tooltip wraps disabled buttons with `<span>` and moves all styles
and classes to that `<span>`. There is a piece of CSS that fixes
button appearance, but also wwe need to add `disabled` class to
disabled buttons so it will be assigned to wrapper and make it
looking properly
*/}
<button
type="button"
className={'btn btn-primary m-l-5' + (isExecuteDisabled ? ' disabled' : '')}
disabled={isExecuteDisabled}
onClick={this.props.executeQuery}
data-test="ExecuteButton"
>
<span className="zmdi zmdi-play" />
<span className="hidden-xs m-l-5">Execute</span>
</button>
</Tooltip>
</div>
</div>
</div>
</section>
);
}
}
export default function init(ngModule) {
ngModule.component('queryEditor', react2angular(QueryEditor, null, ['QuerySnippet', 'Query', 'KeyboardShortcuts']));
}
init.init = true;

View File

@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Footer renders 1`] = `
<div
id="footer"
>
<a
href="https://redash.io"
>
Redash
</a>
5.0.1
(
dev
)
<small>
<a
href="https://version.redash.io/"
>
(New Redash version available)
</a>
</small>
<a
href="https://redash.io/help/"
>
Documentation
</a>
<a
href="https://github.com/getredash/redash"
>
Contribute
</a>
</div>
`;

View File

@@ -111,3 +111,6 @@ export default function init(ngModule) {
controller,
}));
}
init.init = true;

View File

@@ -86,7 +86,7 @@
<!--<a href="users" title="Settings"><i class="fa fa-cog"></i></a>-->
<!--</li>-->
<li class="dropdown" uib-dropdown>
<a href="#" class="dropdown-toggle dropdown--profile" uib-dropdown-toggle>
<a href="#" class="dropdown-toggle dropdown--profile" uib-dropdown-toggle data-test="ProfileDropdown">
<img ng-src="{{ $ctrl.currentUser.profile_image_url }}" class="profile__image--navbar" width="20"/>
<span class="dropdown--profile__username" ng-bind="$ctrl.currentUser.name"></span> <span
class="caret caret--nav"></span></a>
@@ -128,7 +128,7 @@
<!-- Search -->
<form class="navbar-form navbar-right" role="search" ng-submit="$ctrl.searchQueries()">
<div class="input-group menu-search">
<input type="text" ng-model="$ctrl.term" class="form-control navbar__search__input" placeholder="Search queries...">
<input type="text" ng-model="$ctrl.searchTerm" class="form-control navbar__search__input" placeholder="Search queries...">
<span class="input-group-btn">
<button type="submit" class="btn btn-default"><span class="zmdi zmdi-search"></span></button>
</span>

View File

@@ -40,7 +40,7 @@ function controller($rootScope, $location, $route, $uibModal, Auth, currentUser,
};
this.searchQueries = () => {
$location.path('/queries').search({ q: this.term });
$location.path('/queries').search({ q: this.searchTerm });
$route.reload();
};
@@ -55,3 +55,5 @@ export default function init(ngModule) {
controller,
});
}
init.init = true;

View File

@@ -82,12 +82,18 @@ class AppViewComponent {
}
export default function init(ngModule) {
ngModule.factory('$exceptionHandler', () => function exceptionHandler(exception) {
ngModule.factory(
'$exceptionHandler',
() =>
function exceptionHandler(exception) {
handler.process(exception);
});
},
);
ngModule.component('appView', {
template,
controller: AppViewComponent,
});
}
init.init = true;

View File

@@ -6,14 +6,14 @@ function cancelQueryButton() {
taskId: '=',
},
transclude: true,
template: '<button class="btn btn-default" ng-disabled="inProgress" ng-click="cancelExecution()"><i class="zmdi zmdi-spinner zmdi-hc-spin" ng-if="inProgress"></i> Cancel</button>',
template:
'<button class="btn btn-default" ng-disabled="inProgress" ng-click="cancelExecution()"><i class="zmdi zmdi-spinner zmdi-hc-spin" ng-if="inProgress"></i> Cancel</button>',
replace: true,
controller($scope, $http, currentUser, Events) {
$scope.inProgress = false;
$scope.cancelExecution = () => {
$http.delete(`api/jobs/${$scope.taskId}`).success(() => {
});
$http.delete(`api/jobs/${$scope.taskId}`).success(() => {});
let queryId = $scope.queryId;
if ($scope.queryId === 'adhoc') {
@@ -30,3 +30,5 @@ function cancelQueryButton() {
export default function init(ngModule) {
ngModule.directive('cancelQueryButton', cancelQueryButton);
}
init.init = true;

View File

@@ -114,3 +114,5 @@ const AddWidgetDialog = {
export default function init(ngModule) {
ngModule.component('addWidgetDialog', AddWidgetDialog);
}
init.init = true;

View File

@@ -42,3 +42,5 @@ const EditDashboardDialog = {
export default function init(ngModule) {
ngModule.component('editDashboardDialog', EditDashboardDialog);
}
init.init = true;

View File

@@ -459,3 +459,5 @@ export default function init(ngModule) {
ngModule.directive('gridstack', gridstack);
ngModule.directive('gridstackItem', gridstackItem);
}
init.init = true;

View File

@@ -0,0 +1,12 @@
<div class="modal-header">
<button type="button" class="close" aria-hidden="true" ng-click="$ctrl.dismiss()">&times;</button>
<div class="visualization-title">
<query-link query="$ctrl.widget.getQuery()" visualization="$ctrl.widget.visualization" readonly="true"></query-link>
</div>
</div>
<div class="modal-body">
<visualization-renderer visualization="$ctrl.widget.visualization" query-result="$ctrl.widget.getQueryResult()" class="t-body"></visualization-renderer>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="$ctrl.dismiss()">Close</button>
</div>

View File

@@ -0,0 +1,8 @@
.visualization-title {
font-weight: 500;
font-size: 15px;
}
body.modal-open .dropdown.open {
z-index: 10000;
}

View File

@@ -21,13 +21,9 @@
</ul>
</div>
<div class="th-title">
<p class="hidden-print">
<span ng-hide="$ctrl.canViewQuery">{{$ctrl.widget.getQuery().name}}</span>
<query-link query="$ctrl.widget.getQuery()" visualization="$ctrl.widget.visualization" ng-show="$ctrl.canViewQuery"></query-link>
</p>
<p class="visible-print">
<span>{{$ctrl.widget.getQuery().name}}</span>
<visualization-name visualization="$ctrl.widget.visualization"/>
<p>
<query-link query="$ctrl.widget.getQuery()" visualization="$ctrl.widget.visualization"
readonly="!$ctrl.canViewQuery"></query-link>
</p>
<div class="text-muted query--description" ng-bind-html="$ctrl.widget.getQuery().description | markdown"></div>
</div>
@@ -63,6 +59,7 @@
</span>
<button class="btn btn-sm btn-default pull-right hidden-print btn-transparent btn__refresh" ng-click="$ctrl.refresh()" ng-if="!$ctrl.public"><i class="zmdi zmdi-refresh"></i></button>
<button class="btn btn-sm btn-default pull-right hidden-print btn-transparent btn__refresh" ng-click="$ctrl.expandVisualization()"><i class="zmdi zmdi-fullscreen"></i></button>
</div>
</div>

View File

@@ -1,8 +1,22 @@
import template from './widget.html';
import editTextBoxTemplate from './edit-text-box.html';
import widgetDialogTemplate from './widget-dialog.html';
import './widget.less';
import './widget-dialog.less';
import './add-widget-dialog.less';
const WidgetDialog = {
template: widgetDialogTemplate,
bindings: {
resolve: '<',
close: '&',
dismiss: '&',
},
controller() {
this.widget = this.resolve.widget;
},
};
const EditTextBoxComponent = {
template: editTextBoxTemplate,
bindings: {
@@ -51,6 +65,16 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
});
};
this.expandVisualization = () => {
$uibModal.open({
component: 'widgetDialog',
resolve: {
widget: this.widget,
},
size: 'lg',
});
};
this.localParametersDefs = () => {
if (!this.localParameters) {
this.localParameters = this.widget
@@ -66,8 +90,6 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
return;
}
Events.record('delete', 'widget', this.widget.id);
this.widget.delete().then(() => {
if (this.deleted) {
this.deleted({});
@@ -101,6 +123,7 @@ function DashboardWidgetCtrl($location, $uibModal, $window, Events, currentUser)
export default function init(ngModule) {
ngModule.component('editTextBox', EditTextBoxComponent);
ngModule.component('widgetDialog', WidgetDialog);
ngModule.component('dashboardWidget', {
template,
controller: DashboardWidgetCtrl,
@@ -112,3 +135,6 @@ export default function init(ngModule) {
},
});
}
init.init = true;

View File

@@ -1,18 +1,21 @@
<form name="dynamicForm">
<div class="form-group required" ng-class='{"has-error": (dynamicForm.targetName | showError)}'>
<label class="control-label" for="dataSourceName">Name</label>
<input type="string" class="form-control" name="targetName" ng-model="target.name" autofocus required>
<input type="string" class="form-control" name="targetName" ng-model="target.name"
data-test="TargetName" autofocus required>
<error-messages input="dynamicForm.targetName" form="dynamicForm"></error-messages>
</div>
<hr>
<div class="form-group" ng-class='{"has-error": (inner.input | showError), "required": field.property.required}' ng-form="inner" ng-repeat="field in fields">
<label ng-if="field.property.type !== 'checkbox'" class="control-label">{{field.property.title || field.name | toHuman}}</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}}">
ng-if="field.property.type !== 'file' && field.property.type !== 'checkbox'" accesskey="tab" placeholder="{{field.property.default}}"
data-test="{{field.property.title || field.name | toHuman}}">
<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}}">
ng-if="field.property.type !== 'file'" accesskey="tab" placeholder="{{field.property.default}}"
data-test="{{field.property.title || field.name | toHuman}}">
{{field.property.title || field.name | toHuman}}
</label>
@@ -23,7 +26,7 @@
<error-messages input="inner.input" form="inner"></error-messages>
</div>
<button class="btn btn-block btn-primary m-b-10" ng-disabled="!dynamicForm.$valid" ng-click="saveChanges()">Save</button>
<button class="btn btn-block btn-primary m-b-10" ng-disabled="!dynamicForm.$valid" ng-click="saveChanges()" data-test="Submit">Save</button>
<span ng-repeat="action in actions">
<button class="btn"
ng-class="action.class"

View File

@@ -119,3 +119,6 @@ function DynamicForm($http, toastr) {
export default function init(ngModule) {
ngModule.directive('dynamicForm', DynamicForm);
}
init.init = true;

View File

@@ -43,3 +43,6 @@ export default function init(ngModule) {
},
}));
}
init.init = true;

View File

@@ -16,10 +16,15 @@ export default function init(ngModule) {
$scope.$watch('render', () => {
if (isFunction($scope.render)) {
$scope.render($scope, (clonedElement) => {
$element.empty().append(clonedElement).append('<td></td>');
$element
.empty()
.append(clonedElement)
.append('<td></td>');
});
}
});
},
}));
}
init.init = true;

View File

@@ -1,5 +1,5 @@
<div class="dynamic-table-container">
<table class="table table-condensed table-hover">
<table class="table table-condensed table-hover" data-test="DynamicTable">
<thead>
<tr>
<th ng-repeat="column in $ctrl.columns" ng-click="$ctrl.onColumnHeaderClick($event, column)"

View File

@@ -1,5 +1,5 @@
.dynamic-table-container {
overflow-x: scroll;
overflow-x: auto;
th {
white-space: nowrap;

View File

@@ -2,8 +2,12 @@ import { find, filter, map, each } from 'lodash';
import template from './dynamic-table.html';
import './dynamic-table.less';
function isNullOrUndefined(v) {
return v === null || v === undefined;
}
function filterRows(rows, searchTerm, columns) {
if ((searchTerm === '') || (columns.length === 0) || (rows.length === 0)) {
if (searchTerm === '' || columns.length === 0 || rows.length === 0) {
return rows;
}
searchTerm = searchTerm.toUpperCase();
@@ -24,7 +28,7 @@ function filterRows(rows, searchTerm, columns) {
}
function sortRows(rows, orderBy) {
if ((orderBy.length === 0) || (rows.length === 0)) {
if (orderBy.length === 0 || rows.length === 0) {
return rows;
}
// Create a copy of array before sorting, because .sort() will modify original array
@@ -34,11 +38,11 @@ function sortRows(rows, orderBy) {
for (let i = 0; i < orderBy.length; i += 1) {
va = a[orderBy[i].name];
vb = b[orderBy[i].name];
if (va < vb) {
if (isNullOrUndefined(va) || va < vb) {
// if a < b - we should return -1, but take in account direction
return orderBy[i].direction * -1;
}
if (va > vb) {
if (va > vb || isNullOrUndefined(vb)) {
// if a > b - we should return 1, but take in account direction
return orderBy[i].direction * 1;
}
@@ -48,7 +52,7 @@ function sortRows(rows, orderBy) {
}
function validateItemsPerPage(value, defaultValue) {
defaultValue = defaultValue || 10;
defaultValue = defaultValue || 25;
value = parseInt(value, 10) || defaultValue;
return value > 0 ? value : defaultValue;
}
@@ -106,10 +110,7 @@ function DynamicTable($compile) {
const updateRowsToDisplay = (performFilterAndSort) => {
if (performFilterAndSort) {
this.preparedRows = sortRows(
filterRows(this.rows, this.searchTerm, this.searchColumns),
this.orderBy,
);
this.preparedRows = sortRows(filterRows(this.rows, this.searchTerm, this.searchColumns), this.orderBy);
}
const first = (this.currentPage - 1) * this.itemsPerPage;
const last = first + this.itemsPerPage;
@@ -173,7 +174,6 @@ function DynamicTable($compile) {
updateOrderByColumnsInfo();
updateRowsToDisplay(true);
// Remove text selection - may occur accidentally
if ($event.shiftKey) {
document.getSelection().removeAllRanges();
@@ -185,10 +185,7 @@ function DynamicTable($compile) {
};
this.onSearchTermChanged = () => {
this.preparedRows = sortRows(
filterRows(this.rows, this.searchTerm, this.searchColumns),
this.orderBy,
);
this.preparedRows = sortRows(filterRows(this.rows, this.searchTerm, this.searchColumns), this.orderBy);
this.currentPage = 1;
updateRowsToDisplay(true);
};
@@ -226,3 +223,5 @@ export default function init(ngModule) {
},
});
}
init.init = true;

View File

@@ -5,7 +5,7 @@ import template from './template.html';
const MAX_JSON_SIZE = 50000;
function parseValue(value) {
if (isString(value) && (value.length <= MAX_JSON_SIZE)) {
if (isString(value) && value.length <= MAX_JSON_SIZE) {
try {
return JSON.parse(value);
} catch (e) {
@@ -38,3 +38,5 @@ export default function init(ngModule) {
},
}));
}
init.init = true;

View File

@@ -1,97 +0,0 @@
import $ from 'jquery';
import { isEmpty } from 'lodash';
// From: http://jsfiddle.net/joshdmiller/NDFHg/
function EditInPlace() {
return {
restrict: 'E',
scope: {
value: '=',
ignoreBlanks: '=',
editable: '=',
done: '=',
},
template(tElement, tAttrs) {
const elType = tAttrs.editor || 'input';
const placeholder = tAttrs.placeholder || 'Click to edit';
let viewMode = '';
if (tAttrs.markdown === 'true') {
viewMode = '<span ng-click="editable && edit()" ng-bind-html="value|markdown" ng-class="{editable: editable}"></span>';
} else {
viewMode = '<span ng-click="editable && edit()" ng-bind="value" ng-class="{editable: editable}"></span>';
}
const placeholderSpan = `<span ng-click="editable && edit()"
ng-show="editable && !value"
ng-class="{editable: editable}">${placeholder}</span>`;
const editor = '<{elType} ng-model="value" class="rd-form-control"></{elType}>'.replace('{elType}', elType);
return viewMode + placeholderSpan + editor;
},
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');
// Initially, we're not editing.
$scope.editing = false;
// ng-click handler to activate edit-in-place
$scope.edit = () => {
$scope.oldValue = $scope.value;
$scope.editing = true;
// We control display through a class on the directive itself. See the CSS.
element.addClass('active');
// And we must focus the element.
// `angular.element()` provides a chainable array, like jQuery so to access
// a native DOM function, we have to reference the first element in the array.
inputElement[0].focus();
};
function save() {
if ($scope.editing) {
if ($scope.ignoreBlanks && isEmpty($scope.value)) {
$scope.value = $scope.oldValue;
}
$scope.editing = false;
element.removeClass('active');
if ($scope.value !== $scope.oldValue) {
if ($scope.done) {
$scope.done();
}
}
}
}
$(inputElement).keydown((e) => {
// 'return' or 'enter' key pressed
// allow 'shift' to break lines
if (e.which === keycodeEnter && !e.shiftKey) {
e.preventDefault();
save();
} else if (e.which === keycodeEscape) {
$scope.value = $scope.oldValue;
$scope.$apply(() => {
$(inputElement[0]).blur();
});
}
}).blur(() => {
save();
});
},
};
}
export default function init(ngModule) {
ngModule.directive('editInPlace', EditInPlace);
}

View File

@@ -7,7 +7,10 @@ export default function init(ngModule) {
bindings: {
function: '<',
},
template: '<p class="alert alert-danger" ng-if="$ctrl.showMailWarning">It looks like your mail server isn\'t configured. Make sure to configure it for the {{$ctrl.function}} to work.</p>',
template:
'<p class="alert alert-danger" ng-if="$ctrl.showMailWarning">It looks like your mail server isn\'t configured. Make sure to configure it for the {{$ctrl.function}} to work.</p>',
controller,
});
}
init.init = true;

View File

@@ -51,3 +51,5 @@ const EmptyStateComponent = {
export default function init(ngModule) {
ngModule.component('emptyState', EmptyStateComponent);
}
init.init = true;

View File

@@ -11,10 +11,11 @@ const ErrorMessagesComponent = {
input: '<',
form: '<',
},
controller() {
},
controller() {},
};
export default function init(ngModule) {
ngModule.component('errorMessages', ErrorMessagesComponent);
}
init.init = true;

View File

@@ -38,3 +38,4 @@ export default function init(ngModule) {
});
}
init.init = true;

View File

@@ -23,7 +23,8 @@ const FiltersComponent = {
},
};
export default function init(ngModule) {
ngModule.component('filters', FiltersComponent);
}
init.init = true;

View File

@@ -37,3 +37,5 @@ const EditGroupDialogComponent = {
export default function init(ngModule) {
ngModule.component('editGroupDialog', EditGroupDialogComponent);
}
init.init = true;

View File

@@ -1,7 +1,8 @@
function controller($window, $location, toastr, currentUser) {
this.canEdit = () => currentUser.isAdmin && this.group.type !== 'builtin';
this.saveName = () => {
this.saveName = (name) => {
this.group.name = name;
this.group.$save();
};
@@ -23,7 +24,8 @@ export default function init(ngModule) {
transclude: true,
template: `
<h2 class="m-t-0">
<edit-in-place editable="$ctrl.canEdit()" done="$ctrl.saveName" ignore-blanks='true' value="$ctrl.group.name"></edit-in-place>&nbsp;
<edit-in-place class="edit-in-place" is-editable="$ctrl.canEdit()" on-done="$ctrl.saveName"
ignore-blanks="true" value="$ctrl.group.name" editor="'input'"></edit-in-place>&nbsp;
<button class="btn btn-xs btn-danger" ng-if="$ctrl.canEdit()" ng-click="$ctrl.deleteGroup()">Delete this group</button>
</h2>
`,
@@ -31,3 +33,5 @@ export default function init(ngModule) {
controller,
});
}
init.init = true;

View File

@@ -14,3 +14,5 @@ const Overlay = {
export default function init(ngModule) {
ngModule.component('overlay', Overlay);
}
init.init = true;

View File

@@ -1,8 +1,6 @@
import template from './page-header.html';
function controller() {
}
function controller() {}
export default function init(ngModule) {
ngModule.component('pageHeader', {
@@ -14,3 +12,5 @@ export default function init(ngModule) {
},
});
}
init.init = true;

View File

@@ -26,3 +26,5 @@ export default function init(ngModule) {
controller: PaginatorCtrl,
});
}
init.init = true;

View File

@@ -132,7 +132,9 @@ function ParametersDirective($location, $uibModal) {
link(scope) {
// is this the correct location for this logic?
if (scope.syncValues !== false) {
scope.$watch('parameters', () => {
scope.$watch(
'parameters',
() => {
if (scope.changed) {
scope.changed({});
}
@@ -141,7 +143,9 @@ function ParametersDirective($location, $uibModal) {
extend(params, param.toUrlParams());
});
$location.search(params);
}, true);
},
true,
);
}
scope.showParameterSettings = (param) => {
@@ -184,3 +188,5 @@ export default function init(ngModule) {
ngModule.component('parameterSettings', ParameterSettingsComponent);
ngModule.component('parameterInput', ParameterInputComponent);
}
init.init = true;

View File

@@ -1,4 +1,4 @@
import { includes, each } from 'lodash';
import { includes, each, filter } from 'lodash';
import template from './permissions-editor.html';
const PermissionsEditorComponent = {
@@ -14,6 +14,7 @@ const PermissionsEditorComponent = {
this.grantees = [];
this.newGrantees = {};
this.aclUrl = this.resolve.aclUrl.url;
this.owner = this.resolve.owner;
// List users that are granted permissions
const loadGrantees = () => {
@@ -39,7 +40,7 @@ const PermissionsEditorComponent = {
}
User.query({ q: search }, (response) => {
const users = response.results;
const users = filter(response.results, u => u.id !== this.owner.id);
const existingIds = this.grantees.map(m => m.id);
users.forEach((user) => {
user.alreadyGrantee = includes(existingIds, user.id);
@@ -50,7 +51,7 @@ const PermissionsEditorComponent = {
// Add new user to grantees list
this.addGrantee = (user) => {
this.newGrantees.selected = undefined;
this.newGrantees = {};
const body = { access_type: 'modify', user_id: user.id };
$http.post(this.aclUrl, body).success(() => {
user.alreadyGrantee = true;
@@ -90,3 +91,6 @@ const PermissionsEditorComponent = {
export default function init(ngModule) {
ngModule.component('permissionsEditor', PermissionsEditorComponent);
}
init.init = true;

View File

@@ -4,21 +4,22 @@
</div>
<div class="modal-body">
<div style="overflow: auto; height: 300px">
<ui-select ng-model="$ctrl.newGrantee.selected" on-select="$ctrl.addGrantee($item)">
<ui-select ng-model="$ctrl.newGrantees.selected" on-select="$ctrl.addGrantee($item)">
<ui-select-match placeholder="Add New User"></ui-select-match>
<ui-select-choices repeat="user in $ctrl.foundUsers | filter:$select.search"
refresh="$ctrl.findUser($select.search)"
refresh-delay="0"
ui-disable-choice="user.alreadyGrantee">
<div>
<img ng-src="{{ user.profile_image_url }}" class="profile__image" height="24px">&nbsp;<span
ng-class="{'text-muted': user.is_disabled}">{{user.name}}</span>
<small ng-if="user.alreadyGrantee">(already has permission)</small>
<div class="d-flex align-items-center">
<img ng-src="{{ user.profile_image_url }}" class="profile__image" height="24px">&nbsp;
<span ng-class="{'text-muted': user.is_disabled}">{{user.name}}</span>
&nbsp;<small ng-if="user.alreadyGrantee">(already has permission)</small>
</div>
</ui-select-choices>
</ui-select>
<br/>
<table class="table table-condensed table-hover">
<br>
<h5>Who has access</h5>
<table class="table table-condensed table-hover table--permission">
<thead>
<tr>
<th></th>
@@ -28,10 +29,16 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="grantee in $ctrl.grantees">
<td width="50px"><img ng-src="{{ grantee.profile_image_url }}" class="profile__image" height="40px"/></td>
<tr>
<td width="32px"><img ng-src="{{ $ctrl.owner.profile_image_url }}" class="profile__image" height="32px"/></td>
<td class="text-muted"> {{ $ctrl.owner.name}}</td>
<td class="mp__permission-type">Owner</td>
<td></td>
</tr>
<tr ng-repeat="grantee in $ctrl.grantees" ng-if="grantee.id != $ctrl.owner.id">
<td width="32px"><img ng-src="{{ grantee.profile_image_url }}" class="profile__image" height="32px"/></td>
<td ng-class="{'text-muted': grantee.is_disabled}">{{grantee.name}}</td>
<td>{{grantee.access_type}}</td>
<td class="mp__permission-type">{{grantee.access_type}}</td>
<td><button class="pull-right btn btn-sm btn-danger" ng-click="$ctrl.removeGrantee(grantee)">Remove</button></td>
</tr>
</tbody>

View File

@@ -0,0 +1,16 @@
import PropTypes from 'prop-types';
export const DataSource = PropTypes.shape({
syntax: PropTypes.string,
options: PropTypes.shape({
doc: PropTypes.string,
doc_url: PropTypes.string,
}),
type_name: PropTypes.string,
});
export const Table = PropTypes.shape({
columns: PropTypes.arrayOf(PropTypes.string).isRequired,
});
export const Schema = PropTypes.arrayOf(Table);

View File

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

View File

@@ -35,3 +35,5 @@ const ApiKeyDialog = {
export default function init(ngModule) {
ngModule.component('apiKeyDialog', ApiKeyDialog);
}
init.init = true;

View File

@@ -7,7 +7,9 @@ const EmbedCodeDialog = {
this.query = this.resolve.query;
this.visualization = this.resolve.visualization;
this.embedUrl = `${clientConfig.basePath}embed/query/${this.query.id}/visualization/${this.visualization.id}?api_key=${this.query.api_key}`;
this.embedUrl = `${clientConfig.basePath}embed/query/${this.query.id}/visualization/${
this.visualization.id
}?api_key=${this.query.api_key}`;
if (window.snapshotUrlBuilder) {
this.snapshotUrl = window.snapshotUrlBuilder(this.query, this.visualization);
}
@@ -23,3 +25,5 @@ const EmbedCodeDialog = {
export default function init(ngModule) {
ngModule.component('embedCodeDialog', EmbedCodeDialog);
}
init.init = true;

View File

@@ -1,160 +0,0 @@
import 'brace';
import 'brace/mode/python';
import 'brace/mode/sql';
import 'brace/mode/json';
import 'brace/ext/language_tools';
import 'brace/ext/searchbox';
import { map } from 'lodash';
// By default Ace will try to load snippet files for the different modes and fail.
// We don't need them, so we use these placeholders until we define our own.
function defineDummySnippets(mode) {
window.ace.define(`ace/snippets/${mode}`, ['require', 'exports', 'module'], (require, exports) => {
exports.snippetText = '';
exports.scope = mode;
});
}
defineDummySnippets('python');
defineDummySnippets('sql');
defineDummySnippets('json');
function queryEditor(QuerySnippet, $timeout) {
return {
restrict: 'E',
scope: {
query: '=',
schema: '=',
syntax: '=',
},
template: '<div ui-ace="editorOptions" ng-model="query.query"></div>',
link: {
pre($scope) {
$scope.syntax = $scope.syntax || 'sql';
$scope.editorOptions = {
mode: 'json',
// require: ['ace/ext/language_tools'],
advanced: {
behavioursEnabled: true,
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
autoScrollEditorIntoView: true,
},
onLoad(editor) {
$scope.$on('query-editor.command', ($event, command, ...args) => {
switch (command) {
case 'focus': {
editor.focus();
break;
}
case 'paste': {
const [text] = args;
editor.session.doc.replace(editor.selection.getRange(), text);
const range = editor.selection.getRange();
$scope.query.query = editor.session.getValue();
$timeout(() => {
editor.selection.setRange(range);
});
break;
}
default:
break;
}
});
// Release Cmd/Ctrl+L to the browser
editor.commands.bindKey('Cmd+L', null);
editor.commands.bindKey('Ctrl+P', null);
editor.commands.bindKey('Ctrl+L', null);
QuerySnippet.query((snippets) => {
window.ace.acequire(['ace/snippets'], (snippetsModule) => {
const snippetManager = snippetsModule.snippetManager;
const m = {
snippetText: '',
};
m.snippets = snippetManager.parseSnippetFile(m.snippetText);
snippets.forEach((snippet) => {
m.snippets.push(snippet.getSnippet());
});
snippetManager.register(m.snippets || [], m.scope);
});
});
editor.$blockScrolling = Infinity;
editor.getSession().setUseWrapMode(true);
editor.setShowPrintMargin(false);
$scope.$watch('syntax', (syntax) => {
const newMode = `ace/mode/${syntax}`;
editor.getSession().setMode(newMode);
});
$scope.$watch('schema', (newSchema, oldSchema) => {
if (newSchema !== oldSchema) {
if (newSchema === undefined) {
return;
}
const tokensCount = newSchema.reduce((totalLength, table) => totalLength + table.columns.length, 0);
// If there are too many tokens we disable live autocomplete,
// as it makes typing slower.
if (tokensCount > 5000) {
editor.setOption('enableLiveAutocompletion', false);
} else {
editor.setOption('enableLiveAutocompletion', true);
}
}
});
$scope.$parent.$on('angular-resizable.resizing', () => {
editor.resize();
});
editor.focus();
},
};
const schemaCompleter = {
getCompletions(state, session, pos, prefix, callback) {
if (prefix.length === 0 || !$scope.schema) {
callback(null, []);
return;
}
if (!$scope.schema.keywords) {
const keywords = {};
$scope.schema.forEach((table) => {
keywords[table.name] = 'Table';
table.columns.forEach((c) => {
keywords[c] = 'Column';
keywords[`${table.name}.${c}`] = 'Column';
});
});
$scope.schema.keywords = map(keywords, (v, k) => ({
name: k,
value: k,
score: 0,
meta: v,
}));
}
callback(null, $scope.schema.keywords);
},
};
window.ace.acequire(['ace/ext/language_tools'], (langTools) => {
langTools.addCompleter(schemaCompleter);
});
},
},
};
}
export default function init(ngModule) {
ngModule.directive('queryEditor', queryEditor);
}

View File

@@ -5,7 +5,7 @@ function queryResultLink() {
restrict: 'A',
link(scope, element, attrs) {
const fileType = attrs.fileType ? attrs.fileType : 'csv';
scope.$watch('queryResult && queryResult.getData()', (data) => {
scope.$watch('queryResult && queryResult.getData() && query.name', (data) => {
if (!data) {
return;
}
@@ -24,7 +24,7 @@ function queryResultLink() {
element.attr('href', url);
element.attr(
'download',
`${scope.query.name.replace(' ', '_') +
`${scope.query.name.replace(/ /g, '_') +
moment(scope.queryResult.getUpdatedAt()).format('_YYYY_MM_DD')}.${fileType}`,
);
}
@@ -36,3 +36,6 @@ function queryResultLink() {
export default function init(ngModule) {
ngModule.directive('queryResultLink', queryResultLink);
}
init.init = true;

View File

@@ -127,3 +127,5 @@ export default function init(ngModule) {
ngModule.directive('queryRefreshSelect', queryRefreshSelect);
ngModule.component('scheduleDialog', ScheduleForm);
}
init.init = true;

View File

@@ -1,6 +1,6 @@
<div class="schema-container" ng-if="$ctrl.schema">
<div class="schema-control">
<input type="text" placeholder="Search schema..." class="form-control" ng-model="$ctrl.schemaFilter" ng-model-options="{ debounce: 500 }" ng-disabled="$ctrl.isEmpty()">
<input type="text" placeholder="Search schema..." class="form-control" ng-model="$ctrl.schemaFilter" ng-model-options="{ debounce: 500 }" ng-disabled="$ctrl.isEmpty()" ng-change="$ctrl.splitFilter($ctrl.schemaFilter);">
<button class="btn btn-default"
title="Refresh Schema"
ng-click="$ctrl.onRefresh()">
@@ -9,7 +9,7 @@
</div>
<div class="schema-browser" vs-repeat vs-size="$ctrl.getSize(table)">
<div ng-repeat="table in $ctrl.schema | filter:$ctrl.schemaFilter track by table.name">
<div ng-repeat="table in $ctrl.schema | filter:$ctrl.schemaFilterObject track by table.name">
<div class="table-name" ng-click="$ctrl.showTable(table)">
<i class="fa fa-table"></i>
<strong>
@@ -20,7 +20,7 @@
ng-click="$ctrl.itemSelected($event, [table.name])"></i>
</div>
<div uib-collapse="table.collapsed">
<div ng-repeat="column in table.columns track by column" class="table-open">{{column}}
<div ng-repeat="column in table.columns | filter:$ctrl.schemaFilterColumn track by column" class="table-open">{{column}}
<i class="fa fa-angle-double-right copy-to-editor" aria-hidden="true"
ng-click="$ctrl.itemSelected($event, [column])"></i>
</div>

View File

@@ -27,6 +27,18 @@ function SchemaBrowserCtrl($rootScope, $scope) {
$event.preventDefault();
$event.stopPropagation();
};
this.splitFilter = (filter) => {
filter = filter.replace(/ {2}/g, ' ');
if (filter.includes(' ')) {
const splitTheFilter = filter.split(' ');
this.schemaFilterObject = { name: splitTheFilter[0], columns: splitTheFilter[1] };
this.schemaFilterColumn = splitTheFilter[1];
} else {
this.schemaFilterObject = filter;
this.schemaFilterColumn = '';
}
};
}
const SchemaBrowser = {
@@ -41,3 +53,6 @@ const SchemaBrowser = {
export default function init(ngModule) {
ngModule.component('schemaBrowser', SchemaBrowser);
}
init.init = true;

View File

@@ -51,3 +51,6 @@ export default function init(ngModule) {
});
});
}
init.init = true;

View File

@@ -18,9 +18,10 @@ export default function init(ngModule) {
bindings: {
query: '<',
visualization: '<',
readonly: '<',
},
template: `
<a ng-href="{{$ctrl.link}}" class="query-link">
<a ng-href="{{$ctrl.readonly ? undefined : $ctrl.link}}" class="query-link">
<visualization-name visualization="$ctrl.visualization"/>
<span>{{$ctrl.query.name}}</span>
</a>
@@ -28,3 +29,5 @@ export default function init(ngModule) {
controller: QueryLinkController,
});
}
init.init = true;

View File

@@ -22,3 +22,5 @@ function rdTab($location) {
export default function init(ngModule) {
ngModule.directive('rdTab', rdTab);
}
init.init = true;

View File

@@ -13,3 +13,6 @@ const RdTimeAgo = {
export default function init(ngModule) {
ngModule.component('rdTimeAgo', RdTimeAgo);
}
init.init = true;

View File

@@ -28,3 +28,6 @@ function rdTimer() {
export default function init(ngModule) {
ngModule.directive('rdTimer', rdTimer);
}
init.init = true;

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