feat(invite): progress on invite feature, #2353

This commit is contained in:
Torkel Ödegaard 2015-08-10 13:46:59 +02:00
parent 93b8287d23
commit 775e044e69
13 changed files with 323 additions and 293 deletions

View File

@ -22,7 +22,7 @@ func Register(r *macaron.Macaron) {
r.Post("/login", bind(dtos.LoginCommand{}), wrap(LoginPost))
r.Get("/login/:name", OAuthLogin)
r.Get("/login", LoginView)
r.Get("/invite", Index)
r.Get("/invite/:code", Index)
// authed views
r.Get("/profile/", reqSignedIn, Index)

View File

@ -10,9 +10,10 @@ type AddInviteForm struct {
}
type InviteInfo struct {
Email string `json:"email"`
Name string `json:"name"`
Username string `json:"username"`
Email string `json:"email"`
Name string `json:"name"`
Username string `json:"username"`
InvitedBy string `json:"invitedBy"`
}
type CompleteInviteForm struct {

View File

@ -78,7 +78,7 @@ func AddOrgInvite(c *middleware.Context, inviteDto dtos.AddInviteForm) Response
"OrgName": c.OrgName,
"Email": c.Email,
"LinkUrl": setting.ToAbsUrl("invite/" + cmd.Code),
"InvitedBy": util.StringsFallback2(c.Name, c.Email),
"InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login),
},
}
@ -114,13 +114,14 @@ func GetInviteInfoByCode(c *middleware.Context) Response {
return ApiError(500, "Failed to get invite", err)
}
info := dtos.InviteInfo{
Email: query.Result.Email,
Name: query.Result.Name,
Username: query.Result.Email,
}
invite := query.Result
return Json(200, &info)
return Json(200, dtos.InviteInfo{
Email: invite.Email,
Name: invite.Name,
Username: invite.Email,
InvitedBy: util.StringsFallback3(invite.InvitedByName, invite.InvitedByLogin, invite.InvitedByEmail),
})
}
func CompleteInvite(c *middleware.Context, completeInvite dtos.CompleteInviteForm) Response {

View File

@ -70,18 +70,21 @@ type GetTempUsersForOrgQuery struct {
type GetTempUserByCodeQuery struct {
Code string
Result *TempUser
Result *TempUserDTO
}
type TempUserDTO struct {
Id int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Role string `json:"role"`
InvitedBy string `json:"invitedBy"`
Code string `json:"code"`
Url string `json:"url"`
EmailSent bool `json:"emailSent"`
EmailSentOn time.Time `json:"emailSentOn"`
Created time.Time `json:"createdOn"`
Id int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Role string `json:"role"`
InvitedByLogin string `json:"invitedByLogin"`
InvitedByEmail string `json:"invitedByEmail"`
InvitedByName string `json:"invitedByName"`
Code string `json:"code"`
Status TempUserStatus `json:"status"`
Url string `json:"url"`
EmailSent bool `json:"emailSent"`
EmailSentOn time.Time `json:"emailSentOn"`
Created time.Time `json:"createdOn"`
}

View File

@ -56,10 +56,13 @@ func GetTempUsersForOrg(query *m.GetTempUsersForOrgQuery) error {
tu.name as name,
tu.role as role,
tu.code as code,
tu.status as status,
tu.email_sent as email_sent,
tu.email_sent_on as email_sent_on,
tu.created as created,
u.login as invited_by
u.login as invited_by_login,
u.name as invited_by_name,
u.email as invited_by_email
FROM ` + dialect.Quote("temp_user") + ` as tu
LEFT OUTER JOIN ` + dialect.Quote("user") + ` as u on u.id = tu.invited_by_user_id
WHERE tu.org_id=? AND tu.status =? ORDER BY tu.created desc`
@ -71,8 +74,26 @@ func GetTempUsersForOrg(query *m.GetTempUsersForOrgQuery) error {
}
func GetTempUserByCode(query *m.GetTempUserByCodeQuery) error {
var user m.TempUser
has, err := x.Table("temp_user").Where("code=?", query.Code).Get(&user)
var rawSql = `SELECT
tu.id as id,
tu.email as email,
tu.name as name,
tu.role as role,
tu.code as code,
tu.status as status,
tu.email_sent as email_sent,
tu.email_sent_on as email_sent_on,
tu.created as created,
u.login as invited_by_login,
u.name as invited_by_name,
u.email as invited_by_email
FROM ` + dialect.Quote("temp_user") + ` as tu
LEFT OUTER JOIN ` + dialect.Quote("user") + ` as u on u.id = tu.invited_by_user_id
WHERE tu.code=?`
var tempUser m.TempUserDTO
sess := x.Sql(rawSql, query.Code)
has, err := sess.Get(&tempUser)
if err != nil {
return err
@ -80,6 +101,6 @@ func GetTempUserByCode(query *m.GetTempUserByCodeQuery) error {
return m.ErrTempUserNotFound
}
query.Result = &user
query.Result = &tempUser
return err
}

View File

@ -32,6 +32,14 @@ func TestTempUserCommandsAndQueries(t *testing.T) {
So(len(query.Result), ShouldEqual, 1)
})
Convey("Should be able to get temp users by code", func() {
query := m.GetTempUserByCodeQuery{Code: "asd"}
err = GetTempUserByCode(&query)
So(err, ShouldBeNil)
So(query.Result.Name, ShouldEqual, "hello")
})
Convey("Should be able update status", func() {
cmd2 := m.UpdateTempUserStatusCommand{Code: "asd", Status: m.TmpUserRevoked}
err := UpdateTempUserStatus(&cmd2)

View File

@ -6,3 +6,13 @@ func StringsFallback2(val1 string, val2 string) string {
}
return val2
}
func StringsFallback3(val1 string, val2 string, val3 string) string {
if val1 != "" {
return val1
}
if val2 != "" {
return val2
}
return val3
}

View File

@ -20,7 +20,8 @@ function (angular, config) {
$scope.formModel.username = invite.email;
$scope.formModel.inviteCode = $routeParams.code;
$scope.greeting = invite.name || invite.email;
$scope.greeting = invite.name || invite.email || invite.username;
$scope.invitedBy = invite.invitedBy;
});
};

View File

@ -14,8 +14,8 @@
Hello {{greeting}}.
</h3>
<div class="long-tag modal-tagline">
<span class="body-copy-emphasis">{{.InvitedBy}}</span> has invited you to join the <span class="highlight-word">{{contextSrv.user.orgName}}</span> organization in Grafana.</br>Please complete the following to accept your invitation and continue:
<div class="modal-tagline">
<em>{{invitedBy}}</em> has invited you to join the <span class="highlight-word">{{contextSrv.user.orgName}}</span> organization in Grafana.</br>Please complete the following to accept your invitation and continue:
</div>
<form name="inviteForm" class="login-form">
@ -65,17 +65,6 @@
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 128px">
Confirm Password
</li>
<li>
<input type="password" name="confirmPassword" class="tight-form-input last" required ng-model="formModel.confirmPassword" id="confirmPassword" style="width: 253px" placeholder="confirm password">
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
<div style="margin-left: 147px; width: 254px;">

View File

@ -1,3 +1,4 @@
@import "type.less";
@import "login.less";
@import "submenu.less";
@import "graph.less";
@ -277,11 +278,6 @@
}
}
.long-tag {
width: 90%;
margin: 0 auto;
}
.confirm-modal {
max-width: 500px;
@ -371,7 +367,7 @@
}
.body-copy-emphasis {
color: @headingsColor;
color: @headingsColor;
}
.signup-page-container {

247
public/css/less/type.less Normal file
View File

@ -0,0 +1,247 @@
//
// Typography
// --------------------------------------------------
// Body text
// -------------------------
p {
margin: 0 0 @baseLineHeight / 2;
}
.lead {
margin-bottom: @baseLineHeight;
font-size: @baseFontSize * 1.5;
font-weight: 200;
line-height: @baseLineHeight * 1.5;
}
// Emphasis & misc
// -------------------------
// Ex: 14px base font * 85% = about 12px
small { font-size: 85%; }
strong { font-weight: 500; }
em { font-style: italic; color: @headingsColor; }
cite { font-style: normal; }
// Utility classes
.muted { color: @grayLight; }
a.muted:hover,
a.muted:focus { color: darken(@grayLight, 10%); }
.text-warning { color: @warningText; }
a.text-warning:hover,
a.text-warning:focus { color: darken(@warningText, 10%); }
.text-error { color: @errorText; }
a.text-error:hover,
a.text-error:focus { color: darken(@errorText, 10%); }
.text-info { color: @infoText; }
a.text-info:hover,
a.text-info:focus { color: darken(@infoText, 10%); }
.text-success { color: @successText; }
a.text-success:hover,
a.text-success:focus { color: darken(@successText, 10%); }
.text-left { text-align: left; }
.text-right { text-align: right; }
.text-center { text-align: center; }
// Headings
// -------------------------
h1, h2, h3, h4, h5, h6 {
margin: (@baseLineHeight / 2) 0;
font-family: @headingsFontFamily;
font-weight: @headingsFontWeight;
line-height: @baseLineHeight;
color: @headingsColor;
text-rendering: optimizelegibility; // Fix the character spacing for headings
small {
font-weight: normal;
line-height: 1;
color: @grayLight;
}
}
h1,
h2,
h3 { line-height: @baseLineHeight * 2; }
h1 { font-size: @baseFontSize * 2.00; } // ~38px
h2 { font-size: @baseFontSize * 1.75; } // ~32px
h3 { font-size: @baseFontSize * 1.50; } // ~24px
h4 { font-size: @baseFontSize * 1.25; } // ~18px
h5 { font-size: @baseFontSize; }
h6 { font-size: @baseFontSize * 0.85; } // ~12px
h1 small { font-size: @baseFontSize * 1.75; } // ~24px
h2 small { font-size: @baseFontSize * 1.25; } // ~18px
h3 small { font-size: @baseFontSize; }
h4 small { font-size: @baseFontSize; }
// Page header
// -------------------------
.page-header {
padding-bottom: (@baseLineHeight / 2) - 1;
margin: @baseLineHeight 0 (@baseLineHeight * 1.5);
border-bottom: 1px solid @grayLighter;
}
// Lists
// --------------------------------------------------
// Unordered and Ordered lists
ul, ol {
padding: 0;
margin: 0 0 @baseLineHeight / 2 25px;
}
ul ul,
ul ol,
ol ol,
ol ul {
margin-bottom: 0;
}
li {
line-height: @baseLineHeight;
}
// Remove default list styles
ul.unstyled,
ol.unstyled {
margin-left: 0;
list-style: none;
}
// Single-line list items
ul.inline,
ol.inline {
margin-left: 0;
list-style: none;
> li {
display: inline-block;
.ie7-inline-block();
padding-left: 5px;
padding-right: 5px;
}
}
// Description Lists
dl {
margin-bottom: @baseLineHeight;
}
dt,
dd {
line-height: @baseLineHeight;
}
dt {
font-weight: bold;
}
dd {
margin-left: @baseLineHeight / 2;
}
// Horizontal layout (like forms)
.dl-horizontal {
.clearfix(); // Ensure dl clears floats if empty dd elements present
dt {
float: left;
width: @horizontalComponentOffset - 20;
clear: left;
text-align: right;
.text-overflow();
}
dd {
margin-left: @horizontalComponentOffset;
}
}
// MISC
// ----
// Horizontal rules
hr {
margin: @baseLineHeight 0;
border: 0;
border-top: 1px solid @hrBorder;
border-bottom: 1px solid @white;
}
// Abbreviations and acronyms
abbr[title],
// Added data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257
abbr[data-original-title] {
cursor: help;
border-bottom: 1px dotted @grayLight;
}
abbr.initialism {
font-size: 90%;
text-transform: uppercase;
}
// Blockquotes
blockquote {
padding: 0 0 0 15px;
margin: 0 0 @baseLineHeight;
border-left: 5px solid @grayLighter;
p {
margin-bottom: 0;
font-size: @baseFontSize * 1.25;
font-weight: 300;
line-height: 1.25;
}
small {
display: block;
line-height: @baseLineHeight;
color: @grayLight;
&:before {
content: '\2014 \00A0';
}
}
// Float right with text-align: right
&.pull-right {
float: right;
padding-right: 15px;
padding-left: 0;
border-right: 5px solid @grayLighter;
border-left: 0;
p,
small {
text-align: right;
}
small {
&:before {
content: '';
}
&:after {
content: '\00A0 \2014';
}
}
}
}
// Quotes
q:before,
q:after,
blockquote:before,
blockquote:after {
content: "";
}
// Addresses
address {
display: block;
margin-bottom: @baseLineHeight;
font-style: normal;
line-height: @baseLineHeight;
}

View File

@ -22,7 +22,6 @@
// Base CSS
@import "type.less";
@import "code.less";
@import "forms.less";
@import "tables.less";

View File

@ -1,247 +1 @@
//
// Typography
// --------------------------------------------------
// Body text
// -------------------------
p {
margin: 0 0 @baseLineHeight / 2;
}
.lead {
margin-bottom: @baseLineHeight;
font-size: @baseFontSize * 1.5;
font-weight: 200;
line-height: @baseLineHeight * 1.5;
}
// Emphasis & misc
// -------------------------
// Ex: 14px base font * 85% = about 12px
small { font-size: 85%; }
strong { font-weight: bold; }
em { font-style: italic; }
cite { font-style: normal; }
// Utility classes
.muted { color: @grayLight; }
a.muted:hover,
a.muted:focus { color: darken(@grayLight, 10%); }
.text-warning { color: @warningText; }
a.text-warning:hover,
a.text-warning:focus { color: darken(@warningText, 10%); }
.text-error { color: @errorText; }
a.text-error:hover,
a.text-error:focus { color: darken(@errorText, 10%); }
.text-info { color: @infoText; }
a.text-info:hover,
a.text-info:focus { color: darken(@infoText, 10%); }
.text-success { color: @successText; }
a.text-success:hover,
a.text-success:focus { color: darken(@successText, 10%); }
.text-left { text-align: left; }
.text-right { text-align: right; }
.text-center { text-align: center; }
// Headings
// -------------------------
h1, h2, h3, h4, h5, h6 {
margin: (@baseLineHeight / 2) 0;
font-family: @headingsFontFamily;
font-weight: @headingsFontWeight;
line-height: @baseLineHeight;
color: @headingsColor;
text-rendering: optimizelegibility; // Fix the character spacing for headings
small {
font-weight: normal;
line-height: 1;
color: @grayLight;
}
}
h1,
h2,
h3 { line-height: @baseLineHeight * 2; }
h1 { font-size: @baseFontSize * 2.00; } // ~38px
h2 { font-size: @baseFontSize * 1.75; } // ~32px
h3 { font-size: @baseFontSize * 1.50; } // ~24px
h4 { font-size: @baseFontSize * 1.25; } // ~18px
h5 { font-size: @baseFontSize; }
h6 { font-size: @baseFontSize * 0.85; } // ~12px
h1 small { font-size: @baseFontSize * 1.75; } // ~24px
h2 small { font-size: @baseFontSize * 1.25; } // ~18px
h3 small { font-size: @baseFontSize; }
h4 small { font-size: @baseFontSize; }
// Page header
// -------------------------
.page-header {
padding-bottom: (@baseLineHeight / 2) - 1;
margin: @baseLineHeight 0 (@baseLineHeight * 1.5);
border-bottom: 1px solid @grayLighter;
}
// Lists
// --------------------------------------------------
// Unordered and Ordered lists
ul, ol {
padding: 0;
margin: 0 0 @baseLineHeight / 2 25px;
}
ul ul,
ul ol,
ol ol,
ol ul {
margin-bottom: 0;
}
li {
line-height: @baseLineHeight;
}
// Remove default list styles
ul.unstyled,
ol.unstyled {
margin-left: 0;
list-style: none;
}
// Single-line list items
ul.inline,
ol.inline {
margin-left: 0;
list-style: none;
> li {
display: inline-block;
.ie7-inline-block();
padding-left: 5px;
padding-right: 5px;
}
}
// Description Lists
dl {
margin-bottom: @baseLineHeight;
}
dt,
dd {
line-height: @baseLineHeight;
}
dt {
font-weight: bold;
}
dd {
margin-left: @baseLineHeight / 2;
}
// Horizontal layout (like forms)
.dl-horizontal {
.clearfix(); // Ensure dl clears floats if empty dd elements present
dt {
float: left;
width: @horizontalComponentOffset - 20;
clear: left;
text-align: right;
.text-overflow();
}
dd {
margin-left: @horizontalComponentOffset;
}
}
// MISC
// ----
// Horizontal rules
hr {
margin: @baseLineHeight 0;
border: 0;
border-top: 1px solid @hrBorder;
border-bottom: 1px solid @white;
}
// Abbreviations and acronyms
abbr[title],
// Added data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257
abbr[data-original-title] {
cursor: help;
border-bottom: 1px dotted @grayLight;
}
abbr.initialism {
font-size: 90%;
text-transform: uppercase;
}
// Blockquotes
blockquote {
padding: 0 0 0 15px;
margin: 0 0 @baseLineHeight;
border-left: 5px solid @grayLighter;
p {
margin-bottom: 0;
font-size: @baseFontSize * 1.25;
font-weight: 300;
line-height: 1.25;
}
small {
display: block;
line-height: @baseLineHeight;
color: @grayLight;
&:before {
content: '\2014 \00A0';
}
}
// Float right with text-align: right
&.pull-right {
float: right;
padding-right: 15px;
padding-left: 0;
border-right: 5px solid @grayLighter;
border-left: 0;
p,
small {
text-align: right;
}
small {
&:before {
content: '';
}
&:after {
content: '\00A0 \2014';
}
}
}
}
// Quotes
q:before,
q:after,
blockquote:before,
blockquote:after {
content: "";
}
// Addresses
address {
display: block;
margin-bottom: @baseLineHeight;
font-style: normal;
line-height: @baseLineHeight;
}