diff --git a/Dockerfile b/Dockerfile index dad74716f1..3a62359374 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,8 +66,9 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* ARG databricks_odbc_driver_url=https://databricks.com/wp-content/uploads/2.6.10.1010-2/SimbaSparkODBC-2.6.10.1010-2-Debian-64bit.zip -ADD $databricks_odbc_driver_url /tmp/simba_odbc.zip -RUN unzip /tmp/simba_odbc.zip -d /tmp/ \ +RUN wget --quiet $databricks_odbc_driver_url -O /tmp/simba_odbc.zip \ + && chmod 600 /tmp/simba_odbc.zip \ + && unzip /tmp/simba_odbc.zip -d /tmp/ \ && dpkg -i /tmp/SimbaSparkODBC-*/*.deb \ && echo "[Simba]\nDriver = /opt/simba/spark/lib/64/libsparkodbc_sb64.so" >> /etc/odbcinst.ini \ && rm /tmp/simba_odbc.zip \ @@ -79,15 +80,19 @@ 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 +# rollback pip version to avoid legacy resolver problem +RUN pip install pip==20.2.4; -# 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 ./ -RUN if [ "x$skip_dev_deps" = "x" ] ; then pip install -r requirements.txt -r requirements_dev.txt; else pip install -r requirements.txt; fi +# We first copy only the requirements file, to avoid rebuilding on every file change. +COPY requirements_all_ds.txt ./ RUN if [ "x$skip_ds_deps" = "x" ] ; then pip install -r requirements_all_ds.txt ; else echo "Skipping pip install -r requirements_all_ds.txt" ; fi +COPY requirements_bundles.txt requirements_dev.txt ./ +RUN if [ "x$skip_dev_deps" = "x" ] ; then pip install -r requirements_dev.txt ; fi + +COPY requirements.txt ./ +RUN pip install -r requirements.txt + COPY . /app COPY --from=frontend-builder /frontend/client/dist /app/client/dist RUN chown -R redash /app diff --git a/README.md b/README.md index ff63f476d1..46d44d203d 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ Redash supports more than 35 SQL and NoSQL [data sources](https://redash.io/help - Shell Scripts - Snowflake - SQLite +- TiDB - TreasureData - Vertica - Yandex AppMetrrica diff --git a/client/.eslintrc.js b/client/.eslintrc.js index 45644f6d19..d1bb2599a1 100644 --- a/client/.eslintrc.js +++ b/client/.eslintrc.js @@ -5,10 +5,11 @@ module.exports = { "react-app", "plugin:compat/recommended", "prettier", + "plugin:jsx-a11y/recommended", // Remove any typescript-eslint rules that would conflict with prettier "prettier/@typescript-eslint", ], - plugins: ["jest", "compat", "no-only-tests", "@typescript-eslint"], + plugins: ["jest", "compat", "no-only-tests", "@typescript-eslint", "jsx-a11y"], settings: { "import/resolver": "webpack", }, @@ -19,7 +20,19 @@ module.exports = { rules: { // allow debugger during development "no-debugger": process.env.NODE_ENV === "production" ? 2 : 0, - "jsx-a11y/anchor-is-valid": "off", + "jsx-a11y/anchor-is-valid": [ + // TMP + "off", + { + components: ["Link"], + aspects: ["noHref", "invalidHref", "preferButton"], + }, + ], + "jsx-a11y/no-redundant-roles": "error", + "jsx-a11y/no-autofocus": "off", + "jsx-a11y/click-events-have-key-events": "off", // TMP + "jsx-a11y/no-static-element-interactions": "off", // TMP + "jsx-a11y/no-noninteractive-element-interactions": "off", // TMP "no-console": ["warn", { allow: ["warn", "error"] }], "no-restricted-imports": [ "error", diff --git a/client/app/assets/images/db-logos/corporate_memory.png b/client/app/assets/images/db-logos/corporate_memory.png new file mode 100644 index 0000000000..f168b02ecd Binary files /dev/null and b/client/app/assets/images/db-logos/corporate_memory.png differ diff --git a/client/app/assets/images/db-logos/sparql_endpoint.png b/client/app/assets/images/db-logos/sparql_endpoint.png new file mode 100644 index 0000000000..31ac155d44 Binary files /dev/null and b/client/app/assets/images/db-logos/sparql_endpoint.png differ diff --git a/client/app/assets/less/inc/schema-browser.less b/client/app/assets/less/inc/schema-browser.less index 3f2e66f28d..f005239758 100644 --- a/client/app/assets/less/inc/schema-browser.less +++ b/client/app/assets/less/inc/schema-browser.less @@ -1,102 +1,107 @@ -div.table-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - cursor: pointer; - padding: 2px 22px 2px 10px; - border-radius: @redash-radius; - position: relative; - height: 22px; - - .copy-to-editor { - display: none; - } - - &:hover { - background: fade(@redash-gray, 10%); - - .copy-to-editor { - display: flex; - } - } -} - .schema-container { height: 100%; z-index: 10; background-color: white; -} -.schema-browser { - overflow: hidden; - border: none; - padding-top: 10px; - position: relative; - height: 100%; - - .schema-loading-state { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - } - - .collapse.in { - background: transparent; - } - - .copy-to-editor { - color: fade(@redash-gray, 90%); - cursor: pointer; - position: absolute; - top: 0; - right: 0; - bottom: 0; - width: 20px; - display: flex; - align-items: center; - justify-content: center; - } - - .table-open { - padding: 0 22px 0 26px; + .schema-browser { overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + border: none; + padding-top: 10px; position: relative; - height: 18px; + height: 100%; - .column-type { - color: fade(@text-color, 80%); - font-size: 10px; - margin-left: 2px; - text-transform: uppercase; + .schema-loading-state { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + } + + .collapse.in { + background: transparent; } .copy-to-editor { - display: none; + visibility: hidden; + color: fade(@redash-gray, 90%); + width: 20px; + display: flex; + align-items: center; + justify-content: center; + transition: none; } - &:hover { - background: fade(@redash-gray, 10%); + .schema-list-item { + display: flex; + border-radius: @redash-radius; + height: 22px; + + .table-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + padding: 2px 22px 2px 10px; + } + + &:hover, + &:focus, + &:focus-within { + background: fade(@redash-gray, 10%); - .copy-to-editor { + .copy-to-editor { + visibility: visible; + } + } + } + + .table-open { + .table-open-item { display: flex; + height: 18px; + width: calc(100% - 22px); + padding-left: 22px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: none; + + div:first-child { + flex: 1; + } + + .column-type { + color: fade(@text-color, 80%); + font-size: 10px; + margin-left: 2px; + text-transform: uppercase; + } + + &:hover, + &:focus, + &:focus-within { + background: fade(@redash-gray, 10%); + + .copy-to-editor { + visibility: visible; + } + } } } } -} -.schema-control { - display: flex; - flex-wrap: nowrap; - padding: 0; + .schema-control { + display: flex; + flex-wrap: nowrap; + padding: 0; - .ant-btn { - height: auto; + .ant-btn { + height: auto; + } } -} -.parameter-label { - display: block; + .parameter-label { + display: block; + } } diff --git a/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx b/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx index bc131c09f0..a1550f60dc 100644 --- a/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx +++ b/client/app/components/ApplicationArea/ApplicationLayout/DesktopNavbar.jsx @@ -2,6 +2,7 @@ import React, { useMemo } from "react"; import { first, includes } from "lodash"; import Menu from "antd/lib/menu"; import Link from "@/components/Link"; +import PlainButton from "@/components/PlainButton"; import HelpTrigger from "@/components/HelpTrigger"; import CreateDashboardDialog from "@/components/dashboards/CreateDashboardDialog"; import { useCurrentRoute } from "@/components/ApplicationArea/Router"; @@ -15,8 +16,8 @@ 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 VersionInfo from "./VersionInfo"; + import "./DesktopNavbar.less"; function NavbarSection({ children, ...props }) { @@ -129,9 +130,9 @@ export default function DesktopNavbar() { )} {canCreateDashboard && ( - CreateDashboardDialog.showModal()}> + CreateDashboardDialog.showModal()}> New Dashboard - + )} {canCreateAlert && ( @@ -182,9 +183,9 @@ export default function DesktopNavbar() { )} - Auth.logout()}> + Auth.logout()}> Log out - + diff --git a/client/app/components/ApplicationArea/routeWithUserSession.tsx b/client/app/components/ApplicationArea/routeWithUserSession.tsx index ed943c3b4a..87f88baa98 100644 --- a/client/app/components/ApplicationArea/routeWithUserSession.tsx +++ b/client/app/components/ApplicationArea/routeWithUserSession.tsx @@ -64,9 +64,11 @@ export function UserSessionWrapper

({ bodyClass, currentRoute, render }: UserS {/* @ts-expect-error FIXME */} }> - {({ handleError } /* : { handleError: UserSessionWrapperRenderChildrenProps

["onError"] } FIXME bring back type */) => - render({ ...currentRoute.routeParams, pageTitle: currentRoute.title, onError: handleError }) - } + {( + { + handleError, + } /* : { handleError: UserSessionWrapperRenderChildrenProps

["onError"] } FIXME bring back type */ + ) => render({ ...currentRoute.routeParams, pageTitle: currentRoute.title, onError: handleError })} diff --git a/client/app/components/CodeBlock.jsx b/client/app/components/CodeBlock.jsx index 13a643dc19..7802cdd8c9 100644 --- a/client/app/components/CodeBlock.jsx +++ b/client/app/components/CodeBlock.jsx @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; import Button from "antd/lib/button"; -import Tooltip from "antd/lib/tooltip"; +import Tooltip from "@/components/Tooltip"; import CopyOutlinedIcon from "@ant-design/icons/CopyOutlined"; import "./CodeBlock.less"; diff --git a/client/app/components/CreateSourceDialog.jsx b/client/app/components/CreateSourceDialog.jsx index 8ce19bd055..2105ba078c 100644 --- a/client/app/components/CreateSourceDialog.jsx +++ b/client/app/components/CreateSourceDialog.jsx @@ -1,6 +1,6 @@ import React from "react"; import PropTypes from "prop-types"; -import { isEmpty, toUpper, includes, get } from "lodash"; +import { isEmpty, toUpper, includes, get, uniqueId } from "lodash"; import Button from "antd/lib/button"; import List from "antd/lib/list"; import Modal from "antd/lib/modal"; @@ -45,6 +45,8 @@ class CreateSourceDialog extends React.Component { currentStep: StepEnum.SELECT_TYPE, }; + formId = uniqueId("sourceForm"); + selectType = selectedType => { this.setState({ selectedType, currentStep: StepEnum.CONFIGURE_IT }); }; @@ -82,6 +84,7 @@ class CreateSourceDialog extends React.Component {

this.setState({ searchText: e.target.value })} autoFocus data-test="SearchSource" @@ -116,7 +119,7 @@ class CreateSourceDialog extends React.Component { )}
- + {selectedType.type === "databricks" && ( By using the Databricks Data Source you agree to the Databricks JDBC/ODBC{" "} @@ -170,7 +173,7 @@ class CreateSourceDialog extends React.Component { , ]}> -
+ {isNew && ( {!props.query.isNew() && (!props.query.is_draft || !props.query.is_archived) && ( - props.openAddToDashboardForm(props.selectedTab)}> + props.openAddToDashboardForm(props.selectedTab)}> Add to Dashboard - + )} {!clientConfig.disablePublicUrls && !props.query.isNew() && ( - props.showEmbedDialog(props.query, props.selectedTab)} data-test="ShowEmbedDialogButton"> + props.showEmbedDialog(props.query, props.selectedTab)} + data-test="ShowEmbedDialogButton"> Embed Elsewhere - + )} diff --git a/client/app/components/EmailSettingsWarning.jsx b/client/app/components/EmailSettingsWarning.jsx index d88c408771..98766bbe44 100644 --- a/client/app/components/EmailSettingsWarning.jsx +++ b/client/app/components/EmailSettingsWarning.jsx @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; import { clientConfig, currentUser } from "@/services/auth"; -import Tooltip from "antd/lib/tooltip"; +import Tooltip from "@/components/Tooltip"; import Alert from "antd/lib/alert"; import HelpTrigger from "@/components/HelpTrigger"; import { useUniqueId } from "@/lib/hooks/useUniqueId"; @@ -27,6 +27,7 @@ export default function EmailSettingsWarning({ featureName, className, mode, adm if (mode === "icon") { return ( + {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}