Commit fb57bf77 by Torkel Ödegaard

ux(getting started): progress on getting started panel and persited help flag states, #6466

parent 5f2cb8e1
...@@ -113,6 +113,9 @@ func Register(r *macaron.Macaron) { ...@@ -113,6 +113,9 @@ func Register(r *macaron.Macaron) {
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword)) r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
r.Get("/quotas", wrap(GetUserQuotas)) r.Get("/quotas", wrap(GetUserQuotas))
r.Put("/helpflags/:id", wrap(SetHelpFlag))
// For dev purpose
r.Get("/helpflags/clear", wrap(ClearHelpFlags))
r.Get("/preferences", wrap(GetUserPreferences)) r.Get("/preferences", wrap(GetUserPreferences))
r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences)) r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences))
......
...@@ -22,19 +22,20 @@ type LoginCommand struct { ...@@ -22,19 +22,20 @@ type LoginCommand struct {
} }
type CurrentUser struct { type CurrentUser struct {
IsSignedIn bool `json:"isSignedIn"` IsSignedIn bool `json:"isSignedIn"`
Id int64 `json:"id"` Id int64 `json:"id"`
Login string `json:"login"` Login string `json:"login"`
Email string `json:"email"` Email string `json:"email"`
Name string `json:"name"` Name string `json:"name"`
LightTheme bool `json:"lightTheme"` LightTheme bool `json:"lightTheme"`
OrgId int64 `json:"orgId"` OrgId int64 `json:"orgId"`
OrgName string `json:"orgName"` OrgName string `json:"orgName"`
OrgRole m.RoleType `json:"orgRole"` OrgRole m.RoleType `json:"orgRole"`
IsGrafanaAdmin bool `json:"isGrafanaAdmin"` IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
GravatarUrl string `json:"gravatarUrl"` GravatarUrl string `json:"gravatarUrl"`
Timezone string `json:"timezone"` Timezone string `json:"timezone"`
Locale string `json:"locale"` Locale string `json:"locale"`
HelpFlags1 m.HelpFlags1 `json:"helpFlags1"`
} }
type DashboardMeta struct { type DashboardMeta struct {
......
...@@ -58,6 +58,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { ...@@ -58,6 +58,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
LightTheme: prefs.Theme == "light", LightTheme: prefs.Theme == "light",
Timezone: prefs.Timezone, Timezone: prefs.Timezone,
Locale: locale, Locale: locale,
HelpFlags1: c.HelpFlags1,
}, },
Settings: settings, Settings: settings,
AppUrl: appUrl, AppUrl: appUrl,
......
...@@ -180,3 +180,34 @@ func SearchUsers(c *middleware.Context) Response { ...@@ -180,3 +180,34 @@ func SearchUsers(c *middleware.Context) Response {
return Json(200, query.Result) return Json(200, query.Result)
} }
func SetHelpFlag(c *middleware.Context) Response {
flag := c.ParamsInt64(":id")
bitmask := &c.HelpFlags1
bitmask.AddFlag(m.HelpFlags1(flag))
cmd := m.SetUserHelpFlagCommand{
UserId: c.UserId,
HelpFlags1: *bitmask,
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update help flag", err)
}
return Json(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
}
func ClearHelpFlags(c *middleware.Context) Response {
cmd := m.SetUserHelpFlagCommand{
UserId: c.UserId,
HelpFlags1: m.HelpFlags1(0),
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update help flag", err)
}
return Json(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
}
package models
type HelpFlags1 uint64
const (
HelpFlagGettingStartedPanelDismissed HelpFlags1 = 1 << iota
HelpFlagDashboardHelp1
)
func (f HelpFlags1) HasFlag(flag HelpFlags1) bool { return f&flag != 0 }
func (f *HelpFlags1) AddFlag(flag HelpFlags1) { *f |= flag }
func (f *HelpFlags1) ClearFlag(flag HelpFlags1) { *f &= ^flag }
func (f *HelpFlags1) ToggleFlag(flag HelpFlags1) { *f ^= flag }
type SetUserHelpFlagCommand struct {
HelpFlags1 HelpFlags1
UserId int64
}
...@@ -22,6 +22,7 @@ type User struct { ...@@ -22,6 +22,7 @@ type User struct {
Company string Company string
EmailVerified bool EmailVerified bool
Theme string Theme string
HelpFlags1 HelpFlags1
IsAdmin bool IsAdmin bool
OrgId int64 OrgId int64
...@@ -144,6 +145,7 @@ type SignedInUser struct { ...@@ -144,6 +145,7 @@ type SignedInUser struct {
Email string Email string
ApiKeyId int64 ApiKeyId int64
IsGrafanaAdmin bool IsGrafanaAdmin bool
HelpFlags1 HelpFlags1
} }
type UserProfileDTO struct { type UserProfileDTO struct {
......
...@@ -88,4 +88,8 @@ func addUserMigrations(mg *Migrator) { ...@@ -88,4 +88,8 @@ func addUserMigrations(mg *Migrator) {
})) }))
mg.AddMigration("Drop old table user_v1", NewDropTableMigration("user_v1")) mg.AddMigration("Drop old table user_v1", NewDropTableMigration("user_v1"))
mg.AddMigration("Add column help_flags1 to user table", NewAddColumnMigration(userV2, &Column{
Name: "help_flags1", Type: DB_BigInt, Nullable: false, Default: "0",
}))
} }
...@@ -28,6 +28,7 @@ func init() { ...@@ -28,6 +28,7 @@ func init() {
bus.AddHandler("sql", DeleteUser) bus.AddHandler("sql", DeleteUser)
bus.AddHandler("sql", SetUsingOrg) bus.AddHandler("sql", SetUsingOrg)
bus.AddHandler("sql", UpdateUserPermissions) bus.AddHandler("sql", UpdateUserPermissions)
bus.AddHandler("sql", SetUserHelpFlag)
} }
func getOrgIdForNewUser(cmd *m.CreateUserCommand, sess *session) (int64, error) { func getOrgIdForNewUser(cmd *m.CreateUserCommand, sess *session) (int64, error) {
...@@ -207,7 +208,7 @@ func GetUserByEmail(query *m.GetUserByEmailQuery) error { ...@@ -207,7 +208,7 @@ func GetUserByEmail(query *m.GetUserByEmailQuery) error {
if err != nil { if err != nil {
return err return err
} else if has == false { } else if has == false {
return m.ErrUserNotFound return m.ErrUserNotFound
} }
query.Result = user query.Result = user
...@@ -308,6 +309,7 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error { ...@@ -308,6 +309,7 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
u.email as email, u.email as email,
u.login as login, u.login as login,
u.name as name, u.name as name,
u.help_flags1 as help_flags1,
org.name as org_name, org.name as org_name,
org_user.role as org_role, org_user.role as org_role,
org.id as org_id org.id as org_id
...@@ -380,3 +382,20 @@ func UpdateUserPermissions(cmd *m.UpdateUserPermissionsCommand) error { ...@@ -380,3 +382,20 @@ func UpdateUserPermissions(cmd *m.UpdateUserPermissionsCommand) error {
return err return err
}) })
} }
func SetUserHelpFlag(cmd *m.SetUserHelpFlagCommand) error {
return inTransaction2(func(sess *session) error {
user := m.User{
Id: cmd.UserId,
HelpFlags1: cmd.HelpFlags1,
Updated: time.Now(),
}
if _, err := sess.Id(cmd.UserId).Cols("help_flags1").Update(&user); err != nil {
return err
}
return nil
})
}
...@@ -74,7 +74,9 @@ export class BackendSrv { ...@@ -74,7 +74,9 @@ export class BackendSrv {
return this.$http(options).then(results => { return this.$http(options).then(results => {
if (options.method !== 'GET') { if (options.method !== 'GET') {
if (results && results.data.message) { if (results && results.data.message) {
this.alertSrv.set(results.data.message, '', 'success', 3000); if (options.showSuccessAlert !== false) {
this.alertSrv.set(results.data.message, '', 'success', 3000);
}
} }
} }
return results.data; return results.data;
......
...@@ -10,6 +10,7 @@ export class User { ...@@ -10,6 +10,7 @@ export class User {
isSignedIn: any; isSignedIn: any;
orgRole: any; orgRole: any;
timezone: string; timezone: string;
helpFlags1: number;
constructor() { constructor() {
if (config.bootData.user) { if (config.bootData.user) {
......
...@@ -84,11 +84,8 @@ export class AddPanelCtrl { ...@@ -84,11 +84,8 @@ export class AddPanelCtrl {
var panel = { var panel = {
id: null, id: null,
title: config.new_panel_title, title: config.new_panel_title,
error: false,
span: span < defaultSpan && span > 0 ? span : defaultSpan, span: span < defaultSpan && span > 0 ? span : defaultSpan,
editable: true,
type: panelPluginInfo.id, type: panelPluginInfo.id,
isNew: true,
}; };
this.rowCtrl.closeDropView(); this.rowCtrl.closeDropView();
......
...@@ -19,7 +19,6 @@ export class DashboardRow { ...@@ -19,7 +19,6 @@ export class DashboardRow {
showTitle: false, showTitle: false,
titleSize: 'h6', titleSize: 'h6',
height: 250, height: 250,
isNew: false,
repeat: null, repeat: null,
repeatRowId: null, repeatRowId: null,
repeatIteration: null, repeatIteration: null,
......
...@@ -2,23 +2,34 @@ ...@@ -2,23 +2,34 @@
<div class="dashlist-section"> <div class="dashlist-section">
<h6 class="dashlist-section-header"> <h6 class="dashlist-section-header">
Getting Started with Grafana Getting Started with Grafana
<button class="dashlist-CTA-close-btn"><i class="fa fa-remove"></i></button> <button class="dashlist-CTA-close-btn" ng-click="ctrl.dismiss()">
<i class="fa fa-remove"></i>
</button>
</h6> </h6>
<ul class="progress-tracker progress-tracker--text progress-tracker--center"> <ul class="progress-tracker progress-tracker--text progress-tracker--center" ng-if="ctrl.checksDone">
<li class="progress-step is-complete"> <li class="progress-step completed">
<span class="progress-marker"><i class="icon-gf icon-gf-check gettingstarted-icon-success"></i></span> <span class="progress-marker"><i class="icon-gf icon-gf-check gettingstarted-icon-success"></i></span>
<span class="progress-text"><span class="gettingstarted-blurb-success">Install Grafana</span></span> <span class="progress-text"><span class="gettingstarted-blurb-success">Install Grafana</span></span>
</li> </li>
<li class="progress-step is-active"> <li class="progress-step active" ng-if="!ctrl.hasDatasources">
<span class="progress-marker"><i class="icon-gf icon-gf-datasources gettingstarted-icon-active"></i></span> <span class="progress-marker"><i class="icon-gf icon-gf-datasources gettingstarted-icon-active"></i></span>
<span class="progress-text"> <span class="progress-text">
<a href="#" class="gettingstarted-blurb">Create your first data source.</a> <a href="#" class="gettingstarted-blurb">Create your first data source.</a>
<button class="btn btn-success btn-small">Add Data Source</button> <button class="btn btn-success btn-small">Add Data Source</button>
</span> </span>
</li> </li>
<li class="progress-step"> <li class="progress-step completed" ng-if="ctrl.hasDatasources">
<span class="progress-marker"><i class="icon-gf icon-gf-check gettingstarted-icon-success"></i></span>
<span class="progress-text">
<span class="gettingstarted-blurb-success">Create your first data source.</span>
</span>
</li>
<li class="progress-step active" ng-if="ctrl.hasDatasources">
<span class="progress-marker"><i class="icon-gf icon-gf-dashboard gettingstarted-icon-upcoming"></i></span> <span class="progress-marker"><i class="icon-gf icon-gf-dashboard gettingstarted-icon-upcoming"></i></span>
<span class="progress-text"><a href="#" class="gettingstarted-blurb-upcoming">Create your first dashboard.</a></span> <span class="progress-text">
<a href="#" class="gettingstarted-blurb-upcoming">Create your first dashboard.</a>
<button class="btn btn-success btn-small">Add Data Source</button>
</span>
</li> </li>
<li class="progress-step"> <li class="progress-step">
<span class="progress-marker"><i class="icon-gf icon-gf-users gettingstarted-icon-upcoming"></i></span> <span class="progress-marker"><i class="icon-gf icon-gf-users gettingstarted-icon-upcoming"></i></span>
......
...@@ -2,14 +2,43 @@ ...@@ -2,14 +2,43 @@
import {PanelCtrl} from 'app/plugins/sdk'; import {PanelCtrl} from 'app/plugins/sdk';
class GettingstartedPanelCtrl extends PanelCtrl { import {contextSrv} from 'app/core/core';
class GettingStartedPanelCtrl extends PanelCtrl {
static templateUrl = 'public/app/plugins/panel/gettingstarted/module.html'; static templateUrl = 'public/app/plugins/panel/gettingstarted/module.html';
hasDatasources: boolean;
checksDone: boolean;
/** @ngInject */ /** @ngInject **/
constructor($scope, $injector) { constructor($scope, $injector, private backendSrv, private datasourceSrv) {
super($scope, $injector); super($scope, $injector);
/* tslint:disable */
if (contextSrv.user.helpFlags1 & 1) {
this.row.removePanel(this.panel, false);
return;
}
/* tslint:enable */
var datasources = datasourceSrv.getMetricSources().filter(item => {
return item.meta.builtIn === false;
});
this.hasDatasources = datasources.length > 0;
this.checksDone = true;
} }
dismiss() {
this.row.removePanel(this.panel, false);
this.backendSrv.request({
method: 'PUT',
url: '/api/user/helpflags/1',
showSuccessAlert: false,
}).then(res => {
contextSrv.user.helpFlags1 = res.helpFlags1;
});
}
} }
export {GettingstartedPanelCtrl, GettingstartedPanelCtrl as PanelCtrl} export {GettingStartedPanelCtrl, GettingStartedPanelCtrl as PanelCtrl}
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"sharedCrosshair": false, "sharedCrosshair": false,
"rows": [ "rows": [
{ {
"title": "Row title",
"collapse": false, "collapse": false,
"editable": true, "editable": true,
"height": "25px", "height": "25px",
...@@ -24,9 +25,16 @@ ...@@ -24,9 +25,16 @@
"title": "", "title": "",
"transparent": true, "transparent": true,
"type": "text" "type": "text"
},
{
"id": 8,
"links": [],
"span": 12,
"title": "",
"transparent": false,
"type": "gettingstarted"
} }
], ]
"title": "New row"
}, },
{ {
"collapse": false, "collapse": false,
......
ul.gettingstarted-flex-container {
display: flex;
justify-content: space-around;
flex-direction: row;
padding: 20px;
list-style-type: none;
}
.gettingstarted-flex-item { .gettingstarted-flex-item {
align-items: center; align-items: center;
display: flex; display: flex;
...@@ -19,14 +11,14 @@ ul.gettingstarted-flex-container { ...@@ -19,14 +11,14 @@ ul.gettingstarted-flex-container {
text-align: center; text-align: center;
} }
a.gettingstarted-blurb{ .gettingstarted-blurb {
@extend .gettingstarted-blurb-copy; @extend .gettingstarted-blurb-copy;
color: $text-color; color: $text-color;
display: block; display: block;
}
a.gettingstarted-blurb:hover{ &:hover{
text-decoration: underline; text-decoration: underline;
}
} }
.gettingstarted-blurb-success { .gettingstarted-blurb-success {
...@@ -35,27 +27,11 @@ a.gettingstarted-blurb:hover{ ...@@ -35,27 +27,11 @@ a.gettingstarted-blurb:hover{
text-decoration: line-through; text-decoration: line-through;
} }
a.gettingstarted-blurb-upcoming { .gettingstarted-blurb-upcoming {
@extend .gettingstarted-blurb-copy; @extend .gettingstarted-blurb-copy;
color: $text-color-weak; color: $text-color-weak;
} }
.gettingstarted-icon-container {
height: 50px;
}
.gettingstarted-icon-active {
color: $brand-primary;
-webkit-text-fill-color: transparent;
background: $brand-gradient;
-webkit-background-clip: text;
text-decoration:none;
font-size: 35px;
vertical-align: sub;
animation: iconPulse 500ms forwards 1s;
will-change: zoom;
}
.gettingstarted-icon-upcoming { .gettingstarted-icon-upcoming {
color: $text-color-weak; color: $text-color-weak;
text-decoration:none; text-decoration:none;
...@@ -70,7 +46,6 @@ a.gettingstarted-blurb-upcoming { ...@@ -70,7 +46,6 @@ a.gettingstarted-blurb-upcoming {
vertical-align: sub; vertical-align: sub;
} }
.dashlist-CTA-close-btn { .dashlist-CTA-close-btn {
float: right; float: right;
padding: 0; padding: 0;
...@@ -86,25 +61,6 @@ a.gettingstarted-blurb-upcoming { ...@@ -86,25 +61,6 @@ a.gettingstarted-blurb-upcoming {
} }
} }
@keyframes iconPulse {
from {
zoom: 100%;
}
50% {
zoom: 102%;
}
to {
zoom: 100%;
}
}
// ----- Progress Tracker -----
// ----- Variables -----
// Colours // Colours
$progress-color-dark: $panel-bg !default; $progress-color-dark: $panel-bg !default;
$progress-color: $panel-bg !default; $progress-color: $panel-bg !default;
...@@ -240,17 +196,16 @@ $ripple-color: rgba(0, 0, 0, 0.3) !default; ...@@ -240,17 +196,16 @@ $ripple-color: rgba(0, 0, 0, 0.3) !default;
// States // States
.progress-step { .progress-step {
// Inactive - Default state // Inactive - Default state
@include progress-state($progress-color, null, #fff, $progress-color-grey-light, $progress-color-grey-dark); @include progress-state($progress-color, null, #fff, $progress-color-grey-light, $progress-color-grey-dark);
// Active state // Active state
&.is-active { &.active {
@include progress-state($progress-color); @include progress-state($progress-color);
} }
// Complete state // Complete state
&.is-complete { &.completed {
@include progress-state($progress-color-dark, $path-color: $progress-color-grey); @include progress-state($progress-color-dark, $path-color: $progress-color-grey);
} }
...@@ -258,11 +213,8 @@ $ripple-color: rgba(0, 0, 0, 0.3) !default; ...@@ -258,11 +213,8 @@ $ripple-color: rgba(0, 0, 0, 0.3) !default;
&:hover { &:hover {
@include progress-state($progress-color-light); @include progress-state($progress-color-light);
} }
} }
// ----- Modifiers ----- // ----- Modifiers -----
// Center align markers and text // Center align markers and text
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment