Commit 5b35c694 by Marcus Efraimsson

dashboard: generate and include uid in dashboard model. #7883

parent 025a14ec
......@@ -413,6 +413,12 @@
version = "1.6.3"
[[projects]]
branch = "master"
name = "github.com/teris-io/shortid"
packages = ["."]
revision = "771a37caa5cf0c81f585d7b6df4dfc77e0615b5c"
[[projects]]
name = "github.com/uber/jaeger-client-go"
packages = [
".",
......@@ -625,6 +631,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "98e8d8f5fb21fe448aeb3db41c9fed85fe3bf80400e553211cf39a9c05720e01"
inputs-digest = "4de68f1342ba98a637ec8ca7496aeeae2021bf9e4c7c80db7924e14709151a62"
solver-name = "gps-cdcl"
solver-version = 1
......@@ -193,3 +193,7 @@ ignored = [
non-go = true
go-tests = true
unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/teris-io/shortid"
......@@ -7,6 +7,7 @@ import (
"github.com/gosimple/slug"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/teris-io/shortid"
)
// Typed errors
......@@ -39,6 +40,7 @@ var (
// Dashboard model
type Dashboard struct {
Id int64
Uid string
Slug string
OrgId int64
GnetId int64
......@@ -61,6 +63,7 @@ type Dashboard struct {
// NewDashboard creates a new dashboard
func NewDashboard(title string) *Dashboard {
dash := &Dashboard{}
dash.Uid = DashboardUid()
dash.Data = simplejson.New()
dash.Data.Set("title", title)
dash.Title = title
......@@ -107,9 +110,21 @@ func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
dash.GnetId = int64(gnetId)
}
if uid, err := dash.Data.Get("uid").String(); err == nil {
dash.Uid = uid
} else {
dash.Uid = DashboardUid()
}
return dash
}
func DashboardUid() string {
gen, _ := shortid.New(1, shortid.DefaultABC, 1)
uid, _ := gen.Generate()
return uid
}
// GetDashboardModel turns the command into the savable model
func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
dash := NewDashboardFromJson(cmd.Dashboard)
......
......@@ -175,6 +175,7 @@ func GetDashboard(query *m.GetDashboardQuery) error {
}
dashboard.Data.Set("id", dashboard.Id)
dashboard.Data.Set("uid", dashboard.Uid)
query.Result = &dashboard
return nil
}
......
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Copyright (c) 2016-2017. Oleg Sklyar & teris.io. All rights reserved.
// See the LICENSE file in the project root for licensing information.
// Original algorithm:
// Copyright (c) 2015 Dylan Greene, contributors: https://github.com/dylang/shortid.
// MIT-license as found in the LICENSE file.
// Seed computation: based on The Central Randomizer 1.3
// Copyright (c) 1997 Paul Houle (houle@msc.cornell.edu)
// Package shortid enables the generation of short, unique, non-sequential and by default URL friendly
// Ids. The package is heavily inspired by the node.js https://github.com/dylang/shortid library.
//
// Id Length
//
// The standard Id length is 9 symbols when generated at a rate of 1 Id per millisecond,
// occasionally it reaches 11 (at the rate of a few thousand Ids per millisecond) and very-very
// rarely it can go beyond that during continuous generation at full throttle on high-performant
// hardware. A test generating 500k Ids at full throttle on conventional hardware generated the
// following Ids at the head and the tail (length > 9 is expected for this test):
//
// -NDveu-9Q
// iNove6iQ9J
// NVDve6-9Q
// VVDvc6i99J
// NVovc6-QQy
// VVoveui9QC
// ...
// tFmGc6iQQs
// KpTvcui99k
// KFTGcuiQ9p
// KFmGeu-Q9O
// tFTvcu-QQt
// tpTveu-99u
//
// Life span
//
// The package guarantees the generation of unique Ids with zero collisions for 34 years
// (1/1/2016-1/1/2050) using the same worker Id within a single (although concurrent) application if
// application restarts take longer than 1 millisecond. The package supports up to 32 works, all
// providing unique sequences.
//
// Implementation details
//
// Although heavily inspired by the node.js shortid library this is
// not a simple Go port. In addition it
//
// - is safe to concurrency;
// - does not require any yearly version/epoch resets;
// - provides stable Id size over a long period at the rate of 1ms;
// - guarantees no collisions (due to guaranteed fixed size of Ids between milliseconds and because
// multiple requests within the same ms lead to longer Ids with the prefix unique to the ms);
// - supports 32 over 16 workers.
//
// The algorithm uses less randomness than the original node.js implementation, which permits to
// extend the life span as well as reduce and guarantee the length. In general terms, each Id
// has the following 3 pieces of information encoded: the millisecond (first 8 symbols), the worker
// Id (9th symbol), running concurrent counter within the same millisecond, only if required, over
// all remaining symbols. The element of randomness per symbol is 1/2 for the worker and the
// millisecond and 0 for the counter. Here 0 means no randomness, i.e. every value is encoded using
// a 64-base alphabet; 1/2 means one of two matching symbols of the supplied alphabet, 1/4 one of
// four matching symbols. The original algorithm of the node.js module uses 1/4 throughout.
//
// All methods accepting the parameters that govern the randomness are exported and can be used
// to directly implement an algorithm with e.g. more randomness, but with longer Ids and shorter
// life spans.
package shortid
import (
randc "crypto/rand"
"errors"
"fmt"
"math"
randm "math/rand"
"sync"
"sync/atomic"
"time"
"unsafe"
)
// Version defined the library version.
const Version = 1.1
// DefaultABC is the default URL-friendly alphabet.
const DefaultABC = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-"
// Abc represents a shuffled alphabet used to generate the Ids and provides methods to
// encode data.
type Abc struct {
alphabet []rune
}
// Shortid type represents a short Id generator working with a given alphabet.
type Shortid struct {
abc Abc
worker uint
epoch time.Time // ids can be generated for 34 years since this date
ms uint // ms since epoch for the last id
count uint // request count within the same ms
mx sync.Mutex // locks access to ms and count
}
var shortid *Shortid
func init() {
shortid = MustNew(0, DefaultABC, 1)
}
// GetDefault retrieves the default short Id generator initialised with the default alphabet,
// worker=0 and seed=1. The default can be overwritten using SetDefault.
func GetDefault() *Shortid {
return (*Shortid)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&shortid))))
}
// SetDefault overwrites the default generator.
func SetDefault(sid *Shortid) {
target := (*unsafe.Pointer)(unsafe.Pointer(&shortid))
source := unsafe.Pointer(sid)
atomic.SwapPointer(target, source)
}
// Generate generates an Id using the default generator.
func Generate() (string, error) {
return shortid.Generate()
}
// MustGenerate acts just like Generate, but panics instead of returning errors.
func MustGenerate() string {
id, err := Generate()
if err == nil {
return id
}
panic(err)
}
// New constructs an instance of the short Id generator for the given worker number [0,31], alphabet
// (64 unique symbols) and seed value (to shuffle the alphabet). The worker number should be
// different for multiple or distributed processes generating Ids into the same data space. The
// seed, on contrary, should be identical.
func New(worker uint8, alphabet string, seed uint64) (*Shortid, error) {
if worker > 31 {
return nil, errors.New("expected worker in the range [0,31]")
}
abc, err := NewAbc(alphabet, seed)
if err == nil {
sid := &Shortid{
abc: abc,
worker: uint(worker),
epoch: time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC),
ms: 0,
count: 0,
}
return sid, nil
}
return nil, err
}
// MustNew acts just like New, but panics instead of returning errors.
func MustNew(worker uint8, alphabet string, seed uint64) *Shortid {
sid, err := New(worker, alphabet, seed)
if err == nil {
return sid
}
panic(err)
}
// Generate generates a new short Id.
func (sid *Shortid) Generate() (string, error) {
return sid.GenerateInternal(nil, sid.epoch)
}
// MustGenerate acts just like Generate, but panics instead of returning errors.
func (sid *Shortid) MustGenerate() string {
id, err := sid.Generate()
if err == nil {
return id
}
panic(err)
}
// GenerateInternal should only be used for testing purposes.
func (sid *Shortid) GenerateInternal(tm *time.Time, epoch time.Time) (string, error) {
ms, count := sid.getMsAndCounter(tm, epoch)
idrunes := make([]rune, 9)
if tmp, err := sid.abc.Encode(ms, 8, 5); err == nil {
copy(idrunes, tmp) // first 8 symbols
} else {
return "", err
}
if tmp, err := sid.abc.Encode(sid.worker, 1, 5); err == nil {
idrunes[8] = tmp[0]
} else {
return "", err
}
if count > 0 {
if countrunes, err := sid.abc.Encode(count, 0, 6); err == nil {
// only extend if really need it
idrunes = append(idrunes, countrunes...)
} else {
return "", err
}
}
return string(idrunes), nil
}
func (sid *Shortid) getMsAndCounter(tm *time.Time, epoch time.Time) (uint, uint) {
sid.mx.Lock()
defer sid.mx.Unlock()
var ms uint
if tm != nil {
ms = uint(tm.Sub(epoch).Nanoseconds() / 1000000)
} else {
ms = uint(time.Now().Sub(epoch).Nanoseconds() / 1000000)
}
if ms == sid.ms {
sid.count++
} else {
sid.count = 0
sid.ms = ms
}
return sid.ms, sid.count
}
// String returns a string representation of the short Id generator.
func (sid *Shortid) String() string {
return fmt.Sprintf("Shortid(worker=%v, epoch=%v, abc=%v)", sid.worker, sid.epoch, sid.abc)
}
// Abc returns the instance of alphabet used for representing the Ids.
func (sid *Shortid) Abc() Abc {
return sid.abc
}
// Epoch returns the value of epoch used as the beginning of millisecond counting (normally
// 2016-01-01 00:00:00 local time)
func (sid *Shortid) Epoch() time.Time {
return sid.epoch
}
// Worker returns the value of worker for this short Id generator.
func (sid *Shortid) Worker() uint {
return sid.worker
}
// NewAbc constructs a new instance of shuffled alphabet to be used for Id representation.
func NewAbc(alphabet string, seed uint64) (Abc, error) {
runes := []rune(alphabet)
if len(runes) != len(DefaultABC) {
return Abc{}, fmt.Errorf("alphabet must contain %v unique characters", len(DefaultABC))
}
if nonUnique(runes) {
return Abc{}, errors.New("alphabet must contain unique characters only")
}
abc := Abc{alphabet: nil}
abc.shuffle(alphabet, seed)
return abc, nil
}
// MustNewAbc acts just like NewAbc, but panics instead of returning errors.
func MustNewAbc(alphabet string, seed uint64) Abc {
res, err := NewAbc(alphabet, seed)
if err == nil {
return res
}
panic(err)
}
func nonUnique(runes []rune) bool {
found := make(map[rune]struct{})
for _, r := range runes {
if _, seen := found[r]; !seen {
found[r] = struct{}{}
}
}
return len(found) < len(runes)
}
func (abc *Abc) shuffle(alphabet string, seed uint64) {
source := []rune(alphabet)
for len(source) > 1 {
seed = (seed*9301 + 49297) % 233280
i := int(seed * uint64(len(source)) / 233280)
abc.alphabet = append(abc.alphabet, source[i])
source = append(source[:i], source[i+1:]...)
}
abc.alphabet = append(abc.alphabet, source[0])
}
// Encode encodes a given value into a slice of runes of length nsymbols. In case nsymbols==0, the
// length of the result is automatically computed from data. Even if fewer symbols is required to
// encode the data than nsymbols, all positions are used encoding 0 where required to guarantee
// uniqueness in case further data is added to the sequence. The value of digits [4,6] represents
// represents n in 2^n, which defines how much randomness flows into the algorithm: 4 -- every value
// can be represented by 4 symbols in the alphabet (permitting at most 16 values), 5 -- every value
// can be represented by 2 symbols in the alphabet (permitting at most 32 values), 6 -- every value
// is represented by exactly 1 symbol with no randomness (permitting 64 values).
func (abc *Abc) Encode(val, nsymbols, digits uint) ([]rune, error) {
if digits < 4 || 6 < digits {
return nil, fmt.Errorf("allowed digits range [4,6], found %v", digits)
}
var computedSize uint = 1
if val >= 1 {
computedSize = uint(math.Log2(float64(val)))/digits + 1
}
if nsymbols == 0 {
nsymbols = computedSize
} else if nsymbols < computedSize {
return nil, fmt.Errorf("cannot accommodate data, need %v digits, got %v", computedSize, nsymbols)
}
mask := 1<<digits - 1
random := make([]int, int(nsymbols))
// no random component if digits == 6
if digits < 6 {
copy(random, maskedRandomInts(len(random), 0x3f-mask))
}
res := make([]rune, int(nsymbols))
for i := range res {
shift := digits * uint(i)
index := (int(val>>shift) & mask) | random[i]
res[i] = abc.alphabet[index]
}
return res, nil
}
// MustEncode acts just like Encode, but panics instead of returning errors.
func (abc *Abc) MustEncode(val, size, digits uint) []rune {
res, err := abc.Encode(val, size, digits)
if err == nil {
return res
}
panic(err)
}
func maskedRandomInts(size, mask int) []int {
ints := make([]int, size)
bytes := make([]byte, size)
if _, err := randc.Read(bytes); err == nil {
for i, b := range bytes {
ints[i] = int(b) & mask
}
} else {
for i := range ints {
ints[i] = randm.Intn(0xff) & mask
}
}
return ints
}
// String returns a string representation of the Abc instance.
func (abc Abc) String() string {
return fmt.Sprintf("Abc{alphabet='%v')", abc.Alphabet())
}
// Alphabet returns the alphabet used as an immutable string.
func (abc Abc) Alphabet() string {
return string(abc.alphabet)
}
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