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",
|
"mocha": "7.0.1",
|
||||||
"module-alias": "2.2.0",
|
"module-alias": "2.2.0",
|
||||||
"monaco-editor": "0.15.6",
|
"monaco-editor": "0.15.6",
|
||||||
|
"mutationobserver-shim": "0.3.3",
|
||||||
"ngtemplate-loader": "2.0.1",
|
"ngtemplate-loader": "2.0.1",
|
||||||
"node-sass": "4.13.1",
|
"node-sass": "4.13.1",
|
||||||
"npm": "6.13.4",
|
"npm": "6.13.4",
|
||||||
|
@ -49,6 +49,8 @@ export class GrafanaBootConfig {
|
|||||||
exploreEnabled = false;
|
exploreEnabled = false;
|
||||||
ldapEnabled = false;
|
ldapEnabled = false;
|
||||||
samlEnabled = false;
|
samlEnabled = false;
|
||||||
|
autoAssignOrg = true;
|
||||||
|
verifyEmailEnabled = false;
|
||||||
oauth: any;
|
oauth: any;
|
||||||
disableUserSignUp = false;
|
disableUserSignUp = false;
|
||||||
loginHint: any;
|
loginHint: any;
|
||||||
|
@ -179,6 +179,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
|
|||||||
"alertingErrorOrTimeout": setting.AlertingErrorOrTimeout,
|
"alertingErrorOrTimeout": setting.AlertingErrorOrTimeout,
|
||||||
"alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues,
|
"alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues,
|
||||||
"alertingMinInterval": setting.AlertingMinInterval,
|
"alertingMinInterval": setting.AlertingMinInterval,
|
||||||
|
"autoAssignOrg": setting.AutoAssignOrg,
|
||||||
|
"verfiyEmailEnabled": setting.VerifyEmailEnabled,
|
||||||
"exploreEnabled": setting.ExploreEnabled,
|
"exploreEnabled": setting.ExploreEnabled,
|
||||||
"googleAnalyticsId": setting.GoogleAnalyticsId,
|
"googleAnalyticsId": setting.GoogleAnalyticsId,
|
||||||
"disableLoginForm": setting.DisableLoginForm,
|
"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 DashboardImportCtrl from 'app/features/manage-dashboards/DashboardImportCtrl';
|
||||||
import LdapPage from 'app/features/admin/ldap/LdapPage';
|
import LdapPage from 'app/features/admin/ldap/LdapPage';
|
||||||
import UserAdminPage from 'app/features/admin/UserAdminPage';
|
import UserAdminPage from 'app/features/admin/UserAdminPage';
|
||||||
|
import SignupPage from 'app/features/profile/SignupPage';
|
||||||
|
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { ILocationProvider, route } from 'angular';
|
import { ILocationProvider, route } from 'angular';
|
||||||
// Types
|
// Types
|
||||||
@ -351,8 +353,10 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
|||||||
pageClass: 'sidemenu-hidden',
|
pageClass: 'sidemenu-hidden',
|
||||||
})
|
})
|
||||||
.when('/signup', {
|
.when('/signup', {
|
||||||
templateUrl: 'public/app/partials/signup_step2.html',
|
template: '<react-container/>',
|
||||||
controller: 'SignUpCtrl',
|
resolve: {
|
||||||
|
component: () => SignupPage,
|
||||||
|
},
|
||||||
pageClass: 'sidemenu-hidden',
|
pageClass: 'sidemenu-hidden',
|
||||||
})
|
})
|
||||||
.when('/user/password/send-reset-email', {
|
.when('/user/password/send-reset-email', {
|
||||||
|
@ -2,6 +2,7 @@ import { configure } from 'enzyme';
|
|||||||
import Adapter from 'enzyme-adapter-react-16';
|
import Adapter from 'enzyme-adapter-react-16';
|
||||||
import 'jquery';
|
import 'jquery';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import 'mutationobserver-shim';
|
||||||
|
|
||||||
const global = window as any;
|
const global = window as any;
|
||||||
global.$ = global.jQuery = $;
|
global.$ = global.jQuery = $;
|
||||||
|
@ -16824,6 +16824,11 @@ multimatch@^4.0.0:
|
|||||||
arrify "^2.0.1"
|
arrify "^2.0.1"
|
||||||
minimatch "^3.0.4"
|
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:
|
mute-stream@0.0.5:
|
||||||
version "0.0.5"
|
version "0.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
|
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
|
||||||
|
Loading…
Reference in New Issue
Block a user