diff --git a/README.md b/README.md index c9b1cd3bbddd..99370f95b34d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# [Grafana](https://grafana.com) [![Circle CI](https://circleci.com/gh/grafana/grafana.svg?style=svg)](https://circleci.com/gh/grafana/grafana) [![Go Report Card](https://goreportcard.com/badge/github.com/grafana/grafana)](https://goreportcard.com/report/github.com/grafana/grafana) [![codecov](https://codecov.io/gh/grafana/grafana/branch/master/graph/badge.svg)](https://codecov.io/gh/grafana/grafana) +# [Grafana](https://grafana.com) [![Circle CI](https://circleci.com/gh/grafana/grafana.svg?style=svg)](https://circleci.com/gh/grafana/grafana) [![Go Report Card](https://goreportcard.com/badge/github.com/grafana/grafana)](https://goreportcard.com/report/github.com/grafana/grafana) [Website](https://grafana.com) | [Twitter](https://twitter.com/grafana) | diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 2cf89d0be5e6..b14641892469 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -1,7 +1,7 @@ import React, { Component, createRef } from 'react'; import omit from 'lodash/omit'; -import { PopperController } from '../Tooltip/PopperController'; -import { Popper } from '../Tooltip/Popper'; +import { PopoverController } from '../Tooltip/PopoverController'; +import { Popover } from '../Tooltip/Popover'; import { ColorPickerPopover, ColorPickerProps, ColorPickerChangeHandler } from './ColorPickerPopover'; import { getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; import { SeriesColorPickerPopover } from './SeriesColorPickerPopover'; @@ -46,12 +46,12 @@ export const colorPickerFactory = ( }); return ( - + {(showPopper, hidePopper, popperProps) => { return ( <> {this.pickerTriggerRef.current && ( - ( ); }} - + ); } }; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index 934bc56a550b..e75985aca205 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { NamedColorsPalette } from './NamedColorsPalette'; import { getColorName, getColorFromHexRgbOrName } from '../../utils/namedColorsPalette'; -import { PopperContentProps } from '../Tooltip/PopperController'; +import { PopoverContentProps } from '../Tooltip/Tooltip'; import SpectrumPalette from './SpectrumPalette'; import { GrafanaThemeType, Themeable } from '../../types/theme'; import { warnAboutColorPickerPropsDeprecation } from './warnAboutColorPickerPropsDeprecation'; @@ -19,7 +19,7 @@ export interface ColorPickerProps extends Themeable { enableNamedColors?: boolean; } -export interface Props extends ColorPickerProps, PopperContentProps { +export interface Props extends ColorPickerProps, PopoverContentProps { customPickers?: T; } diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 219d2dd357a0..c40d7434611b 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -1,11 +1,11 @@ import React, { FunctionComponent } from 'react'; import { ColorPickerPopover, ColorPickerProps } from './ColorPickerPopover'; -import { PopperContentProps } from '../Tooltip/PopperController'; +import { PopoverContentProps } from '../Tooltip/Tooltip'; import { Switch } from '../Switch/Switch'; import { withTheme } from '../../themes/ThemeContext'; -export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopperContentProps { +export interface SeriesColorPickerPopoverProps extends ColorPickerProps, PopoverContentProps { yaxis?: number; onToggleAxis?: () => void; } diff --git a/packages/grafana-ui/src/components/FormField/FormField.tsx b/packages/grafana-ui/src/components/FormField/FormField.tsx index 13005dfaca37..c18148b49429 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.tsx @@ -1,10 +1,10 @@ import React, { InputHTMLAttributes, FunctionComponent } from 'react'; import { FormLabel } from '../FormLabel/FormLabel'; -import { PopperContent } from '../Tooltip/PopperController'; +import { PopoverContent } from '../Tooltip/Tooltip'; import { cx } from 'emotion'; export interface Props extends InputHTMLAttributes { label: string; - tooltip?: PopperContent; + tooltip?: PopoverContent; labelWidth?: number; inputWidth?: number; inputEl?: React.ReactNode; diff --git a/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx b/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx index 5e54cb2de439..b4fc429727f2 100644 --- a/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx +++ b/packages/grafana-ui/src/components/FormLabel/FormLabel.tsx @@ -1,7 +1,6 @@ import React, { FunctionComponent, ReactNode } from 'react'; import classNames from 'classnames'; -import { Tooltip } from '../Tooltip/Tooltip'; -import { PopperContent } from '../Tooltip/PopperController'; +import { Tooltip, PopoverContent } from '../Tooltip/Tooltip'; interface Props { children: ReactNode; @@ -9,7 +8,7 @@ interface Props { htmlFor?: string; isFocused?: boolean; isInvalid?: boolean; - tooltip?: PopperContent; + tooltip?: PopoverContent; width?: number; } diff --git a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx index 98eeaef9646f..189aa36d86ca 100644 --- a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx +++ b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import { Tooltip, TooltipProps } from '../Tooltip/Tooltip'; -import { PopperContent } from '../Tooltip/PopperController'; +import { Tooltip, TooltipProps, PopoverContent } from '../Tooltip/Tooltip'; interface InfoTooltipProps extends Omit { - children: PopperContent; + children: PopoverContent; } export const InfoTooltip = ({ children, ...restProps }: InfoTooltipProps) => { diff --git a/packages/grafana-ui/src/components/Select/ButtonSelect.tsx b/packages/grafana-ui/src/components/Select/ButtonSelect.tsx index 5ed8419608d2..b15fd9e21526 100644 --- a/packages/grafana-ui/src/components/Select/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Select/ButtonSelect.tsx @@ -1,6 +1,6 @@ import React, { PureComponent, ReactElement } from 'react'; import Select from './Select'; -import { PopperContent } from '../Tooltip/PopperController'; +import { PopoverContent } from '../Tooltip/Tooltip'; import { SelectableValue } from '@grafana/data'; interface ButtonComponentProps { @@ -38,7 +38,7 @@ export interface Props { components?: any; maxMenuHeight?: number; onChange: (item: SelectableValue) => void; - tooltipContent?: PopperContent; + tooltipContent?: PopoverContent; isMenuOpen?: boolean; onOpenMenu?: () => void; onCloseMenu?: () => void; diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx index 78d63675c1a3..929d369c9513 100644 --- a/packages/grafana-ui/src/components/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Select/Select.tsx @@ -17,7 +17,7 @@ import IndicatorsContainer from './IndicatorsContainer'; import NoOptionsMessage from './NoOptionsMessage'; import resetSelectStyles from './resetSelectStyles'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; -import { PopperContent } from '../Tooltip/PopperController'; +import { PopoverContent } from '../Tooltip/Tooltip'; import { Tooltip } from '../Tooltip/Tooltip'; import { SelectableValue } from '@grafana/data'; @@ -43,7 +43,7 @@ export interface CommonProps { backspaceRemovesValue?: boolean; isOpen?: boolean; components?: any; - tooltipContent?: PopperContent; + tooltipContent?: PopoverContent; onOpenMenu?: () => void; onCloseMenu?: () => void; tabSelectsValue?: boolean; @@ -269,7 +269,7 @@ export interface TooltipWrapperProps { onOpenMenu?: () => void; onCloseMenu?: () => void; isOpen?: boolean; - tooltipContent?: PopperContent; + tooltipContent?: PopoverContent; } export interface TooltipWrapperState { diff --git a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap index 0743bea9ef4a..eb61c639d816 100644 --- a/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap +++ b/packages/grafana-ui/src/components/ThresholdsEditor/__snapshots__/ThresholdsEditor.test.tsx.snap @@ -225,7 +225,7 @@ exports[`Render should render with base threshold 1`] = ` } } > - - + diff --git a/packages/grafana-ui/src/components/Tooltip/Popper.tsx b/packages/grafana-ui/src/components/Tooltip/Popover.tsx similarity index 95% rename from packages/grafana-ui/src/components/Tooltip/Popper.tsx rename to packages/grafana-ui/src/components/Tooltip/Popover.tsx index 84b92d8233a0..49daf9c589e6 100644 --- a/packages/grafana-ui/src/components/Tooltip/Popper.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Popover.tsx @@ -3,7 +3,7 @@ import * as PopperJS from 'popper.js'; import { Manager, Popper as ReactPopper, PopperArrowProps } from 'react-popper'; import { Portal } from '../Portal/Portal'; import Transition from 'react-transition-group/Transition'; -import { PopperContent } from './PopperController'; +import { PopoverContent } from './Tooltip'; const defaultTransitionStyles = { transition: 'opacity 200ms linear', @@ -22,14 +22,14 @@ export type RenderPopperArrowFn = (props: { arrowProps: PopperArrowProps; placem interface Props extends React.HTMLAttributes { show: boolean; placement?: PopperJS.Placement; - content: PopperContent; + content: PopoverContent; referenceElement: PopperJS.ReferenceObject; wrapperClassName?: string; renderArrow?: RenderPopperArrowFn; eventsEnabled?: boolean; } -class Popper extends PureComponent { +class Popover extends PureComponent { static defaultProps: Partial = { eventsEnabled: true, }; @@ -101,4 +101,4 @@ class Popper extends PureComponent { } } -export { Popper }; +export { Popover }; diff --git a/packages/grafana-ui/src/components/Tooltip/PopperController.tsx b/packages/grafana-ui/src/components/Tooltip/PopoverController.tsx similarity index 83% rename from packages/grafana-ui/src/components/Tooltip/PopperController.tsx rename to packages/grafana-ui/src/components/Tooltip/PopoverController.tsx index 79556bd816f1..a76f89c12ea9 100644 --- a/packages/grafana-ui/src/components/Tooltip/PopperController.tsx +++ b/packages/grafana-ui/src/components/Tooltip/PopoverController.tsx @@ -1,18 +1,14 @@ import React from 'react'; import * as PopperJS from 'popper.js'; +import { PopoverContent } from './Tooltip'; // This API allows popovers to update Popper's position when e.g. popover content changes // updatePopperPosition is delivered to content by react-popper -export interface PopperContentProps { - updatePopperPosition?: () => void; -} - -export type PopperContent = string | React.ReactElement | ((props: T) => JSX.Element); export interface UsingPopperProps { show?: boolean; placement?: PopperJS.Placement; - content: PopperContent; + content: PopoverContent; children: JSX.Element; } @@ -22,13 +18,13 @@ type PopperControllerRenderProp = ( popperProps: { show: boolean; placement: PopperJS.Placement; - content: PopperContent; + content: PopoverContent; } ) => JSX.Element; interface Props { placement?: PopperJS.Placement; - content: PopperContent; + content: PopoverContent; className?: string; children: PopperControllerRenderProp; hideAfter?: number; @@ -39,7 +35,7 @@ interface State { show: boolean; } -class PopperController extends React.Component { +class PopoverController extends React.Component { private hideTimeout: any; constructor(props: Props) { @@ -101,4 +97,4 @@ class PopperController extends React.Component { } } -export { PopperController }; +export { PopoverController }; diff --git a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx index e03103002888..4e09a55dbeb1 100644 --- a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx @@ -1,17 +1,24 @@ import React, { createRef } from 'react'; import * as PopperJS from 'popper.js'; -import { Popper } from './Popper'; -import { PopperController, UsingPopperProps } from './PopperController'; +import { Popover } from './Popover'; +import { PopoverController, UsingPopperProps } from './PopoverController'; export interface TooltipProps extends UsingPopperProps { theme?: 'info' | 'error'; } + +export interface PopoverContentProps { + updatePopperPosition?: () => void; +} + +export type PopoverContent = string | React.ReactElement | ((props: PopoverContentProps) => JSX.Element); + export const Tooltip = ({ children, theme, ...controllerProps }: TooltipProps) => { const tooltipTriggerRef = createRef(); const popperBackgroundClassName = 'popper__background' + (theme ? ' popper__background--' + theme : ''); return ( - + {(showPopper, hidePopper, popperProps) => { { /* Override internal 'show' state if passed in as prop */ @@ -23,7 +30,7 @@ export const Tooltip = ({ children, theme, ...controllerProps }: TooltipProps) = return ( <> {tooltipTriggerRef.current && ( - ); }} - + ); }; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts index 7d1261ef7fc0..52a1994640f5 100644 --- a/packages/grafana-ui/src/components/index.ts +++ b/packages/grafana-ui/src/components/index.ts @@ -1,7 +1,7 @@ export { DeleteButton } from './DeleteButton/DeleteButton'; -export { Tooltip } from './Tooltip/Tooltip'; -export { PopperController, PopperContent } from './Tooltip/PopperController'; -export { Popper } from './Tooltip/Popper'; +export { Tooltip, PopoverContent } from './Tooltip/Tooltip'; +export { PopoverController } from './Tooltip/PopoverController'; +export { Popover } from './Tooltip/Popover'; export { Portal } from './Portal/Portal'; export { CustomScrollbar } from './CustomScrollbar/CustomScrollbar'; diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts index e1fde180923b..86249c670d61 100644 --- a/public/app/core/angular_wrappers.ts +++ b/public/app/core/angular_wrappers.ts @@ -11,6 +11,7 @@ import { ColorPicker, SeriesColorPickerPopoverWithTheme, SecretFormField, DataLi import { FunctionEditor } from 'app/plugins/datasource/graphite/FunctionEditor'; import { SearchField } from './components/search/SearchField'; import { GraphContextMenu } from 'app/plugins/panel/graph/GraphContextMenu'; +import ReactProfileWrapper from 'app/features/profile/ReactProfileWrapper'; export function registerAngularDirectives() { react2AngularDirective('sidemenu', SideMenu, []); @@ -87,4 +88,6 @@ export function registerAngularDirectives() { 'suggestions', ['onChange', { watchDepth: 'reference', wrapApply: true }], ]); + + react2AngularDirective('reactProfileWrapper', ReactProfileWrapper, []); } diff --git a/public/app/core/utils/UserProvider.tsx b/public/app/core/utils/UserProvider.tsx index bf855f2879a7..bdb06c07910f 100644 --- a/public/app/core/utils/UserProvider.tsx +++ b/public/app/core/utils/UserProvider.tsx @@ -1,12 +1,17 @@ import React, { PureComponent } from 'react'; import { getBackendSrv } from '@grafana/runtime'; +import { User } from 'app/types'; export interface UserAPI { - changePassword: (ChangePassword: ChangePasswordFields) => void; + changePassword: (changePassword: ChangePasswordFields) => void; + updateUserProfile: (profile: ProfileUpdateFields) => void; + loadUser: () => void; } interface LoadingStates { changePassword: boolean; + loadUser: boolean; + updateUserProfile: boolean; } export interface ChangePasswordFields { @@ -15,11 +20,19 @@ export interface ChangePasswordFields { confirmNew: string; } +export interface ProfileUpdateFields { + name: string; + email: string; + login: string; +} + export interface Props { - children: (api: UserAPI, states: LoadingStates) => JSX.Element; + userId?: number; // passed, will load user on mount + children: (api: UserAPI, states: LoadingStates, user?: User) => JSX.Element; } export interface State { + user?: User; loadingStates: LoadingStates; } @@ -27,24 +40,55 @@ export class UserProvider extends PureComponent { state: State = { loadingStates: { changePassword: false, + loadUser: true, + updateUserProfile: false, }, }; + componentDidMount() { + if (this.props.userId) { + this.loadUser(); + } + } + changePassword = async (payload: ChangePasswordFields) => { this.setState({ loadingStates: { ...this.state.loadingStates, changePassword: true } }); await getBackendSrv().put('/api/user/password', payload); this.setState({ loadingStates: { ...this.state.loadingStates, changePassword: false } }); }; + loadUser = async () => { + this.setState({ + loadingStates: { ...this.state.loadingStates, loadUser: true }, + }); + const user = await getBackendSrv().get('/api/user'); + this.setState({ user, loadingStates: { ...this.state.loadingStates, loadUser: Object.keys(user).length === 0 } }); + }; + + updateUserProfile = async (payload: ProfileUpdateFields) => { + this.setState({ loadingStates: { ...this.state.loadingStates, updateUserProfile: true } }); + await getBackendSrv() + .put('/api/user', payload) + .then(() => { + this.loadUser(); + }) + .catch(e => console.log(e)) + .finally(() => { + this.setState({ loadingStates: { ...this.state.loadingStates, updateUserProfile: false } }); + }); + }; + render() { const { children } = this.props; - const { loadingStates } = this.state; + const { loadingStates, user } = this.state; const api = { changePassword: this.changePassword, + loadUser: this.loadUser, + updateUserProfile: this.updateUserProfile, }; - return <>{children(api, loadingStates)}; + return <>{children(api, loadingStates, user)}; } } diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 2df808c19a5a..0a34242450ed 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { renderMarkdown } from '@grafana/data'; -import { Tooltip, ScopedVars, PopperContent } from '@grafana/ui'; +import { Tooltip, ScopedVars, PopoverContent } from '@grafana/ui'; import { DataLink } from '@grafana/data'; import { PanelModel } from 'app/features/dashboard/state/PanelModel'; @@ -71,7 +71,7 @@ export class PanelHeaderCorner extends Component { ); }; - renderCornerType(infoMode: InfoMode, content: PopperContent) { + renderCornerType(infoMode: InfoMode, content: PopoverContent) { const theme = infoMode === InfoMode.Error ? 'error' : 'info'; return ( diff --git a/public/app/features/plugins/PluginStateInfo.tsx b/public/app/features/plugins/PluginStateInfo.tsx index fe8ce13095ea..5da0acc9e888 100644 --- a/public/app/features/plugins/PluginStateInfo.tsx +++ b/public/app/features/plugins/PluginStateInfo.tsx @@ -1,13 +1,13 @@ import React, { FC, useContext } from 'react'; import { css } from 'emotion'; import { PluginState, Tooltip, ThemeContext } from '@grafana/ui'; -import { PopperContent } from '@grafana/ui/src/components/Tooltip/PopperController'; +import { PopoverContent } from '@grafana/ui/src/components/Tooltip/Tooltip'; interface Props { state?: PluginState; } -function getPluginStateInfoText(state?: PluginState): PopperContent | null { +function getPluginStateInfoText(state?: PluginState): PopoverContent | null { switch (state) { case PluginState.alpha: return ( diff --git a/public/app/features/profile/ChangePasswordForm.tsx b/public/app/features/profile/ChangePasswordForm.tsx index d88c93857956..05d84d04ec2a 100644 --- a/public/app/features/profile/ChangePasswordForm.tsx +++ b/public/app/features/profile/ChangePasswordForm.tsx @@ -16,15 +16,11 @@ export interface State { } export class ChangePasswordForm extends PureComponent { - constructor(props: Props) { - super(props); - - this.state = { - oldPassword: '', - newPassword: '', - confirmNew: '', - }; - } + state: State = { + oldPassword: '', + newPassword: '', + confirmNew: '', + }; onOldPasswordChange = (oldPassword: string) => { this.setState({ oldPassword }); diff --git a/public/app/features/profile/PrefControlCtrl.ts b/public/app/features/profile/PrefControlCtrl.ts deleted file mode 100644 index 39a01bd0e2df..000000000000 --- a/public/app/features/profile/PrefControlCtrl.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { react2AngularDirective } from 'app/core/utils/react2angular'; -import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences'; - -react2AngularDirective('prefsControl', SharedPreferences, ['resourceUri']); diff --git a/public/app/features/profile/ProfileCtrl.ts b/public/app/features/profile/ProfileCtrl.ts index 164c9fb98aa9..d267cd0588b7 100644 --- a/public/app/features/profile/ProfileCtrl.ts +++ b/public/app/features/profile/ProfileCtrl.ts @@ -3,7 +3,6 @@ import { coreModule, NavModelSrv } from 'app/core/core'; import { dateTime } from '@grafana/data'; import { UserSession } from 'app/types'; import { BackendSrv } from 'app/core/services/backend_srv'; -import { ILocationService } from 'angular'; export class ProfileCtrl { user: any; @@ -18,26 +17,13 @@ export class ProfileCtrl { navModel: any; /** @ngInject */ - constructor( - private backendSrv: BackendSrv, - private contextSrv: any, - private $location: ILocationService, - navModelSrv: NavModelSrv - ) { - this.getUser(); + constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) { this.getUserSessions(); this.getUserTeams(); this.getUserOrgs(); this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0); } - getUser() { - this.backendSrv.get('/api/user').then((user: any) => { - this.user = user; - this.user.theme = user.theme || 'dark'; - }); - } - getUserSessions() { this.backendSrv.get('/api/user/auth-tokens').then((sessions: UserSession[]) => { sessions.reverse(); @@ -103,19 +89,6 @@ export class ProfileCtrl { window.location.href = config.appSubUrl + '/profile'; }); } - - update() { - if (!this.userForm.$valid) { - return; - } - - this.backendSrv.put('/api/user/', this.user).then(() => { - this.contextSrv.user.name = this.user.name || this.user.login; - if (this.oldTheme !== this.user.theme) { - window.location.href = config.appSubUrl + this.$location.path(); - } - }); - } } coreModule.controller('ProfileCtrl', ProfileCtrl); diff --git a/public/app/features/profile/ReactProfileWrapper.tsx b/public/app/features/profile/ReactProfileWrapper.tsx new file mode 100644 index 000000000000..ab1c6dc7a3d5 --- /dev/null +++ b/public/app/features/profile/ReactProfileWrapper.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { UserProvider } from 'app/core/utils/UserProvider'; +import { UserProfileEditForm } from './UserProfileEditForm'; +import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences'; +import { config } from '@grafana/runtime'; + +export const ReactProfileWrapper = () => ( + + {(api, states, user) => { + return ( + <> + {!states.loadUser && ( + + )} + + + ); + }} + +); + +export default ReactProfileWrapper; diff --git a/public/app/features/profile/UserProfileEditForm.tsx b/public/app/features/profile/UserProfileEditForm.tsx new file mode 100644 index 000000000000..4b196ef089fc --- /dev/null +++ b/public/app/features/profile/UserProfileEditForm.tsx @@ -0,0 +1,105 @@ +import React, { PureComponent, ChangeEvent, MouseEvent } from 'react'; +import { Button, FormLabel, Input, Tooltip } from '@grafana/ui'; +import { User } from 'app/types'; +import config from 'app/core/config'; +import { ProfileUpdateFields } from 'app/core/utils/UserProvider'; + +export interface Props { + user: User; + isSavingUser: boolean; + updateProfile: (payload: ProfileUpdateFields) => void; +} + +export interface State { + name: string; + email: string; + login: string; +} + +export class UserProfileEditForm extends PureComponent { + constructor(props: Props) { + super(props); + + const { + user: { name, email, login }, + } = this.props; + + this.state = { + name, + email, + login, + }; + } + + onNameChange = (event: ChangeEvent) => { + this.setState({ name: event.target.value }); + }; + + onEmailChange = (event: ChangeEvent) => { + this.setState({ email: event.target.value }); + }; + + onLoginChange = (event: ChangeEvent) => { + this.setState({ login: event.target.value }); + }; + + onSubmitProfileUpdate = (event: MouseEvent) => { + event.preventDefault(); + this.props.updateProfile({ ...this.state }); + }; + + render() { + const { name, email, login } = this.state; + const { isSavingUser } = this.props; + const { disableLoginForm } = config; + + return ( + <> +

Edit Profile

+
+
+ Name + +
+
+ Email + + {disableLoginForm && ( + + + + )} +
+
+ Username + + {disableLoginForm && ( + + + + )} +
+
+ +
+ + + ); + } +} + +export default UserProfileEditForm; diff --git a/public/app/features/profile/all.ts b/public/app/features/profile/all.ts index 656ca1ddcfa1..dfe5812f5d25 100644 --- a/public/app/features/profile/all.ts +++ b/public/app/features/profile/all.ts @@ -1,2 +1 @@ import './ProfileCtrl'; -import './PrefControlCtrl'; diff --git a/public/app/features/profile/partials/profile.html b/public/app/features/profile/partials/profile.html index 7978aa410b0e..9d30a2277f3f 100644 --- a/public/app/features/profile/partials/profile.html +++ b/public/app/features/profile/partials/profile.html @@ -1,49 +1,7 @@
-

User Profile

- -
-
- Name - -
-
- Email - - -
-
- Username - - -
-
- -
-
- - +

Teams

diff --git a/public/app/features/teams/TeamMembers.test.tsx b/public/app/features/teams/TeamMembers.test.tsx index 01b9f12aa3b7..c57b577a0f9a 100644 --- a/public/app/features/teams/TeamMembers.test.tsx +++ b/public/app/features/teams/TeamMembers.test.tsx @@ -67,6 +67,8 @@ describe('Functions', () => { label: '', avatarUrl: '', login: '', + name: '', + email: '', }; instance.onAddUserToTeam(); diff --git a/public/app/plugins/datasource/graphite/FunctionEditor.tsx b/public/app/plugins/datasource/graphite/FunctionEditor.tsx index 412dc31155c4..3e38fdc12006 100644 --- a/public/app/plugins/datasource/graphite/FunctionEditor.tsx +++ b/public/app/plugins/datasource/graphite/FunctionEditor.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { PopperController, Popper } from '@grafana/ui'; +import { PopoverController, Popover } from '@grafana/ui'; // @ts-ignore import rst2html from 'rst2html'; import { FunctionDescriptor, FunctionEditorControlsProps, FunctionEditorControls } from './FunctionEditorControls'; @@ -68,12 +68,12 @@ class FunctionEditor extends React.PureComponent + {(showPopper, hidePopper, popperProps) => { return ( <> {this.triggerRef && ( - ); }} - + ); } } diff --git a/public/app/plugins/datasource/prometheus/query_hints.test.ts b/public/app/plugins/datasource/prometheus/query_hints.test.ts new file mode 100644 index 000000000000..2c1a22914a4d --- /dev/null +++ b/public/app/plugins/datasource/prometheus/query_hints.test.ts @@ -0,0 +1,44 @@ +import { getQueryHints } from './query_hints'; + +describe('getQueryHints', () => { + describe('when called without datapoints in series', () => { + it('then it should use rows instead and return correct hint', () => { + const series = [ + { + fields: [ + { + name: 'Some Name', + }, + ], + rows: [[1], [2]], + }, + ]; + + const result = getQueryHints('up', series); + expect(result).toEqual([ + { + fix: { action: { query: 'up', type: 'ADD_RATE' }, label: 'Fix by adding rate().' }, + label: 'Time series is monotonically increasing.', + type: 'APPLY_RATE', + }, + ]); + }); + }); + + describe('when called without datapoints and rows in series', () => { + it('then it should use an empty array and return null', () => { + const series = [ + { + fields: [ + { + name: 'Some Name', + }, + ], + }, + ]; + + const result = getQueryHints('up', series); + expect(result).toEqual(null); + }); + }); +}); diff --git a/public/app/plugins/datasource/prometheus/query_hints.ts b/public/app/plugins/datasource/prometheus/query_hints.ts index cc0f13ce166d..8b47ef70f825 100644 --- a/public/app/plugins/datasource/prometheus/query_hints.ts +++ b/public/app/plugins/datasource/prometheus/query_hints.ts @@ -29,7 +29,7 @@ export function getQueryHints(query: string, series?: any[], datasource?: any): // Check for monotonicity on series (table results are being ignored here) if (series && series.length > 0) { series.forEach(s => { - const datapoints: number[][] = s.datapoints; + const datapoints: number[][] = s.datapoints || s.rows || []; if (query.indexOf('rate(') === -1 && datapoints.length > 1) { let increasing = false; const nonNullData = datapoints.filter(dp => dp[0] !== null); diff --git a/public/app/types/user.ts b/public/app/types/user.ts index f1863f0387da..ddf8f6bce98a 100644 --- a/public/app/types/user.ts +++ b/public/app/types/user.ts @@ -16,6 +16,8 @@ export interface User { label: string; avatarUrl: string; login: string; + email: string; + name: string; } export interface Invitee {