From baa356e26d4f7cfe7025bc7d518184ce7ccf3cd8 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Tue, 3 Mar 2020 08:22:26 +0100 Subject: [PATCH] Migration: Save dashboard modals (#22395) * Add mechanism for imperatively showing modals * Migration work in progress * Reorganise save modal components * use app events emmiter instead of root scope one * Add center alignment to layoout component * Make save buttons wotk * Prettier * Remove save dashboard logic from dashboard srv * Remove unused code * Dont show error notifications * Save modal when dashboard is overwritten * For tweaks * Folder picker tweaks * Save dashboard tweaks * Copy provisioned dashboard to clipboard * Enable saving dashboard json to file * Use SaveDashboardAsButton * Review * Align buttons in dashboard settings * Migrate SaveDashboardAs tests * TS fixes * SaveDashboardForm tests migrated * Fixe some failing tests * Fix folder picker tests * Fix HistoryListCtrl tests * Remove old import * Enable fixed positioning for folder picker select menu * Modal: show react modals with appEvents * Open react modals using event * Move save dashboard modals to dashboard feature * Make e2e pass * Update public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx * Hacking old vs new buttons to make all the things look like it's old good Grafana ;) Co-authored-by: Alexander Zobnin --- .../src/pages/saveDashboardModal.ts | 2 + .../src/components/Button/Button.tsx | 5 +- .../components/ConfirmModal/ConfirmModal.tsx | 15 +- .../src/components/Forms/Button.tsx | 17 +- .../grafana-ui/src/components/Forms/Form.tsx | 28 +--- .../components/Forms/Select/SelectBase.tsx | 3 + .../components/Forms/TextArea/TextArea.tsx | 24 +-- .../grafana-ui/src/components/Forms/index.ts | 1 + .../grafana-ui/src/components/Icon/types.ts | 3 +- .../src/components/Layout/Layout.tsx | 2 +- .../grafana-ui/src/components/Modal/Modal.tsx | 114 +++++++------- .../src/components/Modal/ModalsContext.tsx | 63 ++++++++ packages/grafana-ui/src/components/index.ts | 5 +- public/app/core/angular_wrappers.ts | 14 ++ .../CopyToClipboard/CopyToClipboard.tsx | 2 +- .../components/Select/FolderPicker.test.tsx | 20 +-- .../core/components/Select/FolderPicker.tsx | 76 +++++---- .../components/modals/AngularModalProxy.tsx | 16 ++ public/app/core/services/keybindingSrv.ts | 8 +- public/app/core/services/util_srv.ts | 31 +++- .../app/core/utils/connectWithReduxStore.tsx | 13 +- .../dashboard/components/DashNav/DashNav.tsx | 26 +++- .../DashboardSettings/SettingsCtrl.ts | 17 +- .../DashboardSettings/template.html | 14 +- .../SaveDashboard/SaveDashboardAsModal.tsx | 38 +++++ .../SaveDashboard/SaveDashboardButton.tsx | 92 +++++++++++ .../SaveDashboard/SaveDashboardErrorProxy.tsx | 121 ++++++++++++++ .../SaveDashboard/SaveDashboardModal.tsx | 39 +++++ .../SaveDashboard/SaveDashboardModalProxy.tsx | 26 ++++ .../SaveProvisionedDashboard.tsx | 12 ++ .../forms/SaveDashboardAsForm.test.tsx | 113 ++++++++++++++ .../forms/SaveDashboardAsForm.tsx | 107 +++++++++++++ .../forms/SaveDashboardForm.test.tsx | 100 ++++++++++++ .../SaveDashboard/forms/SaveDashboardForm.tsx | 67 ++++++++ .../forms/SaveProvisionedDashboardForm.tsx | 71 +++++++++ .../components/SaveDashboard/types.ts | 21 +++ .../SaveDashboard/useDashboardSave.tsx | 49 ++++++ .../SaveDashboardAsModalCtrl.test.ts | 71 --------- .../SaveModals/SaveDashboardAsModalCtrl.ts | 124 --------------- .../SaveModals/SaveDashboardModalCtrl.test.ts | 57 ------- .../SaveModals/SaveDashboardModalCtrl.ts | 141 ----------------- .../SaveProvisionedDashboardModalCtrl.test.ts | 30 ---- .../SaveProvisionedDashboardModalCtrl.ts | 84 ---------- .../dashboard/components/SaveModals/index.ts | 3 - .../UnsavedChangesModalCtrl.ts | 7 +- .../VersionHistory/HistoryListCtrl.test.ts | 20 ++- .../VersionHistory/HistoryListCtrl.ts | 3 +- public/app/features/dashboard/index.ts | 1 - .../dashboard/services/ChangeTracker.ts | 17 +- .../dashboard/services/DashboardSrv.ts | 147 +----------------- public/app/routes/ReactContainer.tsx | 17 +- public/app/types/events.ts | 6 + 52 files changed, 1235 insertions(+), 868 deletions(-) create mode 100644 packages/grafana-ui/src/components/Modal/ModalsContext.tsx create mode 100644 public/app/core/components/modals/AngularModalProxy.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/SaveDashboardAsModal.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/SaveDashboardButton.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/SaveDashboardModal.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/SaveDashboardModalProxy.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/SaveProvisionedDashboard.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.test.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardAsForm.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.test.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/forms/SaveDashboardForm.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/forms/SaveProvisionedDashboardForm.tsx create mode 100644 public/app/features/dashboard/components/SaveDashboard/types.ts create mode 100644 public/app/features/dashboard/components/SaveDashboard/useDashboardSave.tsx delete mode 100644 public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.test.ts delete mode 100644 public/app/features/dashboard/components/SaveModals/SaveDashboardAsModalCtrl.ts delete mode 100644 public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.test.ts delete mode 100644 public/app/features/dashboard/components/SaveModals/SaveDashboardModalCtrl.ts delete mode 100644 public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.test.ts delete mode 100644 public/app/features/dashboard/components/SaveModals/SaveProvisionedDashboardModalCtrl.ts delete mode 100644 public/app/features/dashboard/components/SaveModals/index.ts diff --git a/packages/grafana-e2e/src/pages/saveDashboardModal.ts b/packages/grafana-e2e/src/pages/saveDashboardModal.ts index f1131808325..aafd837e71b 100644 --- a/packages/grafana-e2e/src/pages/saveDashboardModal.ts +++ b/packages/grafana-e2e/src/pages/saveDashboardModal.ts @@ -4,5 +4,7 @@ export const SaveDashboardModal = pageFactory({ url: '', selectors: { save: 'Dashboard settings Save Dashboard Modal Save button', + saveVariables: 'Dashboard settings Save Dashboard Modal Save variables checkbox', + saveTimerange: 'Dashboard settings Save Dashboard Modal Save timerange checkbox', }, }); diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index cad5db2b3d6..b9271e3e536 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -3,6 +3,7 @@ import { ThemeContext } from '../../themes'; import { getButtonStyles } from './styles'; import { ButtonContent } from './ButtonContent'; import { ButtonSize, ButtonStyles, ButtonVariant } from './types'; +import { cx } from 'emotion'; type CommonProps = { size?: ButtonSize; @@ -34,7 +35,7 @@ export const Button = React.forwardRef((props, r }); return ( - ); @@ -62,7 +63,7 @@ export const LinkButton = React.forwardRef(( }); return ( - + {children} ); diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx index ca3906e2fef..84e5de2a8c2 100644 --- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx +++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx @@ -5,6 +5,7 @@ import { IconType } from '../Icon/types'; import { Button } from '../Button/Button'; import { stylesFactory, ThemeContext } from '../../themes'; import { GrafanaTheme } from '@grafana/data'; +import { HorizontalGroup } from '..'; const getStyles = stylesFactory((theme: GrafanaTheme) => ({ modal: css` @@ -19,13 +20,6 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({ margin-bottom: calc(${theme.spacing.d} * 2); padding-top: ${theme.spacing.d}; `, - modalButtonRow: css` - margin-bottom: 14px; - a, - button { - margin-right: ${theme.spacing.d}; - } - `, })); const defaultIcon: IconType = 'exclamation-triangle'; @@ -33,11 +27,10 @@ const defaultIcon: IconType = 'exclamation-triangle'; interface Props { isOpen: boolean; title: string; - body: string; + body: React.ReactNode; confirmText: string; dismissText?: string; icon?: IconType; - onConfirm(): void; onDismiss(): void; } @@ -59,14 +52,14 @@ export const ConfirmModal: FC = ({
{body}
-
+ -
+
); diff --git a/packages/grafana-ui/src/components/Forms/Button.tsx b/packages/grafana-ui/src/components/Forms/Button.tsx index 4a2e0baddc8..0b8c1a4d38f 100644 --- a/packages/grafana-ui/src/components/Forms/Button.tsx +++ b/packages/grafana-ui/src/components/Forms/Button.tsx @@ -63,7 +63,6 @@ const getPropertiesForVariant = (theme: GrafanaTheme, variant: ButtonVariant) => } `, }; - case 'primary': default: return { @@ -139,23 +138,23 @@ type CommonProps = { export type ButtonProps = CommonProps & ButtonHTMLAttributes; -export const Button = React.forwardRef((props, ref) => { +export const Button = React.forwardRef(({ variant, ...otherProps }, ref) => { const theme = useContext(ThemeContext); const styles = getButtonStyles({ theme, - size: props.size || 'md', - variant: props.variant || 'primary', + size: otherProps.size || 'md', + variant: variant || 'primary', }); - return ; + return ; }); type ButtonLinkProps = CommonProps & AnchorHTMLAttributes; -export const LinkButton = React.forwardRef((props, ref) => { +export const LinkButton = React.forwardRef(({ variant, ...otherProps }, ref) => { const theme = useContext(ThemeContext); const styles = getButtonStyles({ theme, - size: props.size || 'md', - variant: props.variant || 'primary', + size: otherProps.size || 'md', + variant: variant || 'primary', }); - return ; + return ; }); diff --git a/packages/grafana-ui/src/components/Forms/Form.tsx b/packages/grafana-ui/src/components/Forms/Form.tsx index 8ef3d526e6f..b9a75500a1f 100644 --- a/packages/grafana-ui/src/components/Forms/Form.tsx +++ b/packages/grafana-ui/src/components/Forms/Form.tsx @@ -1,20 +1,5 @@ -/** - * This is a stub implementation only for correct styles to be applied - */ - import React, { useEffect } from 'react'; import { useForm, Mode, OnSubmit, DeepPartial, FormContextValues } from 'react-hook-form'; -import { GrafanaTheme } from '@grafana/data'; -import { css } from 'emotion'; -import { stylesFactory, useTheme } from '../../themes'; - -const getFormStyles = stylesFactory((theme: GrafanaTheme) => { - return { - form: css` - margin-bottom: ${theme.spacing.formMargin}; - `, - }; -}); type FormAPI = Pick, 'register' | 'errors' | 'control'>; @@ -26,23 +11,14 @@ interface FormProps { } export function Form({ validateOn, defaultValues, onSubmit, children }: FormProps) { - const theme = useTheme(); const { handleSubmit, register, errors, control, reset, getValues } = useForm({ mode: validateOn || 'onSubmit', - defaultValues: { - ...defaultValues, - }, + defaultValues, }); useEffect(() => { reset({ ...getValues(), ...defaultValues }); }, [defaultValues]); - const styles = getFormStyles(theme); - - return ( -
- {children({ register, errors, control })} -
- ); + return
{children({ register, errors, control })}
; } diff --git a/packages/grafana-ui/src/components/Forms/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Forms/Select/SelectBase.tsx index bea9239b2e5..0af6db41447 100644 --- a/packages/grafana-ui/src/components/Forms/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Forms/Select/SelectBase.tsx @@ -65,6 +65,7 @@ export interface SelectCommonProps { prefix?: JSX.Element | string | null; /** Use a custom element to control Select. A proper ref to the renderControl is needed if 'portal' isn't set to null*/ renderControl?: ControlComponent; + menuPosition?: 'fixed' | 'absolute'; } export interface SelectAsyncProps { @@ -176,6 +177,7 @@ export function SelectBase({ width, invalid, components, + menuPosition, }: SelectBaseProps) { const theme = useTheme(); const styles = getSelectStyles(theme); @@ -246,6 +248,7 @@ export function SelectBase({ renderControl, captureMenuScroll: false, menuPlacement: 'auto', + menuPosition, }; // width property is deprecated in favor of size or className diff --git a/packages/grafana-ui/src/components/Forms/TextArea/TextArea.tsx b/packages/grafana-ui/src/components/Forms/TextArea/TextArea.tsx index b91b49547bc..c84322693c3 100644 --- a/packages/grafana-ui/src/components/Forms/TextArea/TextArea.tsx +++ b/packages/grafana-ui/src/components/Forms/TextArea/TextArea.tsx @@ -1,4 +1,4 @@ -import React, { HTMLProps, forwardRef } from 'react'; +import React, { HTMLProps } from 'react'; import { GrafanaTheme } from '@grafana/data'; import { css, cx } from 'emotion'; import { stylesFactory, useTheme } from '../../../themes'; @@ -12,6 +12,17 @@ export interface Props extends Omit, 'size'> { size?: FormInputSize; } +export const TextArea = React.forwardRef(({ invalid, size = 'auto', ...props }, ref) => { + const theme = useTheme(); + const styles = getTextAreaStyle(theme, invalid); + + return ( +
+