Skip to content

Commit

Permalink
Rewrite user profile edit to react (grafana#17917)
Browse files Browse the repository at this point in the history
* rewrite user profile edit to react (grafana#17525)

* disableLogin change, still need to fix tooltip

* left out disable form for other auth

* PR changes - wrapper to render, userId instead of bool, optional user in state, change provider child param order

* moved directive to angular_wrappers

* catch api error

* finally

* move user arg back to end- optional

* optional type sig
  • Loading branch information
Shavonn Brown committed Aug 19, 2019
1 parent 50058de commit e3e2cd8
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 89 deletions.
3 changes: 3 additions & 0 deletions public/app/core/angular_wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, []);
Expand Down Expand Up @@ -87,4 +88,6 @@ export function registerAngularDirectives() {
'suggestions',
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);

react2AngularDirective('reactProfileWrapper', ReactProfileWrapper, []);
}
52 changes: 48 additions & 4 deletions public/app/core/utils/UserProvider.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -15,36 +20,75 @@ 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;
}

export class UserProvider extends PureComponent<Props, State> {
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)}</>;
}
}

Expand Down
14 changes: 5 additions & 9 deletions public/app/features/profile/ChangePasswordForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,11 @@ export interface State {
}

export class ChangePasswordForm extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);

this.state = {
oldPassword: '',
newPassword: '',
confirmNew: '',
};
}
state: State = {
oldPassword: '',
newPassword: '',
confirmNew: '',
};

onOldPasswordChange = (oldPassword: string) => {
this.setState({ oldPassword });
Expand Down
4 changes: 0 additions & 4 deletions public/app/features/profile/PrefControlCtrl.ts

This file was deleted.

29 changes: 1 addition & 28 deletions public/app/features/profile/ProfileCtrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -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);
26 changes: 26 additions & 0 deletions public/app/features/profile/ReactProfileWrapper.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<UserProvider userId={config.bootData.user.id}>
{(api, states, user) => {
return (
<>
{!states.loadUser && (
<UserProfileEditForm
updateProfile={api.updateUserProfile}
isSavingUser={states.updateUserProfile}
user={user}
/>
)}
<SharedPreferences resourceUri="user" />
</>
);
}}
</UserProvider>
);

export default ReactProfileWrapper;
105 changes: 105 additions & 0 deletions public/app/features/profile/UserProfileEditForm.tsx
Original file line number Diff line number Diff line change
@@ -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<Props, State> {
constructor(props: Props) {
super(props);

const {
user: { name, email, login },
} = this.props;

this.state = {
name,
email,
login,
};
}

onNameChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ name: event.target.value });
};

onEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ email: event.target.value });
};

onLoginChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ login: event.target.value });
};

onSubmitProfileUpdate = (event: MouseEvent<HTMLInputElement>) => {
event.preventDefault();
this.props.updateProfile({ ...this.state });
};

render() {
const { name, email, login } = this.state;
const { isSavingUser } = this.props;
const { disableLoginForm } = config;

return (
<>
<h3 className="page-sub-heading">Edit Profile</h3>
<form name="userForm" className="gf-form-group">
<div className="gf-form max-width-30">
<FormLabel className="width-8">Name</FormLabel>
<Input className="gf-form-input max-width-22" type="text" onChange={this.onNameChange} value={name} />
</div>
<div className="gf-form max-width-30">
<FormLabel className="width-8">Email</FormLabel>
<Input
className="gf-form-input max-width-22"
type="text"
onChange={this.onEmailChange}
value={email}
disabled={disableLoginForm}
/>
{disableLoginForm && (
<Tooltip content="Login Details Locked - managed in another system.">
<i className="fa fa-lock gf-form-icon--right-absolute" />
</Tooltip>
)}
</div>
<div className="gf-form max-width-30">
<FormLabel className="width-8">Username</FormLabel>
<Input
className="gf-form-input max-width-22"
type="text"
onChange={this.onLoginChange}
value={login}
disabled={disableLoginForm}
/>
{disableLoginForm && (
<Tooltip content="Login Details Locked - managed in another system.">
<i className="fa fa-lock gf-form-icon--right-absolute" />
</Tooltip>
)}
</div>
<div className="gf-form-button-row">
<Button variant="primary" onClick={this.onSubmitProfileUpdate} disabled={isSavingUser}>
Save
</Button>
</div>
</form>
</>
);
}
}

export default UserProfileEditForm;
1 change: 0 additions & 1 deletion public/app/features/profile/all.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
import './ProfileCtrl';
import './PrefControlCtrl';
44 changes: 1 addition & 43 deletions public/app/features/profile/partials/profile.html
Original file line number Diff line number Diff line change
@@ -1,49 +1,7 @@
<page-header model="ctrl.navModel"></page-header>

<div class="page-container page-body">
<h3 class="page-sub-heading">User Profile</h3>

<form name="ctrl.userForm" class="gf-form-group">
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Name</span>
<input class="gf-form-input max-width-22" type="text" required ng-model="ctrl.user.name" />
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Email</span>
<input
class="gf-form-input max-width-22"
type="email"
ng-readonly="ctrl.readonlyLoginFields"
required
ng-model="ctrl.user.email"
/>
<i
ng-if="ctrl.readonlyLoginFields"
class="fa fa-lock gf-form-icon--right-absolute"
bs-tooltip="'Login Details Locked - managed in another system.'"
></i>
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Username</span>
<input
class="gf-form-input max-width-22"
type="text"
ng-readonly="ctrl.readonlyLoginFields"
required
ng-model="ctrl.user.login"
/>
<i
ng-if="ctrl.readonlyLoginFields"
class="fa fa-lock gf-form-icon--right-absolute"
bs-tooltip="'Login Details Locked - managed in another system.'"
></i>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-primary" ng-click="ctrl.update()">Save</button>
</div>
</form>

<prefs-control resource-uri="'user'"></prefs-control>
<react-profile-wrapper></react-profile-wrapper>

<h3 class="page-heading" ng-show="ctrl.showTeamsList">Teams</h3>
<div class="gf-form-group" ng-show="ctrl.showTeamsList">
Expand Down
2 changes: 2 additions & 0 deletions public/app/features/teams/TeamMembers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ describe('Functions', () => {
label: '',
avatarUrl: '',
login: '',
name: '',
email: '',
};

instance.onAddUserToTeam();
Expand Down
2 changes: 2 additions & 0 deletions public/app/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface User {
label: string;
avatarUrl: string;
login: string;
email: string;
name: string;
}

export interface Invitee {
Expand Down

0 comments on commit e3e2cd8

Please sign in to comment.