mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Migrations: Signup page (#21514)
* Start Angular migration * Add SignupCtrl * Change name signup * Add backend call * Put form in separate file * Add form model * Start using react-hook-forms * Add FormModel to state * Reduxify * Connect nav with Redux * Fix routing and navModel * Fetch state options on mount * Add default values and add button margin * Add errror messages * Fix title * Remove files and cleanup * Add Signup tests * Add boot config assingnAutoOrg and verifyEmailEnabled * Remove onmount call * Remove ctrl and move everything to SignupForm * Make routeParams optional for testing * Remove name if it is empty * Set username * Make function component * Fix subpath issues and add link button * Move redux to SignupPage
This commit is contained in:
parent
029a6c64b4
commit
0c4dae321c
@ -115,6 +115,7 @@
|
||||
"mocha": "7.0.1",
|
||||
"module-alias": "2.2.0",
|
||||
"monaco-editor": "0.15.6",
|
||||
"mutationobserver-shim": "0.3.3",
|
||||
"ngtemplate-loader": "2.0.1",
|
||||
"node-sass": "4.13.1",
|
||||
"npm": "6.13.4",
|
||||
|
@ -49,6 +49,8 @@ export class GrafanaBootConfig {
|
||||
exploreEnabled = false;
|
||||
ldapEnabled = false;
|
||||
samlEnabled = false;
|
||||
autoAssignOrg = true;
|
||||
verifyEmailEnabled = false;
|
||||
oauth: any;
|
||||
disableUserSignUp = false;
|
||||
loginHint: any;
|
||||
|
@ -179,6 +179,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
|
||||
"alertingErrorOrTimeout": setting.AlertingErrorOrTimeout,
|
||||
"alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues,
|
||||
"alertingMinInterval": setting.AlertingMinInterval,
|
||||
"autoAssignOrg": setting.AutoAssignOrg,
|
||||
"verfiyEmailEnabled": setting.VerifyEmailEnabled,
|
||||
"exploreEnabled": setting.ExploreEnabled,
|
||||
"googleAnalyticsId": setting.GoogleAnalyticsId,
|
||||
"disableLoginForm": setting.DisableLoginForm,
|
||||
|
18
public/app/features/profile/SignupForm.test.tsx
Normal file
18
public/app/features/profile/SignupForm.test.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { SignupForm } from './SignupForm';
|
||||
|
||||
describe('SignupForm', () => {
|
||||
describe('With different values for verifyEmail and autoAssignOrg', () => {
|
||||
it('should render input fields', () => {
|
||||
const wrapper = shallow(<SignupForm verifyEmailEnabled={true} autoAssignOrg={false} />);
|
||||
expect(wrapper.exists('Forms.Input[name="orgName"]'));
|
||||
expect(wrapper.exists('Forms.Input[name="code"]'));
|
||||
});
|
||||
it('should not render input fields', () => {
|
||||
const wrapper = shallow(<SignupForm verifyEmailEnabled={false} autoAssignOrg={true} />);
|
||||
expect(wrapper.exists('Forms.Input[name="orgName"]')).toBeFalsy();
|
||||
expect(wrapper.exists('Forms.Input[name="code"]')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
120
public/app/features/profile/SignupForm.tsx
Normal file
120
public/app/features/profile/SignupForm.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import React, { FC } from 'react';
|
||||
import { Forms } from '@grafana/ui';
|
||||
import { css } from 'emotion';
|
||||
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
interface SignupFormModel {
|
||||
email: string;
|
||||
username?: string;
|
||||
password: string;
|
||||
orgName: string;
|
||||
code?: string;
|
||||
name?: string;
|
||||
}
|
||||
interface Props {
|
||||
email?: string;
|
||||
orgName?: string;
|
||||
username?: string;
|
||||
code?: string;
|
||||
name?: string;
|
||||
verifyEmailEnabled?: boolean;
|
||||
autoAssignOrg?: boolean;
|
||||
}
|
||||
|
||||
const buttonSpacing = css`
|
||||
margin-left: 15px;
|
||||
`;
|
||||
|
||||
export const SignupForm: FC<Props> = props => {
|
||||
const verifyEmailEnabled = props.verifyEmailEnabled;
|
||||
const autoAssignOrg = props.autoAssignOrg;
|
||||
|
||||
const onSubmit = async (formData: SignupFormModel) => {
|
||||
if (formData.name === '') {
|
||||
delete formData.name;
|
||||
}
|
||||
|
||||
const response = await getBackendSrv().post('/api/user/signup/step2', {
|
||||
email: formData.email,
|
||||
code: formData.code,
|
||||
username: formData.email,
|
||||
orgName: formData.orgName,
|
||||
password: formData.password,
|
||||
name: formData.name,
|
||||
});
|
||||
|
||||
if (response.code === 'redirect-to-select-org') {
|
||||
window.location.href = getConfig().appSubUrl + '/profile/select-org?signup=1';
|
||||
}
|
||||
window.location.href = getConfig().appSubUrl + '/';
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
orgName: props.orgName,
|
||||
email: props.email,
|
||||
username: props.email,
|
||||
code: props.code,
|
||||
name: props.name,
|
||||
};
|
||||
|
||||
return (
|
||||
<Forms.Form defaultValues={defaultValues} onSubmit={onSubmit}>
|
||||
{({ register, errors }) => {
|
||||
return (
|
||||
<>
|
||||
{verifyEmailEnabled && (
|
||||
<Forms.Field label="Email verification code (sent to your email)">
|
||||
<Forms.Input name="code" size="md" ref={register} placeholder="Code" />
|
||||
</Forms.Field>
|
||||
)}
|
||||
{!autoAssignOrg && (
|
||||
<Forms.Field label="Org. name">
|
||||
<Forms.Input size="md" name="orgName" placeholder="Org. name" ref={register} />
|
||||
</Forms.Field>
|
||||
)}
|
||||
<Forms.Field label="Your name">
|
||||
<Forms.Input size="md" name="name" placeholder="(optional)" ref={register} />
|
||||
</Forms.Field>
|
||||
<Forms.Field label="Email" invalid={!!errors.email} error={!!errors.email && errors.email.message}>
|
||||
<Forms.Input
|
||||
size="md"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
ref={register({
|
||||
required: 'Email is required',
|
||||
pattern: {
|
||||
value: /^\S+@\S+$/,
|
||||
message: 'Email is invalid',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</Forms.Field>
|
||||
<Forms.Field
|
||||
label="Password"
|
||||
invalid={!!errors.password}
|
||||
error={!!errors.password && errors.password.message}
|
||||
>
|
||||
<Forms.Input
|
||||
size="md"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
ref={register({ required: 'Password is required' })}
|
||||
/>
|
||||
</Forms.Field>
|
||||
|
||||
<Forms.Button type="submit">Submit</Forms.Button>
|
||||
<span className={buttonSpacing}>
|
||||
<Forms.LinkButton href={getConfig().appSubUrl + '/login'} variant="secondary">
|
||||
Back
|
||||
</Forms.LinkButton>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Forms.Form>
|
||||
);
|
||||
};
|
51
public/app/features/profile/SignupPage.tsx
Normal file
51
public/app/features/profile/SignupPage.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { FC } from 'react';
|
||||
import { SignupForm } from './SignupForm';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { connect } from 'react-redux';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { StoreState } from 'app/types';
|
||||
|
||||
const navModel = {
|
||||
main: {
|
||||
icon: 'gicon gicon-branding',
|
||||
text: 'Sign Up',
|
||||
subTitle: 'Register your Grafana account',
|
||||
breadcrumbs: [{ title: 'Login', url: 'login' }],
|
||||
},
|
||||
node: {
|
||||
text: '',
|
||||
},
|
||||
};
|
||||
|
||||
interface Props {
|
||||
email?: string;
|
||||
orgName?: string;
|
||||
username?: string;
|
||||
code?: string;
|
||||
name?: string;
|
||||
}
|
||||
export const SignupPage: FC<Props> = props => {
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<Page.Contents>
|
||||
<h3 className="p-b-1">You're almost there.</h3>
|
||||
<div className="p-b-1">
|
||||
We just need a couple of more bits of
|
||||
<br /> information to finish creating your account.
|
||||
</div>
|
||||
<SignupForm
|
||||
{...props}
|
||||
verifyEmailEnabled={getConfig().verifyEmailEnabled}
|
||||
autoAssignOrg={getConfig().autoAssignOrg}
|
||||
/>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
...state.location.routeParams,
|
||||
});
|
||||
|
||||
export default hot(module)(connect(mapStateToProps)(SignupPage));
|
@ -1,47 +0,0 @@
|
||||
<page-header model="navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<div class="signup">
|
||||
<h3 class="p-b-1">You're almost there.</h3>
|
||||
<div class="p-b-1">
|
||||
We just need a couple of more bits of<br> information to finish creating your account.
|
||||
</div>
|
||||
<form name="signUpForm" class="login-form gf-form-group">
|
||||
<div class="gf-form" ng-if="verifyEmailEnabled">
|
||||
<span class="gf-form-label width-9">
|
||||
Email code<tip>Email verification code (sent to your email)</tip>
|
||||
</span>
|
||||
<input type="text" class="gf-form-input max-width-14" ng-model="formModel.code" required></input>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-if="!autoAssignOrg">
|
||||
<span class="gf-form-label width-9">Org. name</span>
|
||||
<input type="text" name="orgName" class="gf-form-input max-width-14" ng-model="formModel.orgName" placeholder="Name your organization">
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-9">Your name</span>
|
||||
<input type="text" name="name" class="gf-form-input max-width-14" ng-model="formModel.name" placeholder="(optional)">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-9">Email</span>
|
||||
<input type="text" class="gf-form-input max-width-14" required ng-model="formModel.username" placeholder="Email" autocomplete="off">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-9">Password</span>
|
||||
<input type="password" class="gf-form-input max-width-14" required ng-model="formModel.password" id="inputPassword" placeholder="password" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row p-t-3">
|
||||
<button type="submit" class="btn btn-primary" ng-click="ctrl.submit();" ng-disabled="!signUpForm.$valid">
|
||||
Sign Up
|
||||
</button>
|
||||
<a href="login" class="btn btn-inverse">
|
||||
Back
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer />
|
@ -7,6 +7,8 @@ import FolderDashboardsCtrl from 'app/features/folders/FolderDashboardsCtrl';
|
||||
import DashboardImportCtrl from 'app/features/manage-dashboards/DashboardImportCtrl';
|
||||
import LdapPage from 'app/features/admin/ldap/LdapPage';
|
||||
import UserAdminPage from 'app/features/admin/UserAdminPage';
|
||||
import SignupPage from 'app/features/profile/SignupPage';
|
||||
|
||||
import config from 'app/core/config';
|
||||
import { ILocationProvider, route } from 'angular';
|
||||
// Types
|
||||
@ -351,8 +353,10 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
pageClass: 'sidemenu-hidden',
|
||||
})
|
||||
.when('/signup', {
|
||||
templateUrl: 'public/app/partials/signup_step2.html',
|
||||
controller: 'SignUpCtrl',
|
||||
template: '<react-container/>',
|
||||
resolve: {
|
||||
component: () => SignupPage,
|
||||
},
|
||||
pageClass: 'sidemenu-hidden',
|
||||
})
|
||||
.when('/user/password/send-reset-email', {
|
||||
|
@ -2,6 +2,7 @@ import { configure } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
import 'jquery';
|
||||
import $ from 'jquery';
|
||||
import 'mutationobserver-shim';
|
||||
|
||||
const global = window as any;
|
||||
global.$ = global.jQuery = $;
|
||||
|
@ -16824,6 +16824,11 @@ multimatch@^4.0.0:
|
||||
arrify "^2.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
mutationobserver-shim@0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz#65869630bc89d7bf8c9cd9cb82188cd955aacd2b"
|
||||
integrity sha512-gciOLNN8Vsf7YzcqRjKzlAJ6y7e+B86u7i3KXes0xfxx/nfLmozlW1Vn+Sc9x3tPIePFgc1AeIFhtRgkqTjzDQ==
|
||||
|
||||
mute-stream@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
|
||||
|
Loading…
Reference in New Issue
Block a user