diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts index e1fde180923..86249c670d6 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 bf855f2879a..bdb06c07910 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/profile/ChangePasswordForm.tsx b/public/app/features/profile/ChangePasswordForm.tsx index d88c9385795..05d84d04ec2 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 39a01bd0e2d..00000000000 --- 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 164c9fb98aa..d267cd0588b 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 00000000000..ab1c6dc7a3d --- /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 00000000000..4b196ef089f --- /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 656ca1ddcfa..dfe5812f5d2 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 7978aa410b0..9d30a2277f3 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 01b9f12aa3b..c57b577a0f9 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/types/user.ts b/public/app/types/user.ts index f1863f0387d..ddf8f6bce98 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 {