Files
redash/client/app/components/users/UserEdit.jsx
Gabriel Dutra b9644b7456 React version of UserEdit (#3354)
* Update DynamicForm export

* Move UserShow to users folder

* Migrate User profile header and create DynamicForm for basic data

* Update UserShow to use UserProfile prop

* Add API Key input

* Add handler to regenerate API Key button

* Handle user profile save

* Add readOnly prop to DynamicForm and begin disabled user behavior

* Add Change Password Modal

* Remove action buttons for disabled users

* Add send password reset behavior

* Add minLength and password comparison to Password Modal

* Resend Invitation button

* Add Convert User Info

* Fix UserShow test

* Some code updates

* Add enable/disable user button

* Add UserPolicy as an idea

* Remove UserPolicy

* Create Edit Profile spec

* Move User profile screenshot to Edit Profile Spec

* Add tests for saving user and changing password errors

* CC is back :) - Fix trailing spaces

* Add test for succesful password update

* A few improvements from code review

* Remove Toggle User button when seeing your own profile

* Create InputWithCopy

* Fix possible errors when network is off and improve Email not sent alert

* Add default response object for $http possible errors

* Changes in UserEdit
- removed onClick from methods name
- regenerate API Key now uses InputWithCopy
- Password title added

* Update UserEdit render behavior and styling
- Password title changed to h5
- change rendering rules for actions
- Password modal is now closed when password is changed
- change DynamicForm readOnly to the fields and add hideSubmitButton

* Create ChangePasswordDialog and update UserEdit

* Fix possible console error

* Remove password match assertion from spec

* Fix typo
2019-02-14 14:08:30 -02:00

259 lines
6.7 KiB
JavaScript

import React, { Fragment } from 'react';
import Alert from 'antd/lib/alert';
import Button from 'antd/lib/button';
import Form from 'antd/lib/form';
import Modal from 'antd/lib/modal';
import { react2angular } from 'react2angular';
import { User } from '@/services/user';
import { currentUser } from '@/services/auth';
import { absoluteUrl } from '@/services/utils';
import { UserProfile } from '../proptypes';
import { DynamicForm } from '../dynamic-form/DynamicForm';
import ChangePasswordDialog from './ChangePasswordDialog';
import InputWithCopy from '../InputWithCopy';
export class UserEdit extends React.Component {
static propTypes = {
user: UserProfile.isRequired,
};
constructor(props) {
super(props);
this.state = {
user: this.props.user,
regeneratingApiKey: false,
sendingPasswordEmail: false,
resendingInvitation: false,
togglingUser: false,
};
}
changePassword = () => {
ChangePasswordDialog.showModal({ user: this.props.user });
};
sendPasswordReset = () => {
this.setState({ sendingPasswordEmail: true });
User.sendPasswordReset(this.state.user).then((passwordResetLink) => {
this.setState({ passwordResetLink });
}).finally(() => {
this.setState({ sendingPasswordEmail: false });
});
};
resendInvitation = () => {
this.setState({ resendingInvitation: true });
User.resendInvitation(this.state.user).finally(() => {
this.setState({ resendingInvitation: false });
});
};
regenerateApiKey = () => {
const doRegenerate = () => {
this.setState({ regeneratingApiKey: true });
User.regenerateApiKey(this.state.user).then((apiKey) => {
if (apiKey) {
const { user } = this.state;
this.setState({ user: { ...user, apiKey } });
}
}).finally(() => {
this.setState({ regeneratingApiKey: false });
});
};
Modal.confirm({
title: 'Regenerate API Key',
content: 'Are you sure you want to regenerate?',
okText: 'Regenerate',
onOk: doRegenerate,
maskClosable: true,
autoFocusButton: null,
});
};
toggleUser = () => {
const { user } = this.state;
const toggleUser = user.isDisabled ? User.enableUser : User.disableUser;
this.setState({ togglingUser: true });
toggleUser(user).then((data) => {
if (data) {
this.setState({ user: User.convertUserInfo(data.data) });
}
}).finally(() => {
this.setState({ togglingUser: false });
});
};
saveUser = (values, successCallback, errorCallback) => {
const data = {
id: this.props.user.id,
...values,
};
User.save(data, (user) => {
successCallback('Saved.');
this.setState({ user: User.convertUserInfo(user) });
}, (error = {}) => {
errorCallback(error.data && error.data.message || 'Failed saving.');
});
};
renderBasicInfoForm() {
const { user } = this.state;
const formFields = [
{
name: 'name',
title: 'Name',
type: 'text',
initialValue: user.name,
},
{
name: 'email',
title: 'Email',
type: 'email',
initialValue: user.email,
},
].map(field => ({ ...field, readOnly: user.isDisabled, required: true }));
return (
<DynamicForm
fields={formFields}
onSubmit={this.saveUser}
hideSubmitButton={user.isDisabled}
/>
);
}
renderApiKey() {
const { user, regeneratingApiKey } = this.state;
return (
<Form layout="vertical">
<hr />
<Form.Item label="API Key" className="m-b-10">
<InputWithCopy id="apiKey" value={user.apiKey} data-test="ApiKey" readOnly />
</Form.Item>
<Button
className="w-100"
onClick={this.regenerateApiKey}
loading={regeneratingApiKey}
data-test="RegenerateApiKey"
>
Regenerate
</Button>
</Form>
);
}
renderPasswordLinkAlert() {
const { user, passwordResetLink } = this.state;
return (
<Alert
message="Email not sent!"
description={(
<Fragment>
<p>
The mail server is not configured, please send the following link
to <b>{user.name}</b> to reset their password:
</p>
<InputWithCopy value={absoluteUrl(passwordResetLink)} readOnly />
</Fragment>
)}
type="warning"
className="m-t-20"
afterClose={() => { this.setState({ passwordResetLink: null }); }}
closable
/>
);
}
renderResendInvitation() {
return (
<Button
className="w-100 m-t-10"
onClick={this.resendInvitation}
loading={this.state.resendingInvitation}
>
Resend Invitation
</Button>
);
}
renderSendPasswordReset() {
const { sendingPasswordEmail, passwordResetLink } = this.state;
return (
<Fragment>
<Button
className="w-100 m-t-10"
onClick={this.sendPasswordReset}
loading={sendingPasswordEmail}
>
Send Password Reset Email
</Button>
{passwordResetLink && this.renderPasswordLinkAlert()}
</Fragment>
);
}
rendertoggleUser() {
const { user, togglingUser } = this.state;
return user.isDisabled ? (
<Button className="w-100 m-t-10" type="primary" onClick={this.toggleUser} loading={togglingUser}>
Enable User
</Button>
) : (
<Button className="w-100 m-t-10" type="danger" onClick={this.toggleUser} loading={togglingUser}>
Disable User
</Button>
);
}
render() {
const { user } = this.state;
return (
<div className="col-md-4 col-md-offset-4">
<img
alt="Profile"
src={user.profileImageUrl}
className="profile__image"
width="40"
/>
<h3 className="profile__h3">{user.name}</h3>
<hr />
{this.renderBasicInfoForm()}
{!user.isDisabled && (
<Fragment>
{this.renderApiKey()}
<hr />
<h5>Password</h5>
{user.id === currentUser.id && (
<Button className="w-100 m-t-10" onClick={this.changePassword} data-test="ChangePassword">
Change Password
</Button>
)}
{(currentUser.isAdmin && user.id !== currentUser.id) && (
user.isInvitationPending ?
this.renderResendInvitation() : this.renderSendPasswordReset()
)}
</Fragment>
)}
<hr />
{currentUser.isAdmin && user.id !== currentUser.id && this.rendertoggleUser()}
</div>
);
}
}
export default function init(ngModule) {
ngModule.component('userEdit', react2angular(UserEdit));
}
init.init = true;