From 100ea75ceb169159be7d876a8738350bdadef0a4 Mon Sep 17 00:00:00 2001 From: Trung Tin Luong Date: Fri, 19 Mar 2021 14:38:47 +0700 Subject: [PATCH] Update helmchart and redrploymext (#34) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add toggle to disable public URLs (#5140) * Add toggle to disable public URLs * Add Cypress tests * Antd v4: Fix CreateUserDialog (#5139) * Antd v4: Update CreateUserDialog * Add Cypress test for user creation * Misc frontend changes from internal fork (#5143) * Move CardsList to typescript (#5136) * Refactor CardsList - pass a suffix for list item Adding :id to an item to be used as a key suffix is redundant and the same can be accomplished by using :index from the map function. * Move CardsList to typescript * Convert CardsList component to functional component * CR1 * CR2 * Keep selected filters when switching visualizations (#5146) * getredash/redash#4944 Query pages: keep selected filters when switching visualizations * Pass current filters to expanded widget modal * prevent assigning queries to view_only data sources (#5152) * Add default limit (1000) to SQL queries (#5088) * add default limit 1000 * Add frontend changes and connect to backend * Fix query hash because of default limit * fix CircleCI test * adjust for comment * Allow to clear selected tags on list pages (#5142) * Convert TagsList to functional component * Convert TagsList to typescript * Allow to unselect all tags * Add title to Tags block and explicit "clear filter" button * Some tweaks * Keep additional URL params when forking a query (#5184) * Refresh CSRF tokens (#5177) * expire CSRF tokens after 6 hours * use axios' built-in cookie to header copy mechanism * add axios-auth-refresh * retry CSRF-related 400 errors by refreshing the cookie * export the auth refresh interceptor to support ejecting it if neccessary * reject the original request if it's unrelated to CSRF * add 'cancelled' meta directive to all cancelled jobs (#5187) * Ask user to log in when session expires (#5178) * Ask user to log in when session expires * Update implementation * Update implementation * Minor fix * Update modal * Do not intercept calls to api/session as Auth.requireSession() relies on it * Refine code; adjust popup size and position * Some Choropleth improvements/refactoring (#5186) * Directly map query results column to GeoJSON property * Use cache for geoJson requests * Don't handle bounds changes while loading geoJson data * Choropleth: fix map "jumping" on load; don't save bounds if user didn't edit them; refine code a bit * Improve cache * Optimize Japan Perfectures map (remove irrelevant GeoJson properties) * Improve getOptions for Choropleth; remove unused code * Fix test * Add US states map * Convert USA map to Albers projection * Allow to specify user-friendly field names for maps * Align Y axes at zero (#5053) * Align Y axes as zero * Fix typo (with @deecay) * Add alignYAxesAtZero function * Avoid 0 division Co-authored-by: Gabriel Dutra * Generate Code Coverage report for Cypress (#5137) * Move Cypress to dev dependencies (#3991) * Test Cypress on package list * Skip Puppeteer Chromium as well * Put back missing npm install on netlify.toml * Netlify: move env vars to build.environment * Remove cypress:install script * Update Cypress dockerfile * Copy package-lock.json to Cypress dockerfile * ScheduleDialog: Filter empty interval groups (#5196) * Share Embed Spec: Make sure query is executed (#5191) * Updated Cypress to v5.3 and fixed e2e tests (#5199) * Upgraded Cypress to v5.3 and fixed e2e tests * Updated cypress image * Fixed failing tests * Updated NODE_VERSION in netlify * Update client/cypress/integration/visualizations/choropleth_spec.js Co-authored-by: Gabriel Dutra * fixed test in choropleth Co-authored-by: Gabriel Dutra * Extra actions on Queries and Dashboards pages (#5201) * Extra actions for Query View and Query Source pages * Convert Queries List page to functional component * Convert Dashboards List page to functional component * Extra actions for Query List page * Extra actions for Dashboard List page * Extra actions for Dashboard page * Pass some extra data to Dashboard.HeaderExtra component * CR1 * Remove build args from Cypress start script (#5203) * Frontend updates from internal fork (#5209) * Add horizontal bar chart (#5154) * added bar chart boilerplate * added x/y manipulation * replaced x/y management to inner series preparer * added tests * moved axis inversion to all charts series * removed line and area * inverted labels ui * removed normalizer check, simplified inverted axes check * finished working hbar * minor review * added conditional title to YAxis * generalized horizontal chart for line charts, resetted state on globalSeriesType change * fixed updates * fixed updates to layout * fixed minor issues * removed right Y axis when axes inverted * ran prettier * fixed updater function conflict and misuse of getOptions * renamed inverted to swapped * created mappingtypes for swapped columns * removed unused import * minor polishing * improved series behaviour in h-bar * minor fix * added basic filter to ChartTypeSelect * final setup of filtered chart types * Update viz-lib/src/components/visualizations/editor/createTabbedEditor.jsx * added proptypes and renamed ChartTypeSelect props * Add missing import * fixed import, moved result array to global scope * merged import * clearer naming in ChartTypeSelect * better lodash map syntax * fixed global modification * moved result inside useMemo Co-authored-by: Gabriel Dutra Co-authored-by: Levko Kravets * Fix Home EmptyState help link (#5217) * Static SAML configuration and assertion encryption (#5175) * Change front-end and data model for SAML2 auth - static configuration * Add changes to use inline metadata. * add switch for static and dynamic SAML configurations * Fixed config of backend static/dynamic to match UI * add ability to encrypt/decrypt SAML assertions with pem and crt files. Upgraded to pysaml2 6.1.0 to mitigate signature mismatch during decryption * remove print debug statement * Use utility to find xmlsec binary for encryption, formatting saml_auth module * format SAML Javascript, revert want_signed_response to pre-PR value * pysaml2's entityid should point to the sp, not the idp * add logging for entityid for validation * use mustache_render instead of string formatting. put all static logic into static branch * move mustache template for inline saml metadata to the global level * Incorporate SAML type with Enabled setting * Update client/app/pages/settings/components/AuthSettings/SAMLSettings.jsx Co-authored-by: Gabriel Dutra Co-authored-by: Chad Chen Co-authored-by: Gabriel Dutra * Fix dashboard background grid (#5238) * added required to Form.Item and Input for better UI (#5231) * added required to Form.Item and Input for better UI * removed required from input * Revert "removed required from input" This reverts commit b56cd76fa1b1eba4e337e55c2797b6a5d64f2699. * Redo "removed required from input" * removed typo Co-authored-by: rafawendel2010@gmail.com * Fix annotation bug causing queries not to run - ORA-00933 (#5179) * Fix for the typo button in ParameterMappingInput (#5244) * extend the refresh_queries timeout from 3 minutes to 10 minutes (#5253) * Multiselect dropdown slowness (fix) (#5221) * created util to estimate reasonable width for dropdown * removed unused import * improved calculation of item percentile * added getItemOfPercentileLength to relevant spots * added getItemOfPercentileLength to relevant spots * Added missing import * created custom select element * added check for property path * removed uses of percentile util * gave up on getting element reference * finished testing Select component * removed unused imports * removed older uses of Option component * added canvas calculation * removed minWidth from Select * improved calculation * added fallbacks * added estimated offset * removed leftovers 😅 * replaced to percentiles to max value * switched to memo and renamed component * proper useMemo syntax * Update client/app/components/Select.tsx Co-authored-by: Gabriel Dutra * created custom restrictive types * added quick const * fixed style * fixed generics * added pos absolute to fix percy * removed custom select from ParameterMappingInput * applied prettier * Revert "added pos absolute to fix percy" This reverts commit 4daf1d4bef9edf93cd9bb1f404bd022472ff17a2. * Pin Percy version to 0.24.3 * Update client/app/components/ParameterMappingInput.jsx Co-authored-by: Gabriel Dutra * renamed Select.jsx to SelectWithVirtualScroll Co-authored-by: Gabriel Dutra * bugfix: fix #5254 (#5255) Co-authored-by: Jerry * Enable graceful shutdown of rq workers (#5214) * Enable graceful shutdown of rq workers * Use `exec` in the `worker` command of the entrypoint to propagate the `TERM` signal * Allow rq processes managed by supervisor to exit without restart on expected status codes * Allow supervisorctl to contact the running supervisor * Add a `shutdown_worker` command that will send `TERM` to all running worker processes and then sleep. This allows orchestration systems to initiate a graceful shutdown before sending `SIGTERM` to supervisord * Use Heroku worker as the BaseWorker This implements a graceful shutdown on SIGTERM, which simplifies external shutdown procedures. * Fix imports based upon review * Remove supervisorctl config * Enable Boxplot to be horizontal (#5262) * Frontend updates from internal fork (#5259) * DynamicComponent for QuerySourceAlerts * General Settings updates * Dynamic Date[Range] updates * EmptyState updates * Query and SchemaBrowser updates * Adjust page headers and add disablePublish * Policy updates * Separate Home FavoritesList component * Update FormatQuery * Autolimit frontend fixes * Misc updates * Keep registering of QuerySourceDropdown * Undo changes in DynamicComponent * Change sql-formatter package.json syntax * Allow opening help trigger in new tab * Don't run npm commands as root in Dockerfile * Cypress: Remove extra execute query * Correct cleanup_query_results comment (#5276) Correct comment from QUERY_RESULTS_MAX_AGE to QUERY_RESULTS_CLEANUP_MAX_AGE * Remove unwanted props from Select component (#5277) * Explicitly selected props so as to avoid errors from non-wanted props * Simplified approach * Ran prettier 😬 * Fixed minor issues * Fix QuerySourceDropdown value type (#5284) * Changed 'Delete Alert' into 'Delete' for consistency (#5287) * Redesign desktop nav bar (#5294) * Add React Fast Refresh + Hot Module Reloading (#5291) * removed leftover console.log (#5303) * Fix disabled hot reload flow (#5306) * Sync date format from settings with clientConfig (#5299) * added eslint no-console (#5305) * added eslint no-console * Update client/.eslintrc.js to allow warnings Co-authored-by: Gabriel Dutra Co-authored-by: Gabriel Dutra * Convert viz-lib to TypeScript (#5310) Co-authored-by: ts-migrate <> * change item element in system status page (#5323) * Obfuscate non-email alert destinations (#5318) * Dropdown param search fix (#5304) * fixed QueryBasedParamterInput optionFilterProp * added optionFilterProp fallback for SelectWithVirtualScroll * simplified syntax * removed optionFilterProp from QueryBasedParameterInput.jsx Co-authored-by: Gabriel Dutra * restricted SelectWithVirtualScroll props * Added e2e test for parameter filters * moved filter assertion to more suitable place * created helper for option filter prop assertion * moved option filter prop assertion to proper place, added result update assertion * refactor openAndSearchAntdDropdown helper * Fix parameter_spec Co-authored-by: Gabriel Dutra * Add Username and Password fields to MongoDB config (#5314) * docs: fix simple typo, possbily -> possibly (#5329) There is a small typo in redash/settings/__init__.py. Should read `possibly` rather than `possbily`. * Secret handling for Yandex, TreasureData, & Postgres/CockroachDB SSL (#5312) * Bar chart e2e test (#5279) * created bar-chart e2e test boilerplate * refactored assertions * added snapshots and dashboard * refactored assertions to properly deal with async * replaced loops with getters for proper workings of cypress * added a couple other bar charts * ran prettier * added a better query for bar charts * removed leftovers * moved helpers to support folder Co-authored-by: Gabriel Dutra * Truncate large Databricks ODBC result sizes (#5290) Truncates results sets that exceed a limit taken from an environment variable called DATABRICKS_ROW_LIMIT. * Add reorder to dashboard parameter widgets (#5267) * added paramOrder prop * minor refactor * moved logic to widget * Added paramOrder to widget API call * Update client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx Co-authored-by: Gabriel Dutra * Merge branch 'master' into reorder-dashboard-parameters * experimental removal of helper element * cleaner comment * Added dashboard global params logic * Added backend logic for dashboard options * Removed testing leftovers * removed appending sortable to parent component behavior * Revert "Added backend logic for dashboard options" This reverts commit 41ae2ce4755a6fa03fd76d900819b11016919275. * Re-structured backend options * removed temporary edits * Added dashboard/widget param reorder cypress tests * Separated edit and sorting permission * added options to public dashboard serializer * Removed undesirable events from drag * Bring back attaching sortable to its parent This reverts commit 163fb6fef5ecf7ec9924d5ff2dddcb4d889caab8. * Added prop to control draggable destination parent * Removed paramOrder fallback * WIP (for Netflify preview) * fixup! Added prop to control draggable destination parent * Better drag and drop styling and fix for the padding * Revert "WIP (for Netflify preview)" This reverts commit 433e11edc353b645410bac4bc162819ffd37d89a. * Improved dashboard parameter Cypress test * Standardized reorder styling * Changed dashboard param reorder to edit mode only * fixup! Improved dashboard parameter Cypress test * fixup! Improved dashboard parameter Cypress test * Fix for Cypress CI error Co-authored-by: Gabriel Dutra * Fix inconsistent Sankey behavior (#5286) * added type casting to coerce number string into nuber * Merge branch 'master' into fix-inconsistent=sankey-behavior * typed map viz options * Partially typed what was possible * reworked data coercion * improved MapOptionsType types * readaqueted sankey rows so as to allow strings again * Use legacy resolver in pip to fix broken build (#5309) Fixes #5300 and fixes #5307 There have been upstream (`python:37-slim` image) changes that bring in `pip` version 20.3.1, which makes new `2020-resolver` the default. Due to that, un-resolvable dependency conflicts in `requirements_all_ds.txt` now cause the build to fail. This is a workaround until the package versions can be updated to work with the new pip resolver. * Encrypt alert notification destinations (#5317) * Remove unnecessary space in rq log (#5345) * Fix: add a merge migration to solve multi head issue (#5364) * Add unit test to test for multi-head migrations issue * Add merge migration * Fix for Cypress flakiness generated by param_spec (#5349) * Bump dompurify from 2.0.8 to 2.0.17 in /viz-lib (#5326) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.0.8 to 2.0.17. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/2.0.8...2.0.17) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump bl from 1.2.2 to 1.2.3 in /viz-lib (#5257) Bumps [bl](https://github.com/rvagg/bl) from 1.2.2 to 1.2.3. - [Release notes](https://github.com/rvagg/bl/releases) - [Commits](https://github.com/rvagg/bl/compare/v1.2.2...v1.2.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump axios from 0.19.0 to 0.21.1 (#5366) Bumps [axios](https://github.com/axios/axios) from 0.19.0 to 0.21.1. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.19.0...v0.21.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Updated axios (#5371) * Increased waiting time to avoid flakiness (#5370) * Add My Dashboards filter option to the Dashboards list (#5375) * Add My Dashboards filter option to the Dashboards list. Added API endpoint to get the list of a user's dashboards, similar to the My Queries feature. * Update empty dashboard list state to show an invite to create a new dashboard, like My Queries * Update to Levko's suggested approach. Clean up some of the formatting for consistency. Put the 'My Queries/Dashboards' item before the Favorites since that organization seems cleaner to me. * Address Levko's comments * extend sync_user_details expiry (#5330) * Revert "Updated axios (#5371)" (#5385) This reverts commit 49536de1ed8331928cb6139d5aac2a2ebe780fc7. * Fix duplicate stylesheets (#5396) * Upgrade RQ to v1.5 (#5207) * upgrade RQ to v1.5 * set job's started_at * update healthcheck to match string worker names * delay worker healthcheck for 5 minutes from start to allow enough time to load in case many workers try to load simultaneously * log when worker cannot be found * Initial a11y improvements (#5408) * Fixed jsx-a11y problems * Changed tabIndex to type number * Initial improvements to DesktopNavbar accessibility * Added accessibility to favorites list * Improved accessibility in Desktop Navbar * Improvements in Desktop navbar semantics * Added aria roles to tags list * Fixed tabindex type * Improved aria labels in query control dropdown * Added tab for help trigger close button * Fixed typo * Improved accessibility in query selector * Changed resizable role to separator * Added label to empty state close button * Removed redundant and mistaken roles * Used semantic components * Removed tabIndex from anchor tags * Removed mistakenly set menuitem role from anchors * Removed tabIndex from Link components * Removed improper hidden aria label from icon * Reverted button and link roles in anchors for minimal merge conflicts * Replaced alt attr with aria-label for icons * Removed redundant menu role * Improved accessibility of CodeBlock * Removed improper role from schema browser * Reverted favorites list to div * Removed improper presentation role in query snippets * Tracked changes for further PR * Revert "Improved accessibility of CodeBlock" * Add aria-labelledby to the associated code labels This reverts commit 00a1685b1b37ad1ad5770880f9653dbd06d2cf3f. * Wrapped close icon into button * remove check datasource permission * show list dashboard * show list dashboard * show list dashboard * Add plain button (#5419) * Add plain button * Minor syntax improvements * Refactor of Link component (#5418) * Refactor of link component * Applied anchor-is-valid to Link component * Fixed Eslint error * Removed improper anchor uses * Fixed TS errors * show list dashboard * merge original repo * Reset failure counter on adhoc success (#5394) * reset failure counter when query completes successfully via adhoc * Use "query_id" in metadata, but still allow "Query ID" for transition/legacy support * Add setting to identify email block domain (#5377) * Add setting to identify email block domain ref: #5368 * rename Co-authored-by: Levko Kravets * rename and add comment Co-authored-by: Levko Kravets * Update redash/handlers/users.py Co-authored-by: Levko Kravets * Update redash/handlers/users.py Co-authored-by: Levko Kravets * Add more comment to settting Co-authored-by: Levko Kravets * feat: support Trino data-source (#5411) * feat: add trino logo * feat: add trino * merge original repo * merge original repo * fix report * fix report * display query owner * check permission can view dashboard * check permission can view dashboard * fix css login * fix css login * fix css login * fix css login * fix css login * fix password user * fix css * fix css * fix sub dashboard * [Tmp] * fix css * add description * fix error build * fix css * fix css * [Change for Deployment] * zip * new version * new version * new version * new version Co-authored-by: Gabriel Dutra Co-authored-by: max-voronov <70445727+max-voronov@users.noreply.github.com> Co-authored-by: Levko Kravets Co-authored-by: Omer Lachish Co-authored-by: Lingkai Kong Co-authored-by: Alexander Rusanov Co-authored-by: Rafael Wendel Co-authored-by: Christopher Grant Co-authored-by: Chad Chen Co-authored-by: Jonathan Hult Co-authored-by: Jerry <610819267@qq.com> Co-authored-by: Jerry Co-authored-by: Josh Bohde Co-authored-by: deecay Co-authored-by: Jiajie Zhong Co-authored-by: Elad Ossadon Co-authored-by: Elad Ossadon Co-authored-by: Patrick Yang Co-authored-by: Tim Gates Co-authored-by: Christopher Grant Co-authored-by: Vipul Mathur Co-authored-by: Arik Fraimovich Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Justin Talbot <76974990+justint-db@users.noreply.github.com> Co-authored-by: Khiem Nguyen Co-authored-by: Đặng Minh Dũng Co-authored-by: Hải Triều Nguyễn Hồ Co-authored-by: Nguyễn Hồ Hải Triều Co-authored-by: Nit --- .circleci/Dockerfile.cypress | 10 +- .circleci/config.yml | 22 + .circleci/docker-compose.cypress.yml | 35 +- .github/workflows/test.yml | 8 +- .gitignore | 3 + Dockerfile | 22 +- Makefile | 2 +- bin/docker-entrypoint | 5 +- client/.babelrc | 7 +- client/.eslintrc.js | 18 + .../app/assets/images/db-logos/databricks.png | Bin 2562 -> 2884 bytes client/app/assets/images/db-logos/trino.png | Bin 0 -> 23773 bytes client/app/assets/less/custom.less | 29 + client/app/assets/less/inc/table.less | 4 +- client/app/assets/less/redash/query.less | 1 + client/app/assets/less/server.less | 43 +- .../ApplicationLayout/DesktopNavbar.jsx | 115 +- .../ApplicationLayout/DesktopNavbar.less | 123 +- .../ApplicationLayout/index.jsx | 26 +- .../ApplicationArea/ErrorMessage.jsx | 20 +- .../ApplicationArea/ErrorMessageDetails.jsx | 11 + .../ApplicationArea/handleNavigationIntent.js | 2 +- .../routeWithApiKeySession.jsx | 4 +- ...erSession.jsx => routeWithUserSession.tsx} | 60 +- client/app/components/CodeBlock.less | 2 +- client/app/components/DialogWrapper.d.ts | 4 +- client/app/components/DynamicComponent.jsx | 8 +- .../EditParameterSettingsDialog.jsx | 2 +- .../QueryControlDropdown.jsx | 3 +- client/app/components/FavoritesControl.jsx | 2 +- client/app/components/HelpTrigger.jsx | 394 +- client/app/components/HelpTrigger.less | 2 +- client/app/components/Link.jsx | 26 - client/app/components/Link.tsx | 35 + .../app/components/ParameterMappingInput.jsx | 18 +- .../app/components/ParameterMappingInput.less | 2 +- client/app/components/ParameterValueInput.jsx | 23 +- .../app/components/ParameterValueInput.less | 2 +- client/app/components/Parameters.jsx | 21 +- client/app/components/Parameters.less | 4 +- .../PermissionsEditorDialog/index.jsx | 9 +- client/app/components/PlainButton.less | 22 + client/app/components/PlainButton.tsx | 20 + .../components/QueryBasedParameterInput.jsx | 20 +- client/app/components/QueryLink.jsx | 6 +- client/app/components/QuerySelector.jsx | 15 +- client/app/components/Resizable/index.jsx | 3 + .../components/SelectWithVirtualScroll.tsx | 45 + client/app/components/TagsList.jsx | 82 - client/app/components/TagsList.less | 44 +- client/app/components/TagsList.tsx | 107 + client/app/components/TimeAgo.jsx | 10 +- .../users => }/components/UserGroups.jsx | 14 +- client/app/components/UserGroups.less | 7 + client/app/components/admin/RQStatus.jsx | 10 +- .../app/components/cards-list/CardsList.jsx | 84 - .../app/components/cards-list/CardsList.less | 7 +- .../app/components/cards-list/CardsList.tsx | 81 + .../components/dashboards/AddWidgetDialog.jsx | 69 +- .../components/dashboards/DashboardGrid.jsx | 14 +- .../dashboards/ExpandedWidgetDialog.jsx | 9 +- .../components/dashboards/dashboard-grid.less | 4 +- .../dashboard-widget/VisualizationWidget.jsx | 68 +- .../dashboards/dashboard-widget/Widget.less | 11 +- .../components/dynamic-form/DynamicForm.less | 2 +- .../dynamic-parameters/DateParameter.jsx | 110 +- .../dynamic-parameters/DateRangeParameter.jsx | 119 +- .../dynamic-parameters/DynamicButton.jsx | 10 +- .../dynamic-parameters/DynamicButton.less | 6 + .../dynamic-parameters/DynamicDatePicker.jsx | 112 + .../DynamicDateRangePicker.jsx | 115 + .../dynamic-parameters/DynamicParameters.less | 25 +- .../components/empty-state/EmptyState.d.ts | 15 +- .../app/components/empty-state/EmptyState.jsx | 93 +- .../components/empty-state/empty-state.less | 34 +- .../components/groups/DetailsPageSidebar.jsx | 16 + .../items-list/components/ItemsTable.jsx | 20 +- .../items-list/components/Sidebar.jsx | 10 +- .../hooks/useItemsListExtraActions.js | 77 + .../components/queries/ApiKeyDialog/index.jsx | 15 +- .../components/queries/EmbedQueryDialog.jsx | 12 +- .../components/queries/EmbedQueryDialog.less | 2 +- .../queries/QueryEditor/AutoLimitCheckbox.jsx | 37 + .../QueryEditor/QueryEditorControls.jsx | 8 + .../QueryEditor/QueryEditorControls.less | 6 + .../app/components/queries/ScheduleDialog.jsx | 22 +- .../app/components/queries/SchemaBrowser.jsx | 30 +- .../__snapshots__/ScheduleDialog.test.js.snap | 140 - .../queries/add-to-dashboard-dialog.less | 5 +- .../databricks/DatabricksSchemaBrowser.jsx | 4 - .../databricks/DatabricksSchemaBrowser.less | 2 +- .../databricks/useDatabricksSchema.js | 46 +- .../visualizations/VisualizationRenderer.jsx | 39 +- .../visualizationComponents.jsx | 31 + client/app/index.html | 27 +- client/app/lib/calculateTextWidth.ts | 20 + client/app/lib/queryFormat.test.js | 56 + client/app/lib/queryFormat.ts | 23 + client/app/lib/useQueryResultData.js | 1 + client/app/lib/utils.js | 82 +- client/app/multi_org.html | 2 +- client/app/pages/alert/AlertEdit.jsx | 2 + client/app/pages/alert/AlertView.jsx | 2 + .../app/pages/alert/components/MenuButton.jsx | 2 +- client/app/pages/alerts/AlertsList.jsx | 19 +- client/app/pages/dashboards/DashboardList.jsx | 208 +- client/app/pages/dashboards/DashboardPage.jsx | 103 +- .../app/pages/dashboards/DashboardPage.less | 9 +- .../dashboards/components/DashboardHeader.jsx | 59 +- .../components/DashboardHeader.less | 2 +- .../components/DashboardListEmptyState.tsx | 37 +- .../pages/dashboards/hooks/useDashboard.js | 5 +- .../pages/dashboards/hooks/useDataSources.js | 28 + .../app/pages/data-sources/EditDataSource.jsx | 4 +- .../pages/destinations/EditDestination.jsx | 4 +- client/app/pages/groups/GroupDashboard.jsx | 273 + client/app/pages/groups/GroupDataSources.jsx | 7 + client/app/pages/groups/GroupMembers.jsx | 6 + client/app/pages/groups/GroupsList.jsx | 1 + client/app/pages/home/Home.jsx | 119 +- .../pages/home/components/FavoritesList.jsx | 94 + client/app/pages/index.js | 1 + client/app/pages/queries-list/QueriesList.jsx | 249 +- .../queries-list/QueriesListEmptyState.jsx | 35 +- client/app/pages/queries/QuerySource.jsx | 61 +- client/app/pages/queries/QueryView.jsx | 41 +- .../app/pages/queries/VisualizationEmbed.jsx | 4 +- .../components/QueryExecutionMetadata.jsx | 14 + .../queries/components/QueryPageHeader.jsx | 14 +- .../queries/components/QuerySourceAlerts.jsx | 11 +- .../components/QuerySourceDropdown.jsx | 38 + .../components/QuerySourceDropdownItem.jsx | 24 + .../components/QuerySourceTypeIcon.jsx | 11 + .../components/QueryVisualizationTabs.jsx | 23 +- .../pages/queries/hooks/useAutoLimitFlags.js | 24 + .../pages/queries/hooks/useDuplicateQuery.js | 17 +- .../app/pages/queries/hooks/useFormatQuery.js | 19 - client/app/pages/queries/hooks/useQuery.js | 9 +- .../queries/hooks/useQueryDataSources.js | 2 + .../app/pages/queries/hooks/useQueryFlags.js | 4 +- .../pages/queries/hooks/useUpdateQuery.jsx | 4 +- .../pages/settings/OrganizationSettings.jsx | 82 +- .../AuthSettings/PasswordLoginSettings.jsx | 33 +- .../components/AuthSettings/SAMLSettings.jsx | 96 +- .../GeneralSettings/BeaconConsentSettings.jsx | 21 +- .../GeneralSettings/FeatureFlagsSettings.jsx | 65 +- .../GeneralSettings/FormatSettings.jsx | 45 +- .../GeneralSettings/PlotlySettings.jsx | 19 +- .../pages/settings/components/prop-types.js | 2 + .../settings/hooks/useOrganizationSettings.js | 65 + client/app/pages/users/UsersList.jsx | 7 +- .../users/components/CreateUserDialog.jsx | 53 +- .../users/components/EditableUserProfile.jsx | 2 +- .../users/components/ReadOnlyUserProfile.jsx | 4 +- .../pages/users/components/UserInfoForm.jsx | 2 +- client/app/redash-font/style.less | 21 +- client/app/services/auth.js | 20 +- client/app/services/auth.test.js | 41 + client/app/services/axios.js | 44 +- client/app/services/dashboard.js | 14 +- client/app/services/group.js | 4 + client/app/services/notification.d.ts | 21 +- .../services/parameters/DateRangeParameter.js | 73 +- client/app/services/policy/DefaultPolicy.js | 10 +- client/app/services/query-result.js | 11 +- client/app/services/query.js | 29 +- client/app/services/restoreSession.jsx | 91 + client/app/services/sanitize.js | 2 + client/app/services/widget.js | 26 +- client/cypress/cypress.js | 6 +- .../integration/dashboard/dashboard_spec.js | 1 + .../dashboard/parameter_mapping_spec.js | 111 - .../integration/dashboard/parameter_spec.js | 164 + .../integration/dashboard/sharing_spec.js | 31 + .../integration/embed/share_embed_spec.js | 38 + .../integration/query/parameter_spec.js | 46 +- .../settings/organization_settings_spec.js | 32 +- .../integration/user/create_user_spec.js | 28 + .../integration/visualizations/chart_spec.js | 110 + .../visualizations/choropleth_spec.js | 14 +- .../integration/visualizations/cohort_spec.js | 8 +- .../visualizations/counter_spec.js | 34 +- .../integration/visualizations/funnel_spec.js | 4 +- .../integration/visualizations/pivot_spec.js | 3 +- .../visualizations/sankey_sunburst_spec.js | 8 +- .../visualizations/word_cloud_spec.js | 14 +- client/cypress/plugins/index.js | 7 +- client/cypress/support/commands.js | 10 + client/cypress/support/index.js | 1 + client/cypress/support/parameters.js | 13 + client/cypress/support/redash-api/index.js | 4 + .../cypress/support/visualizations/chart.js | 100 + client/cypress/tsconfig.json | 7 + client/tsconfig.json | 19 +- cypress.json | 5 +- helm/values.yaml | 14 +- migrations/versions/0ec979123ba4_.py | 28 + .../89bc7873a3e0_fix_multiple_heads.py | 24 + ...d7d747033183_encrypt_alert_destinations.py | 64 + netlify.toml | 5 + package-lock.json | 8664 +++++++++++++++-- package.json | 40 +- redash/authentication/saml_auth.py | 44 +- redash/cli/database.py | 66 +- redash/cli/rq.py | 56 +- redash/destinations/chatwork.py | 1 + redash/destinations/hangoutschat.py | 1 + redash/destinations/hipchat.py | 1 + redash/destinations/mattermost.py | 1 + redash/destinations/pagerduty.py | 1 + redash/destinations/slack.py | 1 + redash/destinations/webhook.py | 2 +- redash/handlers/api.py | 7 +- redash/handlers/authentication.py | 1 + redash/handlers/base.py | 21 +- redash/handlers/dashboards.py | 70 +- redash/handlers/queries.py | 48 +- redash/handlers/query_results.py | 16 +- redash/handlers/users.py | 20 +- redash/models/__init__.py | 88 +- redash/query_runner/__init__.py | 39 +- redash/query_runner/databricks.py | 46 +- redash/query_runner/mongodb.py | 9 + redash/query_runner/oracle.py | 1 + redash/query_runner/pg.py | 2 +- redash/query_runner/treasuredata.py | 1 + redash/query_runner/trino.py | 154 + redash/query_runner/yandex_metrica.py | 1 + redash/security.py | 1 + redash/serializers/__init__.py | 23 +- redash/settings/__init__.py | 17 +- redash/settings/helpers.py | 5 + redash/settings/organization.py | 14 +- redash/tasks/__init__.py | 1 - redash/tasks/general.py | 39 +- redash/tasks/queries/execution.py | 55 +- redash/tasks/queries/maintenance.py | 11 +- redash/tasks/schedule.py | 11 +- redash/tasks/worker.py | 22 +- redash/templates/layouts/signed_out.html | 134 +- redash/templates/setup.html | 77 +- redash/utils/__init__.py | 35 +- redash/worker.py | 2 +- requirements.txt | 9 +- requirements_all_ds.txt | 3 +- tests/handlers/test_queries.py | 16 + tests/handlers/test_query_results.py | 64 +- tests/models/test_dashboards.py | 22 + .../query_runner/test_basesql_queryrunner.py | 102 + tests/query_runner/test_mongodb.py | 29 + tests/tasks/test_queries.py | 56 +- tests/tasks/test_refresh_queries.py | 129 +- tests/test_migrations.py | 20 + tests/test_models.py | 32 + tests/utils/test_query_limit.py | 41 + viz-lib/.babelrc | 9 +- viz-lib/jsconfig.json | 9 - viz-lib/package-lock.json | 2645 ++++- viz-lib/package.json | 24 +- .../ColorPicker/{Input.jsx => Input.tsx} | 33 +- .../ColorPicker/{Label.jsx => Label.tsx} | 23 +- .../ColorPicker/{Swatch.jsx => Swatch.tsx} | 23 +- .../ColorPicker/{index.jsx => index.tsx} | 71 +- .../ColorPicker/{utils.js => utils.ts} | 4 +- .../{ErrorBoundary.jsx => ErrorBoundary.tsx} | 39 +- .../{HtmlContent.jsx => HtmlContent.tsx} | 3 + .../{index.jsx => index.tsx} | 14 +- ...nteractive.jsx => JsonViewInteractive.tsx} | 26 +- .../sortable/{index.jsx => index.tsx} | 41 +- .../{ContextHelp.jsx => ContextHelp.tsx} | 17 +- .../editor/{Section.jsx => Section.tsx} | 31 +- .../editor/{Switch.jsx => Switch.tsx} | 18 +- .../editor/{TextArea.jsx => TextArea.tsx} | 5 +- .../editor/createTabbedEditor.tsx | 57 + .../editor/{index.js => index.ts} | 0 ...hControlLabel.jsx => withControlLabel.tsx} | 35 +- viz-lib/src/{index.js => index.ts} | 0 ...und.js => chooseTextColorForBackground.ts} | 2 +- ...epCompare.js => useMemoWithDeepCompare.ts} | 3 +- viz-lib/src/lib/referenceCountingCache.ts | 46 + viz-lib/src/lib/{utils.js => utils.ts} | 6 +- .../{value-format.jsx => value-format.tsx} | 30 +- .../{resizeObserver.js => resizeObserver.ts} | 2 +- .../src/services/{sanitize.js => sanitize.ts} | 2 + .../{ColorPalette.js => ColorPalette.ts} | 0 .../visualizations/{Editor.jsx => Editor.tsx} | 17 +- .../{Renderer.jsx => Renderer.tsx} | 31 +- .../box-plot/{Editor.jsx => Editor.tsx} | 15 +- .../box-plot/{Renderer.jsx => Renderer.tsx} | 28 +- .../box-plot/{d3box.js => d3box.ts} | 40 +- .../box-plot/{index.js => index.ts} | 4 +- .../chart/Editor/AxisSettings.jsx | 110 - .../chart/Editor/AxisSettings.tsx | 125 + .../chart/Editor/ChartTypeSelect.jsx | 36 - .../chart/Editor/ChartTypeSelect.tsx | 54 + ...ttings.test.js => ColorsSettings.test.tsx} | 6 +- ...{ColorsSettings.jsx => ColorsSettings.tsx} | 6 +- ...pingSelect.jsx => ColumnMappingSelect.tsx} | 43 +- .../chart/Editor/CustomChartSettings.jsx | 48 - .../chart/Editor/CustomChartSettings.tsx | 63 + ...gs.test.js => DataLabelsSettings.test.tsx} | 4 +- ...elsSettings.jsx => DataLabelsSettings.tsx} | 19 +- ...Settings.jsx => DefaultColorsSettings.tsx} | 18 +- .../chart/Editor/GeneralSettings.jsx | 303 - ...tings.test.js => GeneralSettings.test.tsx} | 11 +- .../chart/Editor/GeneralSettings.tsx | 350 + ...Settings.jsx => HeatmapColorsSettings.tsx} | 18 +- ...lorsSettings.jsx => PieColorsSettings.tsx} | 20 +- ...ttings.test.js => SeriesSettings.test.tsx} | 4 +- ...{SeriesSettings.jsx => SeriesSettings.tsx} | 62 +- .../chart/Editor/XAxisSettings.jsx | 47 - ...ettings.test.js => XAxisSettings.test.tsx} | 4 +- .../chart/Editor/XAxisSettings.tsx | 66 + ...ettings.test.js => YAxisSettings.test.tsx} | 8 +- .../chart/Editor/YAxisSettings.tsx | 102 + ...t.js.snap => ColorsSettings.test.tsx.snap} | 0 ....snap => DataLabelsSettings.test.tsx.snap} | 0 ....js.snap => GeneralSettings.test.tsx.snap} | 8 + ...t.js.snap => SeriesSettings.test.tsx.snap} | 0 ...st.js.snap => XAxisSettings.test.tsx.snap} | 0 ...st.js.snap => YAxisSettings.test.tsx.snap} | 0 .../Editor/{index.test.js => index.test.tsx} | 6 +- .../chart/Editor/{index.jsx => index.tsx} | 20 +- ...mPlotlyChart.jsx => CustomPlotlyChart.tsx} | 8 +- .../{PlotlyChart.jsx => PlotlyChart.tsx} | 22 +- .../chart/Renderer/{index.jsx => index.tsx} | 5 +- .../Renderer/{initChart.js => initChart.ts} | 64 +- ...ChartData.test.js => getChartData.test.ts} | 0 .../{getChartData.js => getChartData.ts} | 20 +- .../chart/{getOptions.js => getOptions.ts} | 5 +- .../chart/{index.js => index.ts} | 0 ...ustomChartUtils.js => customChartUtils.ts} | 13 +- .../src/visualizations/chart/plotly/index.ts | 40 + ...repareData.test.js => prepareData.test.ts} | 7 +- .../plotly/{prepareData.js => prepareData.ts} | 3 +- ...reDefaultData.js => prepareDefaultData.ts} | 36 +- ...reGroupedData.js => prepareGroupedData.ts} | 20 +- ...oupedLayout.js => prepareGroupedLayout.ts} | 5 +- ...reHeatmapData.js => prepareHeatmapData.ts} | 10 +- ...reLayout.test.js => prepareLayout.test.ts} | 0 .../{prepareLayout.js => prepareLayout.ts} | 23 +- .../{preparePieData.js => preparePieData.ts} | 17 +- .../plotly/{index.js => remove_index.js} | 0 .../visualizations/chart/plotly/updateAxes.ts | 118 + ...{updateChartSize.js => updateChartSize.ts} | 12 +- .../plotly/{updateData.js => updateData.ts} | 45 +- .../chart/plotly/updateYRanges.js | 44 - .../chart/plotly/{utils.js => utils.ts} | 7 +- .../{ColorPalette.js => ColorPalette.ts} | 0 .../choropleth/Editor/BoundsSettings.jsx | 67 - .../choropleth/Editor/BoundsSettings.tsx | 108 + .../choropleth/Editor/ColorsSettings.jsx | 116 - .../choropleth/Editor/ColorsSettings.tsx | 137 + .../choropleth/Editor/FormatSettings.jsx | 184 - .../choropleth/Editor/FormatSettings.tsx | 196 + .../choropleth/Editor/GeneralSettings.jsx | 102 - .../choropleth/Editor/GeneralSettings.tsx | 112 + .../choropleth/Editor/{index.js => index.ts} | 0 .../visualizations/choropleth/Editor/utils.js | 38 - .../visualizations/choropleth/Editor/utils.ts | 34 + .../Renderer/{Legend.jsx => Legend.tsx} | 24 +- .../choropleth/Renderer/index.jsx | 77 - .../choropleth/Renderer/index.tsx | 64 + .../{initChoropleth.js => initChoropleth.tsx} | 81 +- .../Renderer/{utils.js => utils.ts} | 35 +- .../visualizations/choropleth/getOptions.js | 37 - .../visualizations/choropleth/getOptions.ts | 64 + .../choropleth/hooks/useLoadGeoJson.ts | 42 + .../choropleth/{index.js => index.ts} | 0 .../choropleth/maps/convert-projection.ts | 43 + .../maps/japan.prefectures.geo.json | 55 +- .../choropleth/maps/usa-albers.geo.json | 1 + .../choropleth/maps/usa.geo.json | 1 + .../cohort/{Cornelius.jsx => Cornelius.tsx} | 90 +- ...nceSettings.jsx => AppearanceSettings.tsx} | 26 +- ...{ColorsSettings.jsx => ColorsSettings.tsx} | 18 +- .../cohort/Editor/ColumnsSettings.jsx | 72 - .../cohort/Editor/ColumnsSettings.tsx | 88 + ...ptionsSettings.jsx => OptionsSettings.tsx} | 15 +- .../cohort/Editor/{index.js => index.ts} | 0 .../cohort/{Renderer.jsx => Renderer.tsx} | 5 +- .../cohort/{getOptions.js => getOptions.ts} | 2 +- .../cohort/{index.js => index.ts} | 0 .../cohort/{prepareData.js => prepareData.ts} | 30 +- .../counter/Editor/FormatSettings.jsx | 78 - .../counter/Editor/FormatSettings.tsx | 92 + .../counter/Editor/GeneralSettings.jsx | 85 - .../counter/Editor/GeneralSettings.tsx | 105 + .../counter/Editor/{index.js => index.ts} | 0 .../counter/{Renderer.jsx => Renderer.tsx} | 18 +- .../counter/{index.js => index.ts} | 5 +- .../counter/{utils.test.js => utils.test.ts} | 2 +- .../counter/{utils.js => utils.ts} | 34 +- ...etailsRenderer.jsx => DetailsRenderer.tsx} | 9 +- .../details/{index.js => index.ts} | 5 +- ...nceSettings.jsx => AppearanceSettings.tsx} | 20 +- .../funnel/Editor/GeneralSettings.jsx | 116 - .../funnel/Editor/GeneralSettings.tsx | 137 + .../funnel/Editor/{index.js => index.ts} | 0 .../Renderer/{FunnelBar.jsx => FunnelBar.tsx} | 21 +- .../funnel/Renderer/{index.jsx => index.tsx} | 31 +- .../{prepareData.js => prepareData.ts} | 7 +- .../funnel/{getOptions.js => getOptions.ts} | 4 +- .../funnel/{index.js => index.ts} | 0 .../src/visualizations/{index.js => index.ts} | 0 ...{FormatSettings.jsx => FormatSettings.tsx} | 14 +- ...eneralSettings.jsx => GeneralSettings.tsx} | 23 +- ...{GroupsSettings.jsx => GroupsSettings.tsx} | 17 +- .../{StyleSettings.jsx => StyleSettings.tsx} | 47 +- .../map/Editor/{index.js => index.ts} | 0 .../map/{Renderer.jsx => Renderer.tsx} | 16 +- .../map/{getOptions.js => getOptions.ts} | 29 +- .../visualizations/map/{index.js => index.ts} | 0 .../map/{initMap.js => initMap.ts} | 39 +- .../map/{prepareData.js => prepareData.ts} | 3 +- viz-lib/src/visualizations/pivot/Editor.jsx | 42 - viz-lib/src/visualizations/pivot/Editor.tsx | 61 + .../pivot/{Renderer.jsx => Renderer.tsx} | 14 +- .../pivot/{index.js => index.ts} | 2 +- viz-lib/src/visualizations/prop-types.js | 31 - viz-lib/src/visualizations/prop-types.ts | 46 + ...zations.js => registeredVisualizations.ts} | 58 +- .../sankey/{Editor.jsx => Editor.tsx} | 0 .../sankey/{Renderer.jsx => Renderer.tsx} | 9 +- .../sankey/{d3sankey.js => d3sankey.ts} | 120 +- viz-lib/src/visualizations/sankey/index.js | 12 - viz-lib/src/visualizations/sankey/index.ts | 26 + .../sankey/{initSankey.js => initSankey.ts} | 127 +- .../sunburst/{Editor.jsx => Editor.tsx} | 2 + .../sunburst/{Renderer.jsx => Renderer.tsx} | 5 +- .../sunburst/{index.js => index.ts} | 4 +- .../{initSunburst.js => initSunburst.ts} | 66 +- .../table/Editor/ColumnEditor.jsx | 89 - .../table/Editor/ColumnEditor.tsx | 100 + ...tings.test.js => ColumnsSettings.test.tsx} | 4 +- ...olumnsSettings.jsx => ColumnsSettings.tsx} | 17 +- ...Settings.test.js => GridSettings.test.tsx} | 4 +- .../{GridSettings.jsx => GridSettings.tsx} | 10 +- ....js.snap => ColumnsSettings.test.tsx.snap} | 0 ...est.js.snap => GridSettings.test.tsx.snap} | 0 .../table/Editor/{index.jsx => index.tsx} | 0 .../table/{Renderer.jsx => Renderer.tsx} | 38 +- ...ean.test.js.snap => boolean.test.tsx.snap} | 0 ...me.test.js.snap => datetime.test.tsx.snap} | 0 ...image.test.js.snap => image.test.tsx.snap} | 0 .../{link.test.js.snap => link.test.tsx.snap} | 0 ...mber.test.js.snap => number.test.tsx.snap} | 0 .../{text.test.js.snap => text.test.tsx.snap} | 0 .../{boolean.test.js => boolean.test.tsx} | 5 +- .../columns/{boolean.jsx => boolean.tsx} | 38 +- .../{datetime.test.js => datetime.test.tsx} | 5 +- .../columns/{datetime.jsx => datetime.tsx} | 30 +- .../columns/{image.test.js => image.test.tsx} | 5 +- .../table/columns/{image.jsx => image.tsx} | 56 +- .../table/columns/{index.js => index.ts} | 0 .../table/columns/{json.jsx => json.tsx} | 8 +- .../columns/{link.test.js => link.test.tsx} | 5 +- .../table/columns/{link.jsx => link.tsx} | 49 +- .../{number.test.js => number.test.tsx} | 5 +- .../table/columns/{number.jsx => number.tsx} | 30 +- .../columns/{text.test.js => text.test.tsx} | 5 +- .../table/columns/{text.jsx => text.tsx} | 31 +- .../table/{getOptions.js => getOptions.ts} | 20 +- .../table/{index.js => index.ts} | 0 .../table/{utils.js => utils.tsx} | 38 +- ...Settings.js => visualizationsSettings.tsx} | 21 +- .../src/visualizations/word-cloud/Editor.jsx | 95 - .../src/visualizations/word-cloud/Editor.tsx | 113 + .../word-cloud/{Renderer.jsx => Renderer.tsx} | 44 +- .../word-cloud/{index.js => index.ts} | 2 +- viz-lib/tsconfig.json | 22 + viz-lib/webpack.config.js | 6 +- webpack.config.js | 44 +- worker.conf | 1 + 474 files changed, 20589 insertions(+), 6405 deletions(-) create mode 100644 client/app/assets/images/db-logos/trino.png create mode 100644 client/app/components/ApplicationArea/ErrorMessageDetails.jsx rename client/app/components/ApplicationArea/{routeWithUserSession.jsx => routeWithUserSession.tsx} (51%) delete mode 100644 client/app/components/Link.jsx create mode 100644 client/app/components/Link.tsx create mode 100644 client/app/components/PlainButton.less create mode 100644 client/app/components/PlainButton.tsx create mode 100644 client/app/components/SelectWithVirtualScroll.tsx delete mode 100644 client/app/components/TagsList.jsx create mode 100644 client/app/components/TagsList.tsx rename client/app/{pages/users => }/components/UserGroups.jsx (50%) create mode 100644 client/app/components/UserGroups.less delete mode 100644 client/app/components/cards-list/CardsList.jsx create mode 100644 client/app/components/cards-list/CardsList.tsx create mode 100644 client/app/components/dynamic-parameters/DynamicDatePicker.jsx create mode 100644 client/app/components/dynamic-parameters/DynamicDateRangePicker.jsx create mode 100644 client/app/components/items-list/hooks/useItemsListExtraActions.js create mode 100644 client/app/components/queries/QueryEditor/AutoLimitCheckbox.jsx create mode 100644 client/app/lib/calculateTextWidth.ts create mode 100644 client/app/lib/queryFormat.test.js create mode 100644 client/app/lib/queryFormat.ts create mode 100644 client/app/pages/dashboards/hooks/useDataSources.js create mode 100644 client/app/pages/groups/GroupDashboard.jsx create mode 100644 client/app/pages/home/components/FavoritesList.jsx create mode 100644 client/app/pages/queries/components/QuerySourceDropdown.jsx create mode 100644 client/app/pages/queries/components/QuerySourceDropdownItem.jsx create mode 100644 client/app/pages/queries/components/QuerySourceTypeIcon.jsx create mode 100644 client/app/pages/queries/hooks/useAutoLimitFlags.js delete mode 100644 client/app/pages/queries/hooks/useFormatQuery.js create mode 100644 client/app/pages/settings/hooks/useOrganizationSettings.js create mode 100644 client/app/services/auth.test.js create mode 100644 client/app/services/restoreSession.jsx delete mode 100644 client/cypress/integration/dashboard/parameter_mapping_spec.js create mode 100644 client/cypress/integration/dashboard/parameter_spec.js create mode 100644 client/cypress/integration/user/create_user_spec.js create mode 100644 client/cypress/integration/visualizations/chart_spec.js create mode 100644 client/cypress/support/parameters.js create mode 100644 client/cypress/support/visualizations/chart.js create mode 100644 client/cypress/tsconfig.json create mode 100644 migrations/versions/0ec979123ba4_.py create mode 100644 migrations/versions/89bc7873a3e0_fix_multiple_heads.py create mode 100644 migrations/versions/d7d747033183_encrypt_alert_destinations.py create mode 100644 redash/query_runner/trino.py create mode 100644 tests/query_runner/test_basesql_queryrunner.py create mode 100644 tests/test_migrations.py create mode 100644 tests/utils/test_query_limit.py delete mode 100644 viz-lib/jsconfig.json rename viz-lib/src/components/ColorPicker/{Input.jsx => Input.tsx} (79%) rename viz-lib/src/components/ColorPicker/{Label.jsx => Label.tsx} (61%) rename viz-lib/src/components/ColorPicker/{Swatch.jsx => Swatch.tsx} (60%) rename viz-lib/src/components/ColorPicker/{index.jsx => index.tsx} (61%) rename viz-lib/src/components/ColorPicker/{utils.js => utils.ts} (71%) rename viz-lib/src/components/{ErrorBoundary.jsx => ErrorBoundary.tsx} (51%) rename viz-lib/src/components/{HtmlContent.jsx => HtmlContent.tsx} (51%) rename viz-lib/src/components/TextAlignmentSelect/{index.jsx => index.tsx} (87%) rename viz-lib/src/components/json-view-interactive/{JsonViewInteractive.jsx => JsonViewInteractive.tsx} (88%) rename viz-lib/src/components/sortable/{index.jsx => index.tsx} (61%) rename viz-lib/src/components/visualizations/editor/{ContextHelp.jsx => ContextHelp.tsx} (79%) rename viz-lib/src/components/visualizations/editor/{Section.jsx => Section.tsx} (50%) rename viz-lib/src/components/visualizations/editor/{Switch.jsx => Switch.tsx} (76%) rename viz-lib/src/components/visualizations/editor/{TextArea.jsx => TextArea.tsx} (84%) create mode 100644 viz-lib/src/components/visualizations/editor/createTabbedEditor.tsx rename viz-lib/src/components/visualizations/editor/{index.js => index.ts} (100%) rename viz-lib/src/components/visualizations/editor/{withControlLabel.jsx => withControlLabel.tsx} (72%) rename viz-lib/src/{index.js => index.ts} (100%) rename viz-lib/src/lib/{chooseTextColorForBackground.js => chooseTextColorForBackground.ts} (85%) rename viz-lib/src/lib/hooks/{useMemoWithDeepCompare.js => useMemoWithDeepCompare.ts} (52%) create mode 100644 viz-lib/src/lib/referenceCountingCache.ts rename viz-lib/src/lib/{utils.js => utils.ts} (83%) rename viz-lib/src/lib/{value-format.jsx => value-format.tsx} (75%) rename viz-lib/src/services/{resizeObserver.js => resizeObserver.ts} (92%) rename viz-lib/src/services/{sanitize.js => sanitize.ts} (96%) rename viz-lib/src/visualizations/{ColorPalette.js => ColorPalette.ts} (100%) rename viz-lib/src/visualizations/{Editor.jsx => Editor.tsx} (59%) rename viz-lib/src/visualizations/{Renderer.jsx => Renderer.tsx} (69%) rename viz-lib/src/visualizations/box-plot/{Editor.jsx => Editor.tsx} (54%) rename viz-lib/src/visualizations/box-plot/{Renderer.jsx => Renderer.tsx} (73%) rename viz-lib/src/visualizations/box-plot/{d3box.js => d3box.ts} (78%) rename viz-lib/src/visualizations/box-plot/{index.js => index.ts} (79%) delete mode 100644 viz-lib/src/visualizations/chart/Editor/AxisSettings.jsx create mode 100644 viz-lib/src/visualizations/chart/Editor/AxisSettings.tsx delete mode 100644 viz-lib/src/visualizations/chart/Editor/ChartTypeSelect.jsx create mode 100644 viz-lib/src/visualizations/chart/Editor/ChartTypeSelect.tsx rename viz-lib/src/visualizations/chart/Editor/{ColorsSettings.test.js => ColorsSettings.test.tsx} (95%) rename viz-lib/src/visualizations/chart/Editor/{ColorsSettings.jsx => ColorsSettings.tsx} (70%) rename viz-lib/src/visualizations/chart/Editor/{ColumnMappingSelect.jsx => ColumnMappingSelect.tsx} (52%) delete mode 100644 viz-lib/src/visualizations/chart/Editor/CustomChartSettings.jsx create mode 100644 viz-lib/src/visualizations/chart/Editor/CustomChartSettings.tsx rename viz-lib/src/visualizations/chart/Editor/{DataLabelsSettings.test.js => DataLabelsSettings.test.tsx} (95%) rename viz-lib/src/visualizations/chart/Editor/{DataLabelsSettings.jsx => DataLabelsSettings.tsx} (70%) rename viz-lib/src/visualizations/chart/Editor/{DefaultColorsSettings.jsx => DefaultColorsSettings.tsx} (55%) delete mode 100644 viz-lib/src/visualizations/chart/Editor/GeneralSettings.jsx rename viz-lib/src/visualizations/chart/Editor/{GeneralSettings.test.js => GeneralSettings.test.tsx} (91%) create mode 100644 viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx rename viz-lib/src/visualizations/chart/Editor/{HeatmapColorsSettings.jsx => HeatmapColorsSettings.tsx} (59%) rename viz-lib/src/visualizations/chart/Editor/{PieColorsSettings.jsx => PieColorsSettings.tsx} (58%) rename viz-lib/src/visualizations/chart/Editor/{SeriesSettings.test.js => SeriesSettings.test.tsx} (95%) rename viz-lib/src/visualizations/chart/Editor/{SeriesSettings.jsx => SeriesSettings.tsx} (61%) delete mode 100644 viz-lib/src/visualizations/chart/Editor/XAxisSettings.jsx rename viz-lib/src/visualizations/chart/Editor/{XAxisSettings.test.js => XAxisSettings.test.tsx} (95%) create mode 100644 viz-lib/src/visualizations/chart/Editor/XAxisSettings.tsx rename viz-lib/src/visualizations/chart/Editor/{YAxisSettings.test.js => YAxisSettings.test.tsx} (91%) create mode 100644 viz-lib/src/visualizations/chart/Editor/YAxisSettings.tsx rename viz-lib/src/visualizations/chart/Editor/__snapshots__/{ColorsSettings.test.js.snap => ColorsSettings.test.tsx.snap} (100%) rename viz-lib/src/visualizations/chart/Editor/__snapshots__/{DataLabelsSettings.test.js.snap => DataLabelsSettings.test.tsx.snap} (100%) rename viz-lib/src/visualizations/chart/Editor/__snapshots__/{GeneralSettings.test.js.snap => GeneralSettings.test.tsx.snap} (86%) rename viz-lib/src/visualizations/chart/Editor/__snapshots__/{SeriesSettings.test.js.snap => SeriesSettings.test.tsx.snap} (100%) rename viz-lib/src/visualizations/chart/Editor/__snapshots__/{XAxisSettings.test.js.snap => XAxisSettings.test.tsx.snap} (100%) rename viz-lib/src/visualizations/chart/Editor/__snapshots__/{YAxisSettings.test.js.snap => YAxisSettings.test.tsx.snap} (100%) rename viz-lib/src/visualizations/chart/Editor/{index.test.js => index.test.tsx} (93%) rename viz-lib/src/visualizations/chart/Editor/{index.jsx => index.tsx} (62%) rename viz-lib/src/visualizations/chart/Renderer/{CustomPlotlyChart.jsx => CustomPlotlyChart.tsx} (71%) rename viz-lib/src/visualizations/chart/Renderer/{PlotlyChart.jsx => PlotlyChart.tsx} (74%) rename viz-lib/src/visualizations/chart/Renderer/{index.jsx => index.tsx} (89%) rename viz-lib/src/visualizations/chart/Renderer/{initChart.js => initChart.ts} (80%) rename viz-lib/src/visualizations/chart/{getChartData.test.js => getChartData.test.ts} (100%) rename viz-lib/src/visualizations/chart/{getChartData.js => getChartData.ts} (54%) rename viz-lib/src/visualizations/chart/{getOptions.js => getOptions.ts} (94%) rename viz-lib/src/visualizations/chart/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/chart/plotly/{customChartUtils.js => customChartUtils.ts} (52%) create mode 100644 viz-lib/src/visualizations/chart/plotly/index.ts rename viz-lib/src/visualizations/chart/plotly/{prepareData.test.js => prepareData.test.ts} (98%) rename viz-lib/src/visualizations/chart/plotly/{prepareData.js => prepareData.ts} (77%) rename viz-lib/src/visualizations/chart/plotly/{prepareDefaultData.js => prepareDefaultData.ts} (80%) rename viz-lib/src/visualizations/chart/plotly/{prepareGroupedData.js => prepareGroupedData.ts} (71%) rename viz-lib/src/visualizations/chart/plotly/{prepareGroupedLayout.js => prepareGroupedLayout.ts} (74%) rename viz-lib/src/visualizations/chart/plotly/{prepareHeatmapData.js => prepareHeatmapData.ts} (72%) rename viz-lib/src/visualizations/chart/plotly/{prepareLayout.test.js => prepareLayout.test.ts} (100%) rename viz-lib/src/visualizations/chart/plotly/{prepareLayout.js => prepareLayout.ts} (67%) rename viz-lib/src/visualizations/chart/plotly/{preparePieData.js => preparePieData.ts} (78%) rename viz-lib/src/visualizations/chart/plotly/{index.js => remove_index.js} (100%) create mode 100644 viz-lib/src/visualizations/chart/plotly/updateAxes.ts rename viz-lib/src/visualizations/chart/plotly/{updateChartSize.js => updateChartSize.ts} (89%) rename viz-lib/src/visualizations/chart/plotly/{updateData.js => updateData.ts} (74%) delete mode 100644 viz-lib/src/visualizations/chart/plotly/updateYRanges.js rename viz-lib/src/visualizations/chart/plotly/{utils.js => utils.ts} (64%) rename viz-lib/src/visualizations/choropleth/{ColorPalette.js => ColorPalette.ts} (100%) delete mode 100644 viz-lib/src/visualizations/choropleth/Editor/BoundsSettings.jsx create mode 100644 viz-lib/src/visualizations/choropleth/Editor/BoundsSettings.tsx delete mode 100644 viz-lib/src/visualizations/choropleth/Editor/ColorsSettings.jsx create mode 100644 viz-lib/src/visualizations/choropleth/Editor/ColorsSettings.tsx delete mode 100644 viz-lib/src/visualizations/choropleth/Editor/FormatSettings.jsx create mode 100644 viz-lib/src/visualizations/choropleth/Editor/FormatSettings.tsx delete mode 100644 viz-lib/src/visualizations/choropleth/Editor/GeneralSettings.jsx create mode 100644 viz-lib/src/visualizations/choropleth/Editor/GeneralSettings.tsx rename viz-lib/src/visualizations/choropleth/Editor/{index.js => index.ts} (100%) delete mode 100644 viz-lib/src/visualizations/choropleth/Editor/utils.js create mode 100644 viz-lib/src/visualizations/choropleth/Editor/utils.ts rename viz-lib/src/visualizations/choropleth/Renderer/{Legend.jsx => Legend.tsx} (59%) delete mode 100644 viz-lib/src/visualizations/choropleth/Renderer/index.jsx create mode 100644 viz-lib/src/visualizations/choropleth/Renderer/index.tsx rename viz-lib/src/visualizations/choropleth/Renderer/{initChoropleth.js => initChoropleth.tsx} (58%) rename viz-lib/src/visualizations/choropleth/Renderer/{utils.js => utils.ts} (59%) delete mode 100644 viz-lib/src/visualizations/choropleth/getOptions.js create mode 100644 viz-lib/src/visualizations/choropleth/getOptions.ts create mode 100644 viz-lib/src/visualizations/choropleth/hooks/useLoadGeoJson.ts rename viz-lib/src/visualizations/choropleth/{index.js => index.ts} (100%) create mode 100644 viz-lib/src/visualizations/choropleth/maps/convert-projection.ts create mode 100644 viz-lib/src/visualizations/choropleth/maps/usa-albers.geo.json create mode 100644 viz-lib/src/visualizations/choropleth/maps/usa.geo.json rename viz-lib/src/visualizations/cohort/{Cornelius.jsx => Cornelius.tsx} (62%) rename viz-lib/src/visualizations/cohort/Editor/{AppearanceSettings.jsx => AppearanceSettings.tsx} (54%) rename viz-lib/src/visualizations/cohort/Editor/{ColorsSettings.jsx => ColorsSettings.tsx} (56%) delete mode 100644 viz-lib/src/visualizations/cohort/Editor/ColumnsSettings.jsx create mode 100644 viz-lib/src/visualizations/cohort/Editor/ColumnsSettings.tsx rename viz-lib/src/visualizations/cohort/Editor/{OptionsSettings.jsx => OptionsSettings.tsx} (51%) rename viz-lib/src/visualizations/cohort/Editor/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/cohort/{Renderer.jsx => Renderer.tsx} (94%) rename viz-lib/src/visualizations/cohort/{getOptions.js => getOptions.ts} (92%) rename viz-lib/src/visualizations/cohort/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/cohort/{prepareData.js => prepareData.ts} (68%) delete mode 100644 viz-lib/src/visualizations/counter/Editor/FormatSettings.jsx create mode 100644 viz-lib/src/visualizations/counter/Editor/FormatSettings.tsx delete mode 100644 viz-lib/src/visualizations/counter/Editor/GeneralSettings.jsx create mode 100644 viz-lib/src/visualizations/counter/Editor/GeneralSettings.tsx rename viz-lib/src/visualizations/counter/Editor/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/counter/{Renderer.jsx => Renderer.tsx} (63%) rename viz-lib/src/visualizations/counter/{index.js => index.ts} (84%) rename viz-lib/src/visualizations/counter/{utils.test.js => utils.test.ts} (99%) rename viz-lib/src/visualizations/counter/{utils.js => utils.ts} (57%) rename viz-lib/src/visualizations/details/{DetailsRenderer.jsx => DetailsRenderer.tsx} (83%) rename viz-lib/src/visualizations/details/{index.js => index.ts} (72%) rename viz-lib/src/visualizations/funnel/Editor/{AppearanceSettings.jsx => AppearanceSettings.tsx} (58%) delete mode 100644 viz-lib/src/visualizations/funnel/Editor/GeneralSettings.jsx create mode 100644 viz-lib/src/visualizations/funnel/Editor/GeneralSettings.tsx rename viz-lib/src/visualizations/funnel/Editor/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/funnel/Renderer/{FunnelBar.jsx => FunnelBar.tsx} (68%) rename viz-lib/src/visualizations/funnel/Renderer/{index.jsx => index.tsx} (71%) rename viz-lib/src/visualizations/funnel/Renderer/{prepareData.js => prepareData.ts} (68%) rename viz-lib/src/visualizations/funnel/{getOptions.js => getOptions.ts} (94%) rename viz-lib/src/visualizations/funnel/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/map/Editor/{FormatSettings.jsx => FormatSettings.tsx} (66%) rename viz-lib/src/visualizations/map/Editor/{GeneralSettings.jsx => GeneralSettings.tsx} (51%) rename viz-lib/src/visualizations/map/Editor/{GroupsSettings.jsx => GroupsSettings.tsx} (58%) rename viz-lib/src/visualizations/map/Editor/{StyleSettings.jsx => StyleSettings.tsx} (59%) rename viz-lib/src/visualizations/map/Editor/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/map/{Renderer.jsx => Renderer.tsx} (62%) rename viz-lib/src/visualizations/map/{getOptions.js => getOptions.ts} (50%) rename viz-lib/src/visualizations/map/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/map/{initMap.js => initMap.ts} (70%) rename viz-lib/src/visualizations/map/{prepareData.js => prepareData.ts} (80%) delete mode 100644 viz-lib/src/visualizations/pivot/Editor.jsx create mode 100644 viz-lib/src/visualizations/pivot/Editor.tsx rename viz-lib/src/visualizations/pivot/{Renderer.jsx => Renderer.tsx} (85%) rename viz-lib/src/visualizations/pivot/{index.js => index.ts} (86%) delete mode 100644 viz-lib/src/visualizations/prop-types.js create mode 100644 viz-lib/src/visualizations/prop-types.ts rename viz-lib/src/visualizations/{registeredVisualizations.js => registeredVisualizations.ts} (53%) rename viz-lib/src/visualizations/sankey/{Editor.jsx => Editor.tsx} (100%) rename viz-lib/src/visualizations/sankey/{Renderer.jsx => Renderer.tsx} (61%) rename viz-lib/src/visualizations/sankey/{d3sankey.js => d3sankey.ts} (63%) delete mode 100644 viz-lib/src/visualizations/sankey/index.js create mode 100644 viz-lib/src/visualizations/sankey/index.ts rename viz-lib/src/visualizations/sankey/{initSankey.js => initSankey.ts} (57%) rename viz-lib/src/visualizations/sunburst/{Editor.jsx => Editor.tsx} (82%) rename viz-lib/src/visualizations/sunburst/{Renderer.jsx => Renderer.tsx} (77%) rename viz-lib/src/visualizations/sunburst/{index.js => index.ts} (76%) rename viz-lib/src/visualizations/sunburst/{initSunburst.js => initSunburst.ts} (72%) delete mode 100644 viz-lib/src/visualizations/table/Editor/ColumnEditor.jsx create mode 100644 viz-lib/src/visualizations/table/Editor/ColumnEditor.tsx rename viz-lib/src/visualizations/table/Editor/{ColumnsSettings.test.js => ColumnsSettings.test.tsx} (95%) rename viz-lib/src/visualizations/table/Editor/{ColumnsSettings.jsx => ColumnsSettings.tsx} (79%) rename viz-lib/src/visualizations/table/Editor/{GridSettings.test.js => GridSettings.test.tsx} (90%) rename viz-lib/src/visualizations/table/Editor/{GridSettings.jsx => GridSettings.tsx} (53%) rename viz-lib/src/visualizations/table/Editor/__snapshots__/{ColumnsSettings.test.js.snap => ColumnsSettings.test.tsx.snap} (100%) rename viz-lib/src/visualizations/table/Editor/__snapshots__/{GridSettings.test.js.snap => GridSettings.test.tsx.snap} (100%) rename viz-lib/src/visualizations/table/Editor/{index.jsx => index.tsx} (100%) rename viz-lib/src/visualizations/table/{Renderer.jsx => Renderer.tsx} (65%) rename viz-lib/src/visualizations/table/columns/__snapshots__/{boolean.test.js.snap => boolean.test.tsx.snap} (100%) rename viz-lib/src/visualizations/table/columns/__snapshots__/{datetime.test.js.snap => datetime.test.tsx.snap} (100%) rename viz-lib/src/visualizations/table/columns/__snapshots__/{image.test.js.snap => image.test.tsx.snap} (100%) rename viz-lib/src/visualizations/table/columns/__snapshots__/{link.test.js.snap => link.test.tsx.snap} (100%) rename viz-lib/src/visualizations/table/columns/__snapshots__/{number.test.js.snap => number.test.tsx.snap} (100%) rename viz-lib/src/visualizations/table/columns/__snapshots__/{text.test.js.snap => text.test.tsx.snap} (100%) rename viz-lib/src/visualizations/table/columns/{boolean.test.js => boolean.test.tsx} (82%) rename viz-lib/src/visualizations/table/columns/{boolean.jsx => boolean.tsx} (55%) rename viz-lib/src/visualizations/table/columns/{datetime.test.js => datetime.test.tsx} (77%) rename viz-lib/src/visualizations/table/columns/{datetime.jsx => datetime.tsx} (64%) rename viz-lib/src/visualizations/table/columns/{image.test.js => image.test.tsx} (88%) rename viz-lib/src/visualizations/table/columns/{image.jsx => image.tsx} (51%) rename viz-lib/src/visualizations/table/columns/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/table/columns/{json.jsx => json.tsx} (88%) rename viz-lib/src/visualizations/table/columns/{link.test.js => link.test.tsx} (88%) rename viz-lib/src/visualizations/table/columns/{link.jsx => link.tsx} (54%) rename viz-lib/src/visualizations/table/columns/{number.test.js => number.test.tsx} (76%) rename viz-lib/src/visualizations/table/columns/{number.jsx => number.tsx} (64%) rename viz-lib/src/visualizations/table/columns/{text.test.js => text.test.tsx} (82%) rename viz-lib/src/visualizations/table/columns/{text.jsx => text.tsx} (66%) rename viz-lib/src/visualizations/table/{getOptions.js => getOptions.ts} (78%) rename viz-lib/src/visualizations/table/{index.js => index.ts} (100%) rename viz-lib/src/visualizations/table/{utils.js => utils.tsx} (60%) rename viz-lib/src/visualizations/{visualizationsSettings.js => visualizationsSettings.tsx} (75%) delete mode 100644 viz-lib/src/visualizations/word-cloud/Editor.jsx create mode 100644 viz-lib/src/visualizations/word-cloud/Editor.tsx rename viz-lib/src/visualizations/word-cloud/{Renderer.jsx => Renderer.tsx} (68%) rename viz-lib/src/visualizations/word-cloud/{index.js => index.ts} (84%) create mode 100644 viz-lib/tsconfig.json diff --git a/.circleci/Dockerfile.cypress b/.circleci/Dockerfile.cypress index d4e29881cc..0681a9910f 100644 --- a/.circleci/Dockerfile.cypress +++ b/.circleci/Dockerfile.cypress @@ -1,12 +1,12 @@ -FROM cypress/browsers:chrome67 +FROM cypress/browsers:node14.0.0-chrome84 ENV APP /usr/src/app WORKDIR $APP -COPY package.json $APP/package.json -RUN npm run cypress:install > /dev/null +COPY package.json package-lock.json $APP/ +COPY viz-lib $APP/viz-lib +RUN npm ci > /dev/null -COPY client/cypress $APP/client/cypress -COPY cypress.json $APP/cypress.json +COPY . $APP RUN ./node_modules/.bin/cypress verify diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b3a61e1ae..ded3ad6dee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,6 +57,9 @@ jobs: - store_artifacts: path: coverage.xml frontend-lint: + environment: + CYPRESS_INSTALL_BINARY: 0 + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 docker: - image: circleci/node:12 steps: @@ -67,6 +70,9 @@ jobs: - store_test_results: path: /tmp/test-results frontend-unit-tests: + environment: + CYPRESS_INSTALL_BINARY: 0 + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 docker: - image: circleci/node:12 steps: @@ -90,11 +96,20 @@ jobs: PERCY_TOKEN_ENCODED: ZGRiY2ZmZDQ0OTdjMzM5ZWE0ZGQzNTZiOWNkMDRjOTk4Zjg0ZjMxMWRmMDZiM2RjOTYxNDZhOGExMjI4ZDE3MA== CYPRESS_PROJECT_ID_ENCODED: OTI0Y2th CYPRESS_RECORD_KEY_ENCODED: YzA1OTIxMTUtYTA1Yy00NzQ2LWEyMDMtZmZjMDgwZGI2ODgx + CYPRESS_INSTALL_BINARY: 0 + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1 docker: - image: circleci/node:12 steps: - setup_remote_docker - checkout + - run: + name: Enable Code Coverage report for master branch + command: | + if [ "$CIRCLE_BRANCH" = "master" ]; then + echo 'export CODE_COVERAGE=true' >> $BASH_ENV + source $BASH_ENV + fi - run: name: Install npm dependencies command: | @@ -113,6 +128,13 @@ jobs: command: | docker-compose logs when: on_fail + - run: + name: Copy Code Coverage results + command: | + docker cp cypress:/usr/src/app/coverage ./coverage || true + when: always + - store_artifacts: + path: coverage build-docker-image: *build-docker-image-job build-preview-docker-image: *build-docker-image-job workflows: diff --git a/.circleci/docker-compose.cypress.yml b/.circleci/docker-compose.cypress.yml index f058d652e4..d265c85744 100644 --- a/.circleci/docker-compose.cypress.yml +++ b/.circleci/docker-compose.cypress.yml @@ -1,7 +1,20 @@ -version: '2.2' +version: "2.2" +x-redash-service: &redash-service + build: + context: ../ + args: + skip_dev_deps: "true" + skip_ds_deps: "true" + code_coverage: ${CODE_COVERAGE} +x-redash-environment: &redash-environment + REDASH_LOG_LEVEL: "INFO" + REDASH_REDIS_URL: "redis://redis:6379/0" + REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres" + REDASH_RATELIMIT_ENABLED: "false" + REDASH_ENFORCE_CSRF: "true" services: server: - build: ../ + <<: *redash-service command: server depends_on: - postgres @@ -9,30 +22,25 @@ services: ports: - "5000:5000" environment: + <<: *redash-environment PYTHONUNBUFFERED: 0 - REDASH_LOG_LEVEL: "INFO" - REDASH_REDIS_URL: "redis://redis:6379/0" - REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres" - REDASH_RATELIMIT_ENABLED: "false" - REDASH_ENFORCE_CSRF: "true" scheduler: - build: ../ + <<: *redash-service command: scheduler depends_on: - server environment: - REDASH_REDIS_URL: "redis://redis:6379/0" + <<: *redash-environment worker: - build: ../ + <<: *redash-service command: worker depends_on: - server environment: + <<: *redash-environment PYTHONUNBUFFERED: 0 - REDASH_LOG_LEVEL: "INFO" - REDASH_REDIS_URL: "redis://redis:6379/0" - REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres" cypress: + ipc: host build: context: ../ dockerfile: .circleci/Dockerfile.cypress @@ -42,6 +50,7 @@ services: - scheduler environment: CYPRESS_baseUrl: "http://server:5000" + CYPRESS_coverage: ${CODE_COVERAGE} PERCY_TOKEN: ${PERCY_TOKEN} PERCY_BRANCH: ${CIRCLE_BRANCH} PERCY_COMMIT: ${CIRCLE_SHA1} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f1e4b0bbea..800fc05d15 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: Tests & Build Redash on: push: - branches: "develop" + branches: "tinrpt_new" jobs: buidl-redash: @@ -28,10 +28,10 @@ jobs: - name: Build Redash image run: | # cd packages/redash/ - docker build --tag docker.pkg.github.com/ttekglobal/redash/ttekredash:dev-$IMAGE_VERSION . + docker build --tag docker.pkg.github.com/ttekglobal/redash/ttekredash_d9:dev-$IMAGE_VERSION . - name: Push Redash image - run: docker push docker.pkg.github.com/ttekglobal/redash/ttekredash:dev-$IMAGE_VERSION + run: docker push docker.pkg.github.com/ttekglobal/redash/ttekredash_d9:dev-$IMAGE_VERSION deploy-k8s: name: "Deployment" runs-on: ubuntu-latest @@ -63,5 +63,5 @@ jobs: - name: Install / Upgrade App run: | - helm upgrade --install ra --namespace=ra --values ./helm/values.yaml ./helm \ + helm upgrade --install rpd9 --namespace=rpd9 --values ./helm/values.yaml ./helm \ --set image.tag=dev-$IMAGE_VERSION diff --git a/.gitignore b/.gitignore index ab0e3b6f7a..930df1e89f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ venv/ .coveralls.yml .idea *.pyc +.nyc_output +coverage .coverage coverage.xml client/dist @@ -26,3 +28,4 @@ npm-debug.log client/cypress/screenshots client/cypress/videos redash_dev +redash/tasks/worker.py diff --git a/Dockerfile b/Dockerfile index 14bef05a94..dad74716f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,13 +3,24 @@ FROM node:12 as frontend-builder # Controls whether to build the frontend assets ARG skip_frontend_build +ENV CYPRESS_INSTALL_BINARY=0 +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 + +RUN useradd -m -d /frontend redash +USER redash + WORKDIR /frontend -COPY package.json package-lock.json /frontend/ -COPY viz-lib /frontend/viz-lib +COPY --chown=redash package.json package-lock.json /frontend/ +COPY --chown=redash viz-lib /frontend/viz-lib + +# Controls whether to instrument code for coverage information +ARG code_coverage +ENV BABEL_ENV=${code_coverage:+test} + RUN if [ "x$skip_frontend_build" = "x" ] ; then npm ci --unsafe-perm; fi -COPY client /frontend/client -COPY webpack.config.js /frontend/ +COPY --chown=redash client /frontend/client +COPY --chown=redash webpack.config.js /frontend/ RUN if [ "x$skip_frontend_build" = "x" ] ; then npm run build; else mkdir -p /frontend/client/dist && touch /frontend/client/dist/multi_org.html && touch /frontend/client/dist/index.html; fi FROM python:3.7-slim @@ -68,6 +79,9 @@ WORKDIR /app ENV PIP_DISABLE_PIP_VERSION_CHECK=1 ENV PIP_NO_CACHE_DIR=1 +# Use legacy resolver to work around broken build due to resolver changes in pip +ENV PIP_USE_DEPRECATED=legacy-resolver + # We first copy only the requirements file, to avoid rebuilding on every file # change. COPY requirements.txt requirements_bundles.txt requirements_dev.txt requirements_all_ds.txt ./ diff --git a/Makefile b/Makefile index 7bc3849190..dd826ed9a4 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ backend-unit-tests: up test_db docker-compose run --rm --name tests server tests frontend-unit-tests: bundle - npm ci + CYPRESS_INSTALL_BINARY=0 PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 npm ci npm run bundle npm test diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index f54dd20643..021c1392ef 100755 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -18,8 +18,9 @@ worker() { export WORKERS_COUNT=${WORKERS_COUNT:-2} export QUEUES=${QUEUES:-} - - supervisord -c worker.conf + + exec supervisord -c worker.conf + # supervisord -c worker.conf } dev_worker() { diff --git a/client/.babelrc b/client/.babelrc index af5a043b4b..e8b6be2c9b 100644 --- a/client/.babelrc +++ b/client/.babelrc @@ -20,5 +20,10 @@ "globals": ["Error"] } ] - ] + ], + "env": { + "test": { + "plugins": ["istanbul"] + } + } } diff --git a/client/.eslintrc.js b/client/.eslintrc.js index 8bc0055d03..45644f6d19 100644 --- a/client/.eslintrc.js +++ b/client/.eslintrc.js @@ -20,6 +20,22 @@ module.exports = { // allow debugger during development "no-debugger": process.env.NODE_ENV === "production" ? 2 : 0, "jsx-a11y/anchor-is-valid": "off", + "no-console": ["warn", { allow: ["warn", "error"] }], + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "antd", + message: "Please use 'import XXX from antd/lib/XXX' import instead.", + }, + { + name: "antd/lib", + message: "Please use 'import XXX from antd/lib/XXX' import instead.", + }, + ], + }, + ], }, overrides: [ { @@ -34,6 +50,8 @@ module.exports = { // Do not complain about useless contructors in declaration files "no-useless-constructor": "off", "@typescript-eslint/no-useless-constructor": "error", + // Many API fields and generated types use camelcase + "@typescript-eslint/camelcase": "off", }, }, ], diff --git a/client/app/assets/images/db-logos/databricks.png b/client/app/assets/images/db-logos/databricks.png index 7624f2adcff64f262f72c71a4fe8cbecbf29ba05..e28eb9212fc417d78926ada2ab8053f8d9586092 100644 GIT binary patch literal 2884 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 GE%tv<)o)z@ literal 2562 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 W*UnH)#O|pB?5{Z5ys^6Z*Z%+pZck+Z diff --git a/client/app/assets/images/db-logos/trino.png b/client/app/assets/images/db-logos/trino.png new file mode 100644 index 0000000000000000000000000000000000000000..904db40bb56c51ad324331776f2d6193dbab5c37 GIT binary patch literal 23773 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 Ns!HmL)iBGj{{v-jp+^7! literal 0 HcmV?d00001 diff --git a/client/app/assets/less/custom.less b/client/app/assets/less/custom.less index 4620f9881a..471a327b67 100644 --- a/client/app/assets/less/custom.less +++ b/client/app/assets/less/custom.less @@ -328,6 +328,15 @@ table { color: #fff !important; } } + + .col-md-4 { + width: 350px; + } + + .logo-img { + padding-left: 20px; + padding-bottom: 20px; + } } .ant-modal-close-icon { @@ -938,6 +947,10 @@ div[data-test="QueryPageVisualizationTabs"] { } } } + +.ant-list-item.selected { + background-color: #26387b !important; +} // .react-grid-item { // background: red; // } @@ -962,3 +975,19 @@ div[data-test="QueryPageVisualizationTabs"] { ::-webkit-scrollbar-thumb:hover { background: #555; } + +div[data-test="GroupList"] { + a.ant-btn { + border-radius: 4px !important; + margin-right: 10px; + } + a.ant-btn:last-child { + margin-right: 0; + } +} + +// .layout-sidebar { +// .tags-list { +// display: none; +// } +// } diff --git a/client/app/assets/less/inc/table.less b/client/app/assets/less/inc/table.less index 9b562f1f01..084121cc0c 100755 --- a/client/app/assets/less/inc/table.less +++ b/client/app/assets/less/inc/table.less @@ -103,7 +103,7 @@ padding-top: 5px !important; } - .btn-favourite, + .btn-favorite, .btn-archive { font-size: 15px; } @@ -114,7 +114,7 @@ line-height: 1.7 !important; } -.btn-favourite { +.btn-favorite { color: #d4d4d4; transition: all 0.25s ease-in-out; diff --git a/client/app/assets/less/redash/query.less b/client/app/assets/less/redash/query.less index f65d3bbc7c..03f7400402 100644 --- a/client/app/assets/less/redash/query.less +++ b/client/app/assets/less/redash/query.less @@ -141,6 +141,7 @@ a.label-tag { display: flex; flex-direction: column; flex-grow: 1; + position: relative; } .query-fullscreen { diff --git a/client/app/assets/less/server.less b/client/app/assets/less/server.less index d275273f0e..421ef8085e 100644 --- a/client/app/assets/less/server.less +++ b/client/app/assets/less/server.less @@ -1,33 +1,34 @@ /** LESS Plugins **/ -@import 'inc/less-plugins/for'; +@import "inc/less-plugins/for"; /** Load Main Bootstrap LESS files **/ -@import '~bootstrap/less/bootstrap'; -@import '~material-design-iconic-font/dist/css/material-design-iconic-font.css'; - -@import 'inc/variables'; -@import 'inc/mixins'; -@import 'inc/font'; -@import 'inc/print'; - -@import 'inc/bootstrap-overrides'; -@import 'inc/base'; -@import 'inc/generics'; -@import 'inc/form'; -@import 'inc/button'; -@import 'inc/404'; -@import 'inc/ie-warning'; -@import 'inc/flex'; - -html, body { +@import "~bootstrap/less/bootstrap"; +@import "~material-design-iconic-font/dist/css/material-design-iconic-font.css"; + +@import "inc/variables"; +@import "inc/mixins"; +@import "inc/font"; +@import "inc/print"; + +@import "inc/bootstrap-overrides"; +@import "inc/base"; +@import "inc/generics"; +@import "inc/form"; +@import "inc/button"; +@import "inc/404"; +@import "inc/ie-warning"; +@import "inc/flex"; + +html, +body { height: 100%; margin: 0; padding: 0; - background: #F6F8F9; + background: #f6f8f9; } .signed-out { - + width: 350px; } hr { diff --git a/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx b/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx index 07f1c1db9e..bc131c09f0 100644 --- a/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx +++ b/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx @@ -1,10 +1,10 @@ -import { first } from "lodash"; -import React, { useState } from "react"; -import Button from "antd/lib/button"; +import React, { useMemo } from "react"; +import { first, includes } from "lodash"; import Menu from "antd/lib/menu"; import Link from "@/components/Link"; import HelpTrigger from "@/components/HelpTrigger"; import CreateDashboardDialog from "@/components/dashboards/CreateDashboardDialog"; +import { useCurrentRoute } from "@/components/ApplicationArea/Router"; import { Auth, currentUser } from "@/services/auth"; import settingsMenu from "@/services/settingsMenu"; import logoUrl from "@/assets/images/redash_icon_small.png"; @@ -15,83 +15,109 @@ import AlertOutlinedIcon from "@ant-design/icons/AlertOutlined"; import PlusOutlinedIcon from "@ant-design/icons/PlusOutlined"; import QuestionCircleOutlinedIcon from "@ant-design/icons/QuestionCircleOutlined"; import SettingOutlinedIcon from "@ant-design/icons/SettingOutlined"; -import MenuUnfoldOutlinedIcon from "@ant-design/icons/MenuUnfoldOutlined"; -import MenuFoldOutlinedIcon from "@ant-design/icons/MenuFoldOutlined"; import VersionInfo from "./VersionInfo"; import "./DesktopNavbar.less"; -function NavbarSection({ inlineCollapsed, children, ...props }) { +function NavbarSection({ children, ...props }) { return ( - + {children} ); } -export default function DesktopNavbar() { - const [collapsed, setCollapsed] = useState(true); +function useNavbarActiveState() { + const currentRoute = useCurrentRoute(); + + return useMemo( + () => ({ + dashboards: includes( + [ + "Dashboards.List", + "Dashboards.Favorites", + "Dashboards.My", + "Dashboards.ViewOrEdit", + "Dashboards.LegacyViewOrEdit", + ], + currentRoute.id + ), + queries: includes( + [ + "Queries.List", + "Queries.Favorites", + "Queries.Archived", + "Queries.My", + "Queries.View", + "Queries.New", + "Queries.Edit", + ], + currentRoute.id + ), + dataSources: includes(["DataSources.List"], currentRoute.id), + alerts: includes(["Alerts.List", "Alerts.New", "Alerts.View", "Alerts.Edit"], currentRoute.id), + }), + [currentRoute.id] + ); +} +export default function DesktopNavbar() { const firstSettingsTab = first(settingsMenu.getAvailableItems()); + const activeState = useNavbarActiveState(); + const canCreateQuery = currentUser.hasPermission("create_query"); const canCreateDashboard = currentUser.hasPermission("create_dashboard"); const canCreateAlert = currentUser.hasPermission("list_alerts"); return ( -
- - + ); } diff --git a/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.less b/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.less index 7cafb94008..4b8bedda7f 100644 --- a/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.less +++ b/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.less @@ -1,12 +1,17 @@ @backgroundColor: #001529; @dividerColor: rgba(255, 255, 255, 0.5); @textColor: rgba(255, 255, 255, 0.75); +@brandColor: #ff7964; // Redash logo color +@activeItemColor: @brandColor; +@iconSize: 26px; .desktop-navbar { background: @backgroundColor; display: flex; flex-direction: column; height: 100%; + width: 80px; + overflow: hidden; &-spacer { flex: 1 1 auto; @@ -21,12 +26,6 @@ height: 40px; transition: all 270ms; } - - &.ant-menu-inline-collapsed { - img { - height: 20px; - } - } } .help-trigger { @@ -34,33 +33,38 @@ } .ant-menu { - &:not(.ant-menu-inline-collapsed) { - width: 170px; - } - - &.ant-menu-inline-collapsed > .ant-menu-submenu-title span img + span, - &.ant-menu-inline-collapsed > .ant-menu-item i + span { - display: inline-block; - max-width: 0; - opacity: 0; - } - - .ant-menu-item-divider { - background: @dividerColor; - } - .ant-menu-item, .ant-menu-submenu { font-weight: 500; color: @textColor; + &.navbar-active-item { + box-shadow: inset 3px 0 0 @activeItemColor; + + .anticon { + color: @activeItemColor; + } + } + &.ant-menu-submenu-open, &.ant-menu-submenu-active, &:hover, - &:active { + &:active, + &:focus, + &:focus-within { color: #fff; } + .anticon { + font-size: @iconSize; + margin: 0; + } + + .desktop-navbar-label { + margin-top: 4px; + font-size: 11px; + } + a, span, .anticon { @@ -71,21 +75,33 @@ .ant-menu-submenu-arrow { display: none; } - } - .ant-btn.desktop-navbar-collapse-button { - background-color: @backgroundColor; - border: 0; - border-radius: 0; - color: @textColor; + .ant-menu-item, + .ant-menu-submenu { + padding: 0; + height: 60px; + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + } - &:hover, - &:active { - color: #fff; + .ant-menu-submenu-title { + width: 100%; + padding: 0; } - &:after { - animation: 0s !important; + a, + &.ant-menu-vertical > .ant-menu-submenu > .ant-menu-submenu-title, + .ant-menu-submenu-title { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + line-height: normal; + height: auto; + background: none; + color: inherit; } } @@ -99,37 +115,8 @@ .profile__image_thumb { margin: 0; vertical-align: middle; - } - - .profile__image_thumb + span { - flex: 1 1 auto; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - margin-left: 10px; - vertical-align: middle; - display: inline-block; - - // styles from Antd - opacity: 1; - transition: opacity 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), - margin-left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - } - } - - &.ant-menu-inline-collapsed { - .ant-menu-submenu-title { - padding-left: 16px !important; - padding-right: 16px !important; - } - - .desktop-navbar-profile-menu-title { - .profile__image_thumb + span { - opacity: 0; - max-width: 0; - margin-left: 0; - } + width: @iconSize; + height: @iconSize; } } } @@ -146,7 +133,9 @@ color: @textColor; &:hover, - &:active { + &:active, + &:focus, + &:focus-within { color: #fff; } @@ -171,7 +160,9 @@ color: rgba(255, 255, 255, 0.8); &:hover, - &:active { + &:active, + &:focus, + &:focus-within { color: rgba(255, 255, 255, 1); } } diff --git a/client/app/components/ApplicationArea/ApplicationLayout/index.jsx b/client/app/components/ApplicationArea/ApplicationLayout/index.jsx index c89806d3d9..78f5663c0c 100644 --- a/client/app/components/ApplicationArea/ApplicationLayout/index.jsx +++ b/client/app/components/ApplicationArea/ApplicationLayout/index.jsx @@ -13,19 +13,21 @@ export default function ApplicationLayout({ children }) { return ( -
- - - -
-
- - {children} -
+
+
+ + {children} +
+ ); } diff --git a/client/app/components/ApplicationArea/ErrorMessage.jsx b/client/app/components/ApplicationArea/ErrorMessage.jsx index 852e1452fe..733444f4bf 100644 --- a/client/app/components/ApplicationArea/ErrorMessage.jsx +++ b/client/app/components/ApplicationArea/ErrorMessage.jsx @@ -1,8 +1,10 @@ -import { isObject, get } from "lodash"; +import { get, isObject } from "lodash"; import React from "react"; import PropTypes from "prop-types"; import "./ErrorMessage.less"; +import DynamicComponent from "@/components/DynamicComponent"; +import { ErrorMessageDetails } from "@/components/ApplicationArea/ErrorMessageDetails"; function getErrorMessageByStatus(status, defaultMessage) { switch (status) { @@ -31,21 +33,30 @@ function getErrorMessage(error) { return message; } -export default function ErrorMessage({ error }) { +export default function ErrorMessage({ error, message }) { if (!error) { return null; } console.error(error); + const errorDetailsProps = { + error, + message: message || getErrorMessage(error), + }; + return ( -
+
-

{getErrorMessage(error)}

+ } + {...errorDetailsProps} + />
@@ -54,4 +65,5 @@ export default function ErrorMessage({ error }) { ErrorMessage.propTypes = { error: PropTypes.object.isRequired, + message: PropTypes.string, }; diff --git a/client/app/components/ApplicationArea/ErrorMessageDetails.jsx b/client/app/components/ApplicationArea/ErrorMessageDetails.jsx new file mode 100644 index 0000000000..8e8fd171cd --- /dev/null +++ b/client/app/components/ApplicationArea/ErrorMessageDetails.jsx @@ -0,0 +1,11 @@ +import React from "react"; +import PropTypes from "prop-types"; + +export function ErrorMessageDetails(props) { + return

{props.message}

; +} + +ErrorMessageDetails.propTypes = { + error: PropTypes.instanceOf(Error).isRequired, + message: PropTypes.string.isRequired, +}; diff --git a/client/app/components/ApplicationArea/handleNavigationIntent.js b/client/app/components/ApplicationArea/handleNavigationIntent.js index cdde39a7e1..1c246c2133 100644 --- a/client/app/components/ApplicationArea/handleNavigationIntent.js +++ b/client/app/components/ApplicationArea/handleNavigationIntent.js @@ -9,7 +9,7 @@ export default function handleNavigationIntent(event) { } element = element.parentNode; } - if (!element || !element.hasAttribute("href") || element.hasAttribute("download")) { + if (!element || !element.hasAttribute("href") || element.hasAttribute("download") || element.dataset.skipRouter) { return; } diff --git a/client/app/components/ApplicationArea/routeWithApiKeySession.jsx b/client/app/components/ApplicationArea/routeWithApiKeySession.jsx index 66f04b79cf..771a14c943 100644 --- a/client/app/components/ApplicationArea/routeWithApiKeySession.jsx +++ b/client/app/components/ApplicationArea/routeWithApiKeySession.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState, useContext } from "react"; import PropTypes from "prop-types"; import { ErrorBoundaryContext } from "@redash/viz/lib/components/ErrorBoundary"; -import { Auth } from "@/services/auth"; +import { Auth, clientConfig } from "@/services/auth"; // This wrapper modifies `route.render` function and instead of passing `currentRoute` passes an object // that contains: @@ -33,7 +33,7 @@ function ApiKeySessionWrapper({ apiKey, currentRoute, renderChildren }) { }; }, [apiKey]); - if (!isAuthenticated) { + if (!isAuthenticated || clientConfig.disablePublicUrls) { return null; } diff --git a/client/app/components/ApplicationArea/routeWithUserSession.jsx b/client/app/components/ApplicationArea/routeWithUserSession.tsx similarity index 51% rename from client/app/components/ApplicationArea/routeWithUserSession.jsx rename to client/app/components/ApplicationArea/routeWithUserSession.tsx index 7c3ec86bd0..ed943c3b4a 100644 --- a/client/app/components/ApplicationArea/routeWithUserSession.jsx +++ b/client/app/components/ApplicationArea/routeWithUserSession.tsx @@ -1,21 +1,32 @@ import React, { useEffect, useState } from "react"; -import PropTypes from "prop-types"; import ErrorBoundary, { ErrorBoundaryContext } from "@redash/viz/lib/components/ErrorBoundary"; import { Auth } from "@/services/auth"; import { policy } from "@/services/policy"; +import { CurrentRoute } from "@/services/routes"; import organizationStatus from "@/services/organizationStatus"; +import DynamicComponent from "@/components/DynamicComponent"; import ApplicationLayout from "./ApplicationLayout"; import ErrorMessage from "./ErrorMessage"; +export type UserSessionWrapperRenderChildrenProps

= { + pageTitle?: string; + onError: (error: Error) => void; +} & P; + +export interface UserSessionWrapperProps

{ + render: (props: UserSessionWrapperRenderChildrenProps

) => React.ReactNode; + currentRoute: CurrentRoute

; + bodyClass?: string; +} + // This wrapper modifies `route.render` function and instead of passing `currentRoute` passes an object // that contains: // - `currentRoute.routeParams` // - `pageTitle` field which is equal to `currentRoute.title` // - `onError` field which is a `handleError` method of nearest error boundary -function UserSessionWrapper({ bodyClass, currentRoute, renderChildren }) { +export function UserSessionWrapper

({ bodyClass, currentRoute, render }: UserSessionWrapperProps

) { const [isAuthenticated, setIsAuthenticated] = useState(!!Auth.isAuthenticated()); - useEffect(() => { let isCancelled = false; Promise.all([Auth.requireSession(), organizationStatus.refresh(), policy.refresh()]) @@ -50,10 +61,11 @@ function UserSessionWrapper({ bodyClass, currentRoute, renderChildren }) { return ( - }> + {/* @ts-expect-error FIXME */} + }> - {({ handleError }) => - renderChildren({ ...currentRoute.routeParams, pageTitle: currentRoute.title, onError: handleError }) + {({ handleError } /* : { handleError: UserSessionWrapperRenderChildrenProps

["onError"] } FIXME bring back type */) => + render({ ...currentRoute.routeParams, pageTitle: currentRoute.title, onError: handleError }) } @@ -62,21 +74,35 @@ function UserSessionWrapper({ bodyClass, currentRoute, renderChildren }) { ); } -UserSessionWrapper.propTypes = { - bodyClass: PropTypes.string, - renderChildren: PropTypes.func, +export type RouteWithUserSessionOptions

= { + render: (props: UserSessionWrapperRenderChildrenProps

) => React.ReactNode; + bodyClass?: string; + title: string; + path: string; }; -UserSessionWrapper.defaultProps = { - bodyClass: null, - renderChildren: () => null, -}; +export const UserSessionWrapperDynamicComponentName = "UserSessionWrapper"; -export default function routeWithUserSession({ render, bodyClass, ...rest }) { +export default function routeWithUserSession

({ + render: originalRender, + bodyClass, + ...rest +}: RouteWithUserSessionOptions

) { return { ...rest, - render: currentRoute => ( - - ), + render: (currentRoute: CurrentRoute

) => { + const props = { + render: originalRender, + bodyClass, + currentRoute, + }; + return ( + } + /> + ); + }, }; } diff --git a/client/app/components/CodeBlock.less b/client/app/components/CodeBlock.less index e64dd659a9..a983818c97 100644 --- a/client/app/components/CodeBlock.less +++ b/client/app/components/CodeBlock.less @@ -1,4 +1,4 @@ -@import '~antd/lib/button/style/index'; +@import (reference, less) "~@/assets/less/ant"; .code-block { background: rgba(0, 0, 0, 0.06); diff --git a/client/app/components/DialogWrapper.d.ts b/client/app/components/DialogWrapper.d.ts index a5cee81b22..d879c8303e 100644 --- a/client/app/components/DialogWrapper.d.ts +++ b/client/app/components/DialogWrapper.d.ts @@ -22,8 +22,8 @@ export function wrap( props?: P ) => { update: (props: P) => void; - onClose: (handler: (result: ROk) => Promise) => void; - onDismiss: (handler: (result: RCancel) => Promise) => void; + onClose: (handler: (result: ROk) => Promise | void) => void; + onDismiss: (handler: (result: RCancel) => Promise | void) => void; close: (result: ROk) => void; dismiss: (result: RCancel) => void; }; diff --git a/client/app/components/DynamicComponent.jsx b/client/app/components/DynamicComponent.jsx index 98e2d78c28..5e4af4ade8 100644 --- a/client/app/components/DynamicComponent.jsx +++ b/client/app/components/DynamicComponent.jsx @@ -1,4 +1,4 @@ -import { isFunction, isString } from "lodash"; +import { isFunction, isString, isUndefined } from "lodash"; import React from "react"; import PropTypes from "prop-types"; @@ -24,6 +24,7 @@ export function unregisterComponent(name) { export default class DynamicComponent extends React.Component { static propTypes = { name: PropTypes.string.isRequired, + fallback: PropTypes.node, children: PropTypes.node, }; @@ -40,10 +41,11 @@ export default class DynamicComponent extends React.Component { } render() { - const { name, children, ...props } = this.props; + const { name, children, fallback, ...props } = this.props; const RealComponent = componentsRegistry.get(name); if (!RealComponent) { - return children; + // return fallback if any, otherwise return children + return isUndefined(fallback) ? children : fallback; } return {children}; } diff --git a/client/app/components/EditParameterSettingsDialog.jsx b/client/app/components/EditParameterSettingsDialog.jsx index 187f72bfa5..b0691a1d8d 100644 --- a/client/app/components/EditParameterSettingsDialog.jsx +++ b/client/app/components/EditParameterSettingsDialog.jsx @@ -140,7 +140,7 @@ function EditParameterSettingsDialog(props) { type={param.type} /> )} - + setParam({ ...param, title: e.target.value })} diff --git a/client/app/components/EditVisualizationButton/QueryControlDropdown.jsx b/client/app/components/EditVisualizationButton/QueryControlDropdown.jsx index c12a609b35..3997726e05 100644 --- a/client/app/components/EditVisualizationButton/QueryControlDropdown.jsx +++ b/client/app/components/EditVisualizationButton/QueryControlDropdown.jsx @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import Dropdown from "antd/lib/dropdown"; import Menu from "antd/lib/menu"; import Button from "antd/lib/button"; +import { clientConfig } from "@/services/auth"; import PlusCircleFilledIcon from "@ant-design/icons/PlusCircleFilled"; import ShareAltOutlinedIcon from "@ant-design/icons/ShareAltOutlined"; @@ -22,7 +23,7 @@ export default function QueryControlDropdown(props) { )} - {!props.query.isNew() && ( + {!clientConfig.disablePublicUrls && !props.query.isNew() && ( props.showEmbedDialog(props.query, props.selectedTab)} data-test="ShowEmbedDialogButton"> Embed Elsewhere diff --git a/client/app/components/FavoritesControl.jsx b/client/app/components/FavoritesControl.jsx index 2dff129a57..871233f943 100644 --- a/client/app/components/FavoritesControl.jsx +++ b/client/app/components/FavoritesControl.jsx @@ -31,7 +31,7 @@ export default class FavoritesControl extends React.Component { return ( this.toggleItem(event, item, onChange)}>