Compare commits

...

9 Commits

Author SHA1 Message Date
Elad Ossadon
dc50415687 1 2020-12-08 14:33:15 -08:00
Elad Ossadon
8817113f4a Revert "1"
This reverts commit 6ce2896b0b.
2020-12-08 14:30:22 -08:00
Elad Ossadon
6ce2896b0b 1 2020-12-08 14:28:20 -08:00
Elad Ossadon
fdf636a393 Fix disabled hot reload flow (#5306) 2020-12-07 16:02:52 -08:00
Rafael Wendel
88c13868a3 removed leftover console.log (#5303) 2020-12-07 17:21:40 -03:00
Elad Ossadon
aab11dc79b Add React Fast Refresh + Hot Module Reloading (#5291) 2020-12-07 11:46:46 -08:00
Elad Ossadon
00c77cf36e Redesign desktop nav bar (#5294) 2020-12-06 12:09:19 -08:00
Rafael Wendel
6e2631dec2 Changed 'Delete Alert' into 'Delete' for consistency (#5287) 2020-11-30 18:48:35 -03:00
Rafael Wendel
4b88959341 Fix QuerySourceDropdown value type (#5284) 2020-11-24 11:42:20 -03:00
8 changed files with 2967 additions and 665 deletions

View File

@@ -1,10 +1,10 @@
import { first } from "lodash"; import React, { useMemo } from "react";
import React, { useState } from "react"; import { first, includes } from "lodash";
import Button from "antd/lib/button";
import Menu from "antd/lib/menu"; import Menu from "antd/lib/menu";
import Link from "@/components/Link"; import Link from "@/components/Link";
import HelpTrigger from "@/components/HelpTrigger"; import HelpTrigger from "@/components/HelpTrigger";
import CreateDashboardDialog from "@/components/dashboards/CreateDashboardDialog"; import CreateDashboardDialog from "@/components/dashboards/CreateDashboardDialog";
import { useCurrentRoute } from "@/components/ApplicationArea/Router";
import { Auth, currentUser } from "@/services/auth"; import { Auth, currentUser } from "@/services/auth";
import settingsMenu from "@/services/settingsMenu"; import settingsMenu from "@/services/settingsMenu";
import logoUrl from "@/assets/images/redash_icon_small.png"; import logoUrl from "@/assets/images/redash_icon_small.png";
@@ -15,37 +15,58 @@ import AlertOutlinedIcon from "@ant-design/icons/AlertOutlined";
import PlusOutlinedIcon from "@ant-design/icons/PlusOutlined"; import PlusOutlinedIcon from "@ant-design/icons/PlusOutlined";
import QuestionCircleOutlinedIcon from "@ant-design/icons/QuestionCircleOutlined"; import QuestionCircleOutlinedIcon from "@ant-design/icons/QuestionCircleOutlined";
import SettingOutlinedIcon from "@ant-design/icons/SettingOutlined"; 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 VersionInfo from "./VersionInfo";
import "./DesktopNavbar.less"; import "./DesktopNavbar.less";
function NavbarSection({ inlineCollapsed, children, ...props }) { function NavbarSection({ children, ...props }) {
return ( return (
<Menu <Menu selectable={false} mode="vertical" theme="dark" {...props}>
selectable={false}
mode={inlineCollapsed ? "inline" : "vertical"}
inlineCollapsed={inlineCollapsed}
theme="dark"
{...props}>
{children} {children}
</Menu> </Menu>
); );
} }
export default function DesktopNavbar() { function useNavbarActiveState() {
const [collapsed, setCollapsed] = useState(true); const currentRoute = useCurrentRoute();
return useMemo(
() => ({
dashboards: includes(
["Dashboards.List", "Dashboards.Favorites", "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 firstSettingsTab = first(settingsMenu.getAvailableItems());
const activeState = useNavbarActiveState();
const canCreateQuery = currentUser.hasPermission("create_query"); const canCreateQuery = currentUser.hasPermission("create_query");
const canCreateDashboard = currentUser.hasPermission("create_dashboard"); const canCreateDashboard = currentUser.hasPermission("create_dashboard");
const canCreateAlert = currentUser.hasPermission("list_alerts"); const canCreateAlert = currentUser.hasPermission("list_alerts");
return ( return (
<div className="desktop-navbar"> <div className="desktop-navbar">
<NavbarSection inlineCollapsed={collapsed} className="desktop-navbar-logo"> <NavbarSection className="desktop-navbar-logo">
<div> <div>
<Link href="./"> <Link href="./">
<img src={logoUrl} alt="Redash" /> <img src={logoUrl} alt="Redash" />
@@ -53,45 +74,43 @@ export default function DesktopNavbar() {
</div> </div>
</NavbarSection> </NavbarSection>
<NavbarSection inlineCollapsed={collapsed}> <NavbarSection>
{currentUser.hasPermission("list_dashboards") && ( {currentUser.hasPermission("list_dashboards") && (
<Menu.Item key="dashboards"> <Menu.Item key="dashboards" className={activeState.dashboards ? "navbar-active-item" : null}>
<Link href="dashboards"> <Link href="dashboards">
<DesktopOutlinedIcon /> <DesktopOutlinedIcon />
<span>Dashboards</span> <span className="desktop-navbar-label">Dashboards</span>
</Link> </Link>
</Menu.Item> </Menu.Item>
)} )}
{currentUser.hasPermission("view_query") && ( {currentUser.hasPermission("view_query") && (
<Menu.Item key="queries"> <Menu.Item key="queries" className={activeState.queries ? "navbar-active-item" : null}>
<Link href="queries"> <Link href="queries">
<CodeOutlinedIcon /> <CodeOutlinedIcon />
<span>Queries</span> <span className="desktop-navbar-label">Queries</span>
</Link> </Link>
</Menu.Item> </Menu.Item>
)} )}
{currentUser.hasPermission("list_alerts") && ( {currentUser.hasPermission("list_alerts") && (
<Menu.Item key="alerts"> <Menu.Item key="alerts" className={activeState.alerts ? "navbar-active-item" : null}>
<Link href="alerts"> <Link href="alerts">
<AlertOutlinedIcon /> <AlertOutlinedIcon />
<span>Alerts</span> <span className="desktop-navbar-label">Alerts</span>
</Link> </Link>
</Menu.Item> </Menu.Item>
)} )}
</NavbarSection> </NavbarSection>
<NavbarSection inlineCollapsed={collapsed} className="desktop-navbar-spacer"> <NavbarSection className="desktop-navbar-spacer">
{(canCreateQuery || canCreateDashboard || canCreateAlert) && <Menu.Divider />}
{(canCreateQuery || canCreateDashboard || canCreateAlert) && ( {(canCreateQuery || canCreateDashboard || canCreateAlert) && (
<Menu.SubMenu <Menu.SubMenu
key="create" key="create"
popupClassName="desktop-navbar-submenu" popupClassName="desktop-navbar-submenu"
data-test="CreateButton"
title={ title={
<React.Fragment> <React.Fragment>
<span data-test="CreateButton">
<PlusOutlinedIcon /> <PlusOutlinedIcon />
<span>Create</span> <span className="desktop-navbar-label">Create</span>
</span>
</React.Fragment> </React.Fragment>
}> }>
{canCreateQuery && ( {canCreateQuery && (
@@ -119,32 +138,30 @@ export default function DesktopNavbar() {
)} )}
</NavbarSection> </NavbarSection>
<NavbarSection inlineCollapsed={collapsed}> <NavbarSection>
<Menu.Item key="help"> <Menu.Item key="help">
<HelpTrigger showTooltip={false} type="HOME"> <HelpTrigger showTooltip={false} type="HOME">
<QuestionCircleOutlinedIcon /> <QuestionCircleOutlinedIcon />
<span>Help</span> <span className="desktop-navbar-label">Help</span>
</HelpTrigger> </HelpTrigger>
</Menu.Item> </Menu.Item>
{firstSettingsTab && ( {firstSettingsTab && (
<Menu.Item key="settings"> <Menu.Item key="settings" className={activeState.dataSources ? "navbar-active-item" : null}>
<Link href={firstSettingsTab.path} data-test="SettingsLink"> <Link href={firstSettingsTab.path} data-test="SettingsLink">
<SettingOutlinedIcon /> <SettingOutlinedIcon />
<span>Settings</span> <span className="desktop-navbar-label">Settings</span>
</Link> </Link>
</Menu.Item> </Menu.Item>
)} )}
<Menu.Divider />
</NavbarSection> </NavbarSection>
<NavbarSection inlineCollapsed={collapsed} className="desktop-navbar-profile-menu"> <NavbarSection className="desktop-navbar-profile-menu">
<Menu.SubMenu <Menu.SubMenu
key="profile" key="profile"
popupClassName="desktop-navbar-submenu" popupClassName="desktop-navbar-submenu"
title={ title={
<span data-test="ProfileDropdown" className="desktop-navbar-profile-menu-title"> <span data-test="ProfileDropdown" className="desktop-navbar-profile-menu-title">
<img className="profile__image_thumb" src={currentUser.profile_image_url} alt={currentUser.name} /> <img className="profile__image_thumb" src={currentUser.profile_image_url} alt={currentUser.name} />
<span>{currentUser.name}</span>
</span> </span>
}> }>
<Menu.Item key="profile"> <Menu.Item key="profile">
@@ -167,10 +184,6 @@ export default function DesktopNavbar() {
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
</NavbarSection> </NavbarSection>
<Button onClick={() => setCollapsed(!collapsed)} className="desktop-navbar-collapse-button">
{collapsed ? <MenuUnfoldOutlinedIcon /> : <MenuFoldOutlinedIcon />}
</Button>
</div> </div>
); );
} }

View File

@@ -1,12 +1,17 @@
@backgroundColor: #001529; @backgroundColor: #001529;
@dividerColor: rgba(255, 255, 255, 0.5); @dividerColor: rgba(255, 255, 255, 0.5);
@textColor: rgba(255, 255, 255, 0.75); @textColor: rgba(255, 255, 255, 0.75);
@brandColor: #ff7964; // Redash logo color
@activeItemColor: @brandColor;
@iconSize: 26px;
.desktop-navbar { .desktop-navbar {
background: @backgroundColor; background: @backgroundColor;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
width: 80px;
overflow: hidden;
&-spacer { &-spacer {
flex: 1 1 auto; flex: 1 1 auto;
@@ -21,12 +26,6 @@
height: 40px; height: 40px;
transition: all 270ms; transition: all 270ms;
} }
&.ant-menu-inline-collapsed {
img {
height: 20px;
}
}
} }
.help-trigger { .help-trigger {
@@ -34,26 +33,19 @@
} }
.ant-menu { .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-item,
.ant-menu-submenu { .ant-menu-submenu {
font-weight: 500; font-weight: 500;
color: @textColor; color: @textColor;
&.navbar-active-item {
box-shadow: inset 3px 0 0 @activeItemColor;
.anticon {
color: @activeItemColor;
}
}
&.ant-menu-submenu-open, &.ant-menu-submenu-open,
&.ant-menu-submenu-active, &.ant-menu-submenu-active,
&:hover, &:hover,
@@ -61,6 +53,16 @@
color: #fff; color: #fff;
} }
.anticon {
font-size: @iconSize;
margin: 0;
}
.desktop-navbar-label {
margin-top: 4px;
font-size: 11px;
}
a, a,
span, span,
.anticon { .anticon {
@@ -71,21 +73,33 @@
.ant-menu-submenu-arrow { .ant-menu-submenu-arrow {
display: none; display: none;
} }
.ant-menu-item,
.ant-menu-submenu {
padding: 0;
height: 60px;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
} }
.ant-btn.desktop-navbar-collapse-button { .ant-menu-submenu-title {
background-color: @backgroundColor; width: 100%;
border: 0; padding: 0;
border-radius: 0;
color: @textColor;
&:hover,
&:active {
color: #fff;
} }
&:after { a,
animation: 0s !important; &.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 +113,8 @@
.profile__image_thumb { .profile__image_thumb {
margin: 0; margin: 0;
vertical-align: middle; vertical-align: middle;
} width: @iconSize;
height: @iconSize;
.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;
}
} }
} }
} }

View File

@@ -52,7 +52,7 @@ export default function MenuButton({ doDelete, canEdit, mute, unmute, muted }) {
)} )}
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>
<a onClick={confirmDelete}>Delete Alert</a> <a onClick={confirmDelete}>Delete</a>
</Menu.Item> </Menu.Item>
</Menu> </Menu>
}> }>

View File

@@ -29,7 +29,7 @@ export function QuerySourceDropdown(props) {
QuerySourceDropdown.propTypes = { QuerySourceDropdown.propTypes = {
dataSources: PropTypes.any, dataSources: PropTypes.any,
value: PropTypes.string, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
disabled: PropTypes.bool, disabled: PropTypes.bool,
loading: PropTypes.bool, loading: PropTypes.bool,
onChange: PropTypes.func, onChange: PropTypes.func,

View File

@@ -15,7 +15,7 @@ export function QuerySourceDropdownItem({ dataSource, children }) {
QuerySourceDropdownItem.propTypes = { QuerySourceDropdownItem.propTypes = {
dataSource: PropTypes.shape({ dataSource: PropTypes.shape({
name: PropTypes.string, name: PropTypes.string,
id: PropTypes.string, id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
type: PropTypes.string, type: PropTypes.string,
}).isRequired, }).isRequired,
children: PropTypes.element, children: PropTypes.element,

3365
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -67,9 +67,9 @@
"path-to-regexp": "^3.1.0", "path-to-regexp": "^3.1.0",
"prop-types": "^15.6.1", "prop-types": "^15.6.1",
"query-string": "^6.9.0", "query-string": "^6.9.0",
"react": "^16.13.1", "react": "^16.14.0",
"react-ace": "^9.1.1", "react-ace": "^9.1.1",
"react-dom": "^16.13.1", "react-dom": "^16.14.0",
"react-grid-layout": "^0.18.2", "react-grid-layout": "^0.18.2",
"react-resizable": "^1.10.1", "react-resizable": "^1.10.1",
"react-virtualized": "^9.21.2", "react-virtualized": "^9.21.2",
@@ -89,6 +89,7 @@
"@cypress/code-coverage": "^3.8.1", "@cypress/code-coverage": "^3.8.1",
"@percy/agent": "0.24.3", "@percy/agent": "0.24.3",
"@percy/cypress": "^2.3.2", "@percy/cypress": "^2.3.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@types/classnames": "^2.2.10", "@types/classnames": "^2.2.10",
"@types/hoist-non-react-statics": "^3.3.1", "@types/hoist-non-react-statics": "^3.3.1",
"@types/lodash": "^4.14.157", "@types/lodash": "^4.14.157",
@@ -137,16 +138,19 @@
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^1.19.1", "prettier": "^1.19.1",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"react-refresh": "^0.9.0",
"react-test-renderer": "^16.5.2", "react-test-renderer": "^16.5.2",
"request": "^2.88.0", "request": "^2.88.0",
"request-cookies": "^1.1.0", "request-cookies": "^1.1.0",
"style-loader": "^2.0.0",
"ts-migrate": "^0.1.10",
"typescript": "^3.9.6", "typescript": "^3.9.6",
"url-loader": "^1.1.2", "url-loader": "^1.1.2",
"webpack": "^4.20.2", "webpack": "^4.44.2",
"webpack-build-notifier": "^0.1.30", "webpack-build-notifier": "^0.1.30",
"webpack-bundle-analyzer": "^2.11.1", "webpack-bundle-analyzer": "^2.11.1",
"webpack-cli": "^3.1.2", "webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.9", "webpack-dev-server": "^3.11.0",
"webpack-manifest-plugin": "^2.0.4" "webpack-manifest-plugin": "^2.0.4"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@@ -9,7 +9,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin");
const LessPluginAutoPrefix = require("less-plugin-autoprefix"); const LessPluginAutoPrefix = require("less-plugin-autoprefix");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer") const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
.BundleAnalyzerPlugin; .BundleAnalyzerPlugin;
const fs = require("fs"); const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
const path = require("path"); const path = require("path");
@@ -30,6 +30,9 @@ function optionalRequire(module, defaultReturn = undefined) {
const CONFIG = optionalRequire("./scripts/config", {}); const CONFIG = optionalRequire("./scripts/config", {});
const isProduction = process.env.NODE_ENV === "production"; const isProduction = process.env.NODE_ENV === "production";
const isDevelopment = !isProduction;
const isHotReloadingEnabled =
isDevelopment && process.env.HOT_RELOAD === "true";
const redashBackend = process.env.REDASH_BACKEND || "http://localhost:5000"; const redashBackend = process.env.REDASH_BACKEND || "http://localhost:5000";
const baseHref = CONFIG.baseHref || "/"; const baseHref = CONFIG.baseHref || "/";
@@ -45,7 +48,8 @@ const extensionPath = path.join(__dirname, extensionsRelativePath);
// Function to apply configuration overrides (see scripts/README) // Function to apply configuration overrides (see scripts/README)
function maybeApplyOverrides(config) { function maybeApplyOverrides(config) {
const overridesLocation = process.env.REDASH_WEBPACK_OVERRIDES || "./scripts/webpack/overrides"; const overridesLocation =
process.env.REDASH_WEBPACK_OVERRIDES || "./scripts/webpack/overrides";
const applyOverrides = optionalRequire(overridesLocation); const applyOverrides = optionalRequire(overridesLocation);
if (!applyOverrides) { if (!applyOverrides) {
return config; return config;
@@ -97,6 +101,7 @@ const config = {
filename: "multi_org.html", filename: "multi_org.html",
excludeChunks: ["server"] excludeChunks: ["server"]
}), }),
isProduction &&
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: "[name].[chunkhash].css" filename: "[name].[chunkhash].css"
}), }),
@@ -110,8 +115,9 @@ const config = {
{ from: "client/app/unsupportedRedirect.js" }, { from: "client/app/unsupportedRedirect.js" },
{ from: "client/app/assets/css/*.css", to: "styles/", flatten: true }, { from: "client/app/assets/css/*.css", to: "styles/", flatten: true },
{ from: "client/app/assets/fonts", to: "fonts/" } { from: "client/app/assets/fonts", to: "fonts/" }
]) ]),
], isHotReloadingEnabled && new ReactRefreshWebpackPlugin({ overlay: false })
].filter(Boolean),
optimization: { optimization: {
splitChunks: { splitChunks: {
chunks: chunk => { chunks: chunk => {
@@ -124,7 +130,17 @@ const config = {
{ {
test: /\.(t|j)sx?$/, test: /\.(t|j)sx?$/,
exclude: /node_modules/, exclude: /node_modules/,
use: ["babel-loader", "eslint-loader"] use: [
{
loader: require.resolve("babel-loader"),
options: {
plugins: [
isHotReloadingEnabled && require.resolve("react-refresh/babel")
].filter(Boolean)
}
},
require.resolve("eslint-loader")
]
}, },
{ {
test: /\.html$/, test: /\.html$/,
@@ -139,7 +155,7 @@ const config = {
test: /\.css$/, test: /\.css$/,
use: [ use: [
{ {
loader: MiniCssExtractPlugin.loader loader: isProduction ? MiniCssExtractPlugin.loader : "style-loader"
}, },
{ {
loader: "css-loader", loader: "css-loader",
@@ -153,12 +169,12 @@ const config = {
test: /\.less$/, test: /\.less$/,
use: [ use: [
{ {
loader: MiniCssExtractPlugin.loader loader: isProduction ? MiniCssExtractPlugin.loader : "style-loader"
}, },
{ {
loader: "css-loader", loader: "css-loader",
options: { options: {
minimize: process.env.NODE_ENV === "production" minimize: isProduction
} }
}, },
{ {
@@ -258,7 +274,8 @@ const config = {
stats: { stats: {
modules: false, modules: false,
chunkModules: false chunkModules: false
} },
hot: isHotReloadingEnabled
}, },
performance: { performance: {
hints: false hints: false