snapshot of https://github.com/traggo/server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
build / build (push) Failing after 3s

This commit is contained in:
upstream-mirror
2026-09-16 05:24:45 +05:00
commit cda7c1a474
272 changed files with 31973 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
package convert
import (
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// ToExternalDashboards converts dashboards.
func ToExternalDashboards(dashboards []model.Dashboard) ([]*gqlmodel.Dashboard, error) {
result := []*gqlmodel.Dashboard{}
for _, dashboard := range dashboards {
if converted, err := ToExternalDashboard(dashboard); err == nil {
result = append(result, converted)
} else {
return nil, err
}
}
return result, nil
}
// ToExternalDashboard converts a dashboard.
func ToExternalDashboard(dashboard model.Dashboard) (*gqlmodel.Dashboard, error) {
entries, err := toExternalEntries(dashboard.Entries)
if err != nil {
return nil, err
}
ranges, err := toExternalDashboardsRanges(dashboard.Ranges)
if err != nil {
return nil, err
}
return &gqlmodel.Dashboard{
ID: dashboard.ID,
Name: dashboard.Name,
Items: entries,
Ranges: ranges,
}, nil
}

View File

@@ -0,0 +1,39 @@
package convert
import (
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
func tagFiltersToExternal(tags []model.DashboardTagFilter, include bool) []*gqlmodel.TimeSpanTag {
if len(tags) == 0 {
return nil
}
result := []*gqlmodel.TimeSpanTag{}
for _, tag := range tags {
if tag.Include == include {
result = append(result, &gqlmodel.TimeSpanTag{
Key: tag.Key,
Value: tag.StringValue,
})
}
}
return result
}
func TagFiltersToInternal(gqls []*gqlmodel.InputTimeSpanTag, include bool) []model.DashboardTagFilter {
result := make([]model.DashboardTagFilter, 0)
for _, tag := range gqls {
result = append(result, tagFilterToInternal(*tag, include))
}
return result
}
func tagFilterToInternal(gqls gqlmodel.InputTimeSpanTag, include bool) model.DashboardTagFilter {
return model.DashboardTagFilter{
Key: gqls.Key,
StringValue: gqls.Value,
Include: include,
}
}

View File

@@ -0,0 +1,59 @@
package convert
import (
"strings"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
func toExternalEntries(entries []model.DashboardEntry) ([]*gqlmodel.DashboardEntry, error) {
result := []*gqlmodel.DashboardEntry{}
for _, entry := range entries {
converted, err := ToExternalEntry(entry)
if err != nil {
return nil, err
}
result = append(result, converted)
}
return result, nil
}
// ToExternalEntry converts entries.
func ToExternalEntry(entry model.DashboardEntry) (*gqlmodel.DashboardEntry, error) {
mobilePosition, err := toExternalPosition(entry.MobilePosition)
if err != nil {
return &gqlmodel.DashboardEntry{}, err
}
desktopPos, err := toExternalPosition(entry.DesktopPosition)
if err != nil {
return &gqlmodel.DashboardEntry{}, err
}
pos := gqlmodel.ResponsiveDashboardEntryPos{
Mobile: enhancePos(mobilePosition, "mobile"),
Desktop: enhancePos(desktopPos, "desktop"),
}
dateRange := &gqlmodel.RelativeOrStaticRange{
From: entry.RangeFrom,
To: entry.RangeTo,
}
stats := &gqlmodel.StatsSelection{
Interval: ExternalInterval(entry.Interval),
Tags: strings.Split(entry.Keys, ","),
Range: dateRange,
ExcludeTags: tagFiltersToExternal(entry.TagFilters, false),
IncludeTags: tagFiltersToExternal(entry.TagFilters, true),
}
if entry.RangeID != model.NoRangeIDDefined {
stats.RangeID = &entry.RangeID
stats.Range = nil
}
return &gqlmodel.DashboardEntry{
ID: entry.ID,
Title: entry.Title,
Total: entry.Total,
Pos: &pos,
StatsSelection: stats,
EntryType: ExternalEntryType(entry.Type),
}, nil
}

View File

@@ -0,0 +1,46 @@
package convert
import (
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// InternalEntryType converts entry type.
func InternalEntryType(entryType gqlmodel.EntryType) model.DashboardType {
switch entryType {
case gqlmodel.EntryTypeBarChart:
return model.TypeBarChart
case gqlmodel.EntryTypePieChart:
return model.TypePieChart
case gqlmodel.EntryTypeStackedBarChart:
return model.TypeStackedBarChart
case gqlmodel.EntryTypeLineChart:
return model.TypeLineChart
case gqlmodel.EntryTypeHorizontalTable:
return model.HorizontalTable
case gqlmodel.EntryTypeVerticalTable:
return model.VerticalTable
default:
panic("unknown entry type " + entryType)
}
}
// ExternalEntryType converts entry type.
func ExternalEntryType(entryType model.DashboardType) gqlmodel.EntryType {
switch entryType {
case model.TypeBarChart:
return gqlmodel.EntryTypeBarChart
case model.TypePieChart:
return gqlmodel.EntryTypePieChart
case model.TypeStackedBarChart:
return gqlmodel.EntryTypeStackedBarChart
case model.TypeLineChart:
return gqlmodel.EntryTypeLineChart
case model.HorizontalTable:
return gqlmodel.EntryTypeHorizontalTable
case model.VerticalTable:
return gqlmodel.EntryTypeVerticalTable
default:
panic("unknown entry type " + entryType)
}
}

View File

@@ -0,0 +1,46 @@
package convert
import (
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// InternalInterval converts interval.
func InternalInterval(interval gqlmodel.StatsInterval) model.Interval {
switch interval {
case gqlmodel.StatsIntervalHourly:
return model.IntervalHourly
case gqlmodel.StatsIntervalDaily:
return model.IntervalDaily
case gqlmodel.StatsIntervalWeekly:
return model.IntervalWeekly
case gqlmodel.StatsIntervalMonthly:
return model.IntervalMonthly
case gqlmodel.StatsIntervalYearly:
return model.IntervalYearly
case gqlmodel.StatsIntervalSingle:
return model.IntervalSingle
default:
panic("unknown interval type " + interval)
}
}
// ExternalInterval converts interval.
func ExternalInterval(interval model.Interval) gqlmodel.StatsInterval {
switch interval {
case model.IntervalHourly:
return gqlmodel.StatsIntervalHourly
case model.IntervalDaily:
return gqlmodel.StatsIntervalDaily
case model.IntervalWeekly:
return gqlmodel.StatsIntervalWeekly
case model.IntervalMonthly:
return gqlmodel.StatsIntervalMonthly
case model.IntervalYearly:
return gqlmodel.StatsIntervalYearly
case model.IntervalSingle:
return gqlmodel.StatsIntervalSingle
default:
panic("unknown interval type " + interval)
}
}

View File

@@ -0,0 +1,111 @@
package convert
import (
"encoding/json"
"github.com/jinzhu/copier"
"github.com/rs/zerolog/log"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
func enhancePos(pos *gqlmodel.DashboardEntryPos, size string) *gqlmodel.DashboardEntryPos {
switch size {
case "mobile":
pos.MinH = 2
pos.MinW = 1
case "desktop":
pos.MinH = 3
pos.MinW = 2
default:
panic("unknown size")
}
if pos.MinW > pos.W {
pos.W = pos.MinW
}
if pos.MinH > pos.H {
pos.H = pos.MinH
}
return pos
}
// EmptyPos returns a empty position.
func EmptyPos() string {
internal := internalDashboardPositionV1{
Version: "v1",
H: 0,
W: 0,
X: 0,
Y: 0,
}
if bytes, err := json.Marshal(&internal); err != nil {
panic(err)
} else {
return string(bytes)
}
}
func toInternalPosition(external gqlmodel.InputDashboardEntryPos) (string, error) {
internal := internalDashboardPositionV1{Version: "v1"}
if err := copier.Copy(&internal, &external); err != nil {
return "", err
}
bytes, err := json.Marshal(&internal)
if err != nil {
return "", err
}
return string(bytes), nil
}
// ApplyPos copies the position to the external position.
func ApplyPos(entry *model.DashboardEntry, pos *gqlmodel.InputResponsiveDashboardEntryPos) error {
if pos == nil {
return nil
}
if pos.Mobile != nil {
lpos, err := toInternalPosition(*pos.Mobile)
if err != nil {
return err
}
entry.MobilePosition = lpos
}
if pos.Desktop != nil {
mpos, err := toInternalPosition(*pos.Desktop)
if err != nil {
return err
}
entry.DesktopPosition = mpos
}
return nil
}
func toExternalPosition(pos string) (*gqlmodel.DashboardEntryPos, error) {
if pos == "" {
return nil, nil
}
internal := internalDashboardPositionV1{}
if err := json.Unmarshal([]byte(pos), &internal); err != nil {
return nil, err
}
if internal.Version != "v1" {
log.Error().Interface("pos", internal).Msg("Invalid position version")
}
external := gqlmodel.DashboardEntryPos{}
if err := copier.Copy(&external, &internal); err != nil {
return nil, err
}
return &external, nil
}
type internalDashboardPositionV1 struct {
Version string
W int `json:"w"`
H int `json:"h"`
X int `json:"x"`
Y int `json:"y"`
}

View File

@@ -0,0 +1,46 @@
package convert
import (
"fmt"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
"github.com/traggo/server/time"
)
func toExternalDashboardsRanges(ranges []model.DashboardRange) ([]*gqlmodel.NamedDateRange, error) {
result := []*gqlmodel.NamedDateRange{}
for _, xrange := range ranges {
result = append(result, ToExternalDashboardRange(xrange))
}
return result, nil
}
// ToExternalDashboardRange converts a range.
func ToExternalDashboardRange(xrange model.DashboardRange) *gqlmodel.NamedDateRange {
return &gqlmodel.NamedDateRange{
ID: xrange.ID,
Name: xrange.Name,
Editable: xrange.Editable,
Range: &gqlmodel.RelativeOrStaticRange{
From: xrange.From,
To: xrange.To,
},
}
}
// ToInternalDashboardRange converts a range.
func ToInternalDashboardRange(xrange gqlmodel.InputNamedDateRange) (model.DashboardRange, error) {
if err := time.Validate(xrange.Range.From); err != nil {
return model.DashboardRange{}, fmt.Errorf("range from (%s) invalid: %s", xrange.Range.From, err)
}
if err := time.Validate(xrange.Range.To); err != nil {
return model.DashboardRange{}, fmt.Errorf("range to (%s) invalid: %s", xrange.Range.To, err)
}
return model.DashboardRange{
Editable: xrange.Editable,
From: xrange.Range.From,
To: xrange.Range.To,
Name: xrange.Name,
}, nil
}

27
dashboard/create.go Normal file
View File

@@ -0,0 +1,27 @@
package dashboard
import (
"context"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// CreateDashboard creates a dashboard.
func (r *ResolverForDashboard) CreateDashboard(ctx context.Context, name string) (*gqlmodel.Dashboard, error) {
userID := auth.GetUser(ctx).ID
dashboard := model.Dashboard{
UserID: userID,
Name: name,
}
create := r.DB.Create(&dashboard)
if create.Error != nil {
return &gqlmodel.Dashboard{}, create.Error
}
return convert.ToExternalDashboard(dashboard)
}

106
dashboard/dashboard_test.go Normal file
View File

@@ -0,0 +1,106 @@
package dashboard
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestDashboard(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
db.User(1)
db.User(2)
resolver := NewResolverForDashboard(db.DB)
// initial no dashboards
user1 := fake.User(1)
user2 := fake.User(2)
dashboards, err := resolver.Dashboards(user1)
require.NoError(t, err)
require.Empty(t, dashboards)
dashboard, err := resolver.CreateDashboard(user1, "cool dashboard")
require.NoError(t, err)
expectAdded := &gqlmodel.Dashboard{
ID: 1,
Name: "cool dashboard",
Ranges: []*gqlmodel.NamedDateRange{},
Items: []*gqlmodel.DashboardEntry{},
}
require.Equal(t, expectAdded, dashboard)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.Dashboard{
{
ID: 1,
Name: "cool dashboard",
Ranges: []*gqlmodel.NamedDateRange{},
Items: []*gqlmodel.DashboardEntry{},
},
}, dashboards)
dashboards, err = resolver.Dashboards(user2)
require.NoError(t, err)
require.Empty(t, dashboards)
dashboard, err = resolver.UpdateDashboard(user2, 1, "kek")
require.EqualError(t, err, "dashboard does not exist")
require.Nil(t, dashboard)
dashboard, err = resolver.UpdateDashboard(user2, 55, "kek")
require.EqualError(t, err, "dashboard does not exist")
require.Nil(t, dashboard)
dashboard, err = resolver.UpdateDashboard(user1, 1, "kek")
require.NoError(t, err)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.Dashboard{
{
ID: 1,
Name: "kek",
Ranges: []*gqlmodel.NamedDateRange{},
Items: []*gqlmodel.DashboardEntry{},
},
}, dashboards)
dashboard, err = resolver.RemoveDashboard(user2, 1)
require.EqualError(t, err, "dashboard does not exist")
require.Nil(t, dashboard)
dashboard, err = resolver.RemoveDashboard(user2, 55)
require.EqualError(t, err, "dashboard does not exist")
require.Nil(t, dashboard)
dashboard, err = resolver.RemoveDashboard(user1, 1)
require.NoError(t, err)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Empty(t, dashboards)
}
func TestDeleteDashboard(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
dbOne := db.User(1).Dashboard("test")
dbOne.Range("r")
dbOne.Entry("e")
dbTwo := db.User(2).Dashboard("test")
dbTwo.Range("r")
dbTwo.Entry("e")
resolver := NewResolverForDashboard(db.DB)
_, err := resolver.RemoveDashboard(fake.User(dbOne.User.ID), dbOne.Dashboard.ID)
require.NoError(t, err)
dbOne.AssertExists(false).AssertHasEntry("e", false).AssertHasRange("r", false)
dbTwo.AssertExists(true).AssertHasEntry("e", true).AssertHasRange("r", true)
}

View File

@@ -0,0 +1,27 @@
package dashboard
import (
"github.com/jinzhu/gorm"
"github.com/traggo/server/dashboard/dbrange"
"github.com/traggo/server/dashboard/entry"
)
// ResolverForDashboard resolves dashboard specific things.
type ResolverForDashboard struct {
DB *gorm.DB
entry.ResolverForEntry
dbrange.ResolverForRange
}
// NewResolverForDashboard creates a new resolver.
func NewResolverForDashboard(db *gorm.DB) ResolverForDashboard {
return ResolverForDashboard{
DB: db,
ResolverForEntry: entry.ResolverForEntry{
DB: db,
},
ResolverForRange: dbrange.ResolverForRange{
DB: db,
},
}
}

28
dashboard/dbrange/add.go Normal file
View File

@@ -0,0 +1,28 @@
package dbrange
import (
"context"
"github.com/traggo/server/auth"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/dashboard/util"
"github.com/traggo/server/generated/gqlmodel"
)
// AddDashboardRange adds a dashboard range.
func (r *ResolverForRange) AddDashboardRange(ctx context.Context, dashboardID int, rangeArg gqlmodel.InputNamedDateRange) (*gqlmodel.NamedDateRange, error) {
userID := auth.GetUser(ctx).ID
if _, err := util.FindDashboard(r.DB, userID, dashboardID); err != nil {
return nil, err
}
rangeToAdd, err := convert.ToInternalDashboardRange(rangeArg)
if err != nil {
return nil, err
}
rangeToAdd.DashboardID = dashboardID
save := r.DB.Create(&rangeToAdd)
return convert.ToExternalDashboardRange(rangeToAdd), save.Error
}

View File

@@ -0,0 +1,8 @@
package dbrange
import "github.com/jinzhu/gorm"
// ResolverForRange resolves range specific things.
type ResolverForRange struct {
DB *gorm.DB
}

View File

@@ -0,0 +1,201 @@
package dbrange_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/dashboard"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestRanges(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
resolver := dashboard.NewResolverForDashboard(db.DB)
// initial no dashboards
user1 := fake.User(1)
user2 := fake.User(2)
db.User(1)
db.User(2)
dashboards, err := resolver.Dashboards(user1)
require.NoError(t, err)
require.Empty(t, dashboards)
dashboard, err := resolver.CreateDashboard(user1, "cool dashboard")
require.NoError(t, err)
expectAdded := &gqlmodel.Dashboard{
ID: 1,
Name: "cool dashboard",
Ranges: []*gqlmodel.NamedDateRange{},
Items: []*gqlmodel.DashboardEntry{},
}
require.Equal(t, expectAdded, dashboard)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.Dashboard{expectAdded}, dashboards)
dashboards, err = resolver.Dashboards(user2)
require.NoError(t, err)
require.Empty(t, dashboards)
xrange, err := resolver.AddDashboardRange(user1, dashboard.ID, gqlmodel.InputNamedDateRange{
Name: "new range",
Editable: false,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2d",
},
})
require.NoError(t, err)
require.Equal(t, &gqlmodel.NamedDateRange{
ID: 1,
Name: "new range",
Editable: false,
Range: &gqlmodel.RelativeOrStaticRange{
From: "now-1d",
To: "now-2d",
},
}, xrange)
_, err = resolver.AddDashboardRange(user1, dashboard.ID, gqlmodel.InputNamedDateRange{
Name: "new range",
Editable: false,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1",
To: "now-2d",
},
})
require.EqualError(t, err, "range from (now-1) invalid: expected unit at the end but got nothing")
_, err = resolver.AddDashboardRange(user1, dashboard.ID, gqlmodel.InputNamedDateRange{
Name: "new range",
Editable: false,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2",
},
})
require.EqualError(t, err, "range to (now-2) invalid: expected unit at the end but got nothing")
_, err = resolver.AddDashboardRange(user1, 100, gqlmodel.InputNamedDateRange{
Name: "new range",
Editable: false,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2",
},
})
require.EqualError(t, err, "dashboard does not exist")
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.Dashboard{{
ID: 1,
Name: "cool dashboard",
Ranges: []*gqlmodel.NamedDateRange{{
ID: 1,
Name: "new range",
Editable: false,
Range: &gqlmodel.RelativeOrStaticRange{
From: "now-1d",
To: "now-2d",
},
}},
Items: []*gqlmodel.DashboardEntry{},
}}, dashboards)
_, err = resolver.UpdateDashboardRange(user2, 1, gqlmodel.InputNamedDateRange{
Name: "my range",
Editable: true,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2d/w",
},
})
require.EqualError(t, err, "dashboard does not exist")
xrange, err = resolver.UpdateDashboardRange(user1, 1, gqlmodel.InputNamedDateRange{
Name: "my range",
Editable: true,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2d/w",
},
})
require.Equal(t, &gqlmodel.NamedDateRange{
ID: 1,
Name: "my range",
Editable: true,
Range: &gqlmodel.RelativeOrStaticRange{
From: "now-1d",
To: "now-2d/w",
},
}, xrange)
_, err = resolver.UpdateDashboardRange(user1, 1, gqlmodel.InputNamedDateRange{
Name: "my range",
Editable: true,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2",
},
})
require.EqualError(t, err, "range to (now-2) invalid: expected unit at the end but got nothing")
_, err = resolver.UpdateDashboardRange(user1, 44, gqlmodel.InputNamedDateRange{
Name: "my range",
Editable: true,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2",
},
})
require.EqualError(t, err, "dashboard range does not exist")
entry, err := resolver.AddDashboardEntry(user1, 1, gqlmodel.EntryTypeBarChart, "other", true, gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
RangeID: &xrange.ID,
}, &gqlmodel.InputResponsiveDashboardEntryPos{Desktop: &gqlmodel.InputDashboardEntryPos{
H: 1,
W: 2,
X: 1,
Y: 3,
}})
require.NoError(t, err)
_, err = resolver.RemoveDashboardRange(user1, xrange.ID)
require.EqualError(t, err, "range is used in entries: other")
_, err = resolver.RemoveDashboardEntry(user1, entry.ID)
require.NoError(t, err)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.Dashboard{{
ID: 1,
Name: "cool dashboard",
Ranges: []*gqlmodel.NamedDateRange{{
ID: 1,
Name: "my range",
Editable: true,
Range: &gqlmodel.RelativeOrStaticRange{
From: "now-1d",
To: "now-2d/w",
},
}},
Items: []*gqlmodel.DashboardEntry{},
}}, dashboards)
_, err = resolver.RemoveDashboardRange(user2, 1)
require.EqualError(t, err, "dashboard does not exist")
_, err = resolver.RemoveDashboardRange(user2, 55)
require.EqualError(t, err, "dashboard range does not exist")
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Len(t, dashboards[0].Ranges, 1)
_, err = resolver.RemoveDashboardRange(user1, 1)
require.NoError(t, err)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Len(t, dashboards[0].Ranges, 0)
}

View File

@@ -0,0 +1,43 @@
package dbrange
import (
"context"
"errors"
"strings"
"github.com/traggo/server/auth"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/dashboard/util"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// RemoveDashboardRange removes a dashboard range.
func (r *ResolverForRange) RemoveDashboardRange(ctx context.Context, rangeID int) (*gqlmodel.NamedDateRange, error) {
userID := auth.GetUser(ctx).ID
dashboardRange, err := util.FindDashboardRange(r.DB, rangeID)
if err != nil {
return nil, err
}
if _, err := util.FindDashboard(r.DB, userID, dashboardRange.DashboardID); err != nil {
return nil, err
}
entries := []model.DashboardEntry{}
if err := r.DB.Where(model.DashboardEntry{RangeID: rangeID}).Find(&entries).Error; err != nil {
return nil, err
}
names := []string{}
for _, entry := range entries {
names = append(names, entry.Title)
}
if len(entries) != 0 {
return nil, errors.New("range is used in entries: " + strings.Join(names, ","))
}
remove := r.DB.Delete(&model.DashboardRange{}, rangeID)
return convert.ToExternalDashboardRange(dashboardRange), remove.Error
}

View File

@@ -0,0 +1,35 @@
package dbrange
import (
"context"
"github.com/traggo/server/auth"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/dashboard/util"
"github.com/traggo/server/generated/gqlmodel"
)
// UpdateDashboardRange updates a dashboard range.
func (r *ResolverForRange) UpdateDashboardRange(ctx context.Context, rangeID int, rangeArg gqlmodel.InputNamedDateRange) (*gqlmodel.NamedDateRange, error) {
userID := auth.GetUser(ctx).ID
dashboardRange, err := util.FindDashboardRange(r.DB, rangeID)
if err != nil {
return nil, err
}
if _, err := util.FindDashboard(r.DB, userID, dashboardRange.DashboardID); err != nil {
return nil, err
}
rangeToUpdate, err := convert.ToInternalDashboardRange(rangeArg)
if err != nil {
return nil, err
}
rangeToUpdate.DashboardID = dashboardRange.DashboardID
rangeToUpdate.ID = dashboardRange.ID
save := r.DB.Save(rangeToUpdate)
return convert.ToExternalDashboardRange(rangeToUpdate), save.Error
}

77
dashboard/entry/add.go Normal file
View File

@@ -0,0 +1,77 @@
package entry
import (
"context"
"errors"
"fmt"
"strings"
"github.com/traggo/server/time"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/dashboard/util"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// AddDashboardEntry adds a dashboard entry.
func (r *ResolverForEntry) AddDashboardEntry(ctx context.Context, dashboardID int, entryType gqlmodel.EntryType, title string, total bool, stats gqlmodel.InputStatsSelection, pos *gqlmodel.InputResponsiveDashboardEntryPos) (*gqlmodel.DashboardEntry, error) {
userID := auth.GetUser(ctx).ID
if _, err := util.FindDashboard(r.DB, userID, dashboardID); err != nil {
return nil, err
}
tagFilters := convert.TagFiltersToInternal(stats.ExcludeTags, false)
tagFilters = append(tagFilters, convert.TagFiltersToInternal(stats.IncludeTags, true)...)
if err := tagsDuplicates(tagFilters); err != nil {
return nil, err
}
entry := model.DashboardEntry{
Keys: strings.Join(stats.Tags, ","),
Type: convert.InternalEntryType(entryType),
Title: title,
Total: total,
DashboardID: dashboardID,
Interval: convert.InternalInterval(stats.Interval),
MobilePosition: convert.EmptyPos(),
DesktopPosition: convert.EmptyPos(),
RangeID: -1,
TagFilters: tagFilters,
}
if len(stats.Tags) == 0 {
return nil, errors.New("at least one tag is required")
}
if err := convert.ApplyPos(&entry, pos); err != nil {
return &gqlmodel.DashboardEntry{}, err
}
if stats.RangeID != nil {
if _, err := util.FindDashboardRange(r.DB, *stats.RangeID); err != nil {
return nil, err
}
entry.RangeID = *stats.RangeID
} else if stats.Range != nil {
entry.RangeID = model.NoRangeIDDefined
if err := time.Validate(stats.Range.From); err != nil {
return nil, fmt.Errorf("range from (%s) invalid: %s", stats.Range.From, err)
}
if err := time.Validate(stats.Range.To); err != nil {
return nil, fmt.Errorf("range to (%s) invalid: %s", stats.Range.To, err)
}
entry.RangeFrom = stats.Range.From
entry.RangeTo = stats.Range.To
}
if err := r.DB.Save(&entry).Error; err != nil {
return nil, err
}
return convert.ToExternalEntry(entry)
}

View File

@@ -0,0 +1,8 @@
package entry
import "github.com/jinzhu/gorm"
// ResolverForEntry resolves dashboard entry things.
type ResolverForEntry struct {
DB *gorm.DB
}

View File

@@ -0,0 +1,526 @@
package entry_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/traggo/server/dashboard"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/test"
"github.com/traggo/server/test/fake"
)
func TestEntries(t *testing.T) {
db := test.InMemoryDB(t)
defer db.Close()
var bVal bool
resolver := dashboard.NewResolverForDashboard(db.DB)
// initial no dashboards
user1 := fake.User(1)
user2 := fake.User(2)
db.User(1)
db.User(2)
dashboards, err := resolver.Dashboards(user1)
require.NoError(t, err)
require.Empty(t, dashboards)
dashboard, err := resolver.CreateDashboard(user1, "cool dashboard")
require.NoError(t, err)
expectAdded := &gqlmodel.Dashboard{
ID: 1,
Name: "cool dashboard",
Ranges: []*gqlmodel.NamedDateRange{},
Items: []*gqlmodel.DashboardEntry{},
}
require.Equal(t, expectAdded, dashboard)
_, err = resolver.AddDashboardEntry(user1, 5, gqlmodel.EntryTypeBarChart, "test", false, gqlmodel.InputStatsSelection{
Interval: "",
Tags: []string{"hhol"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2d",
},
}, nil)
require.EqualError(t, err, "dashboard does not exist")
_, err = resolver.AddDashboardEntry(user1, 1, gqlmodel.EntryTypeBarChart, "test", false, gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalHourly,
Tags: []string{"hhol"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: p(55),
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now-2d",
},
}, nil)
require.EqualError(t, err, "dashboard range does not exist")
_, err = resolver.AddDashboardEntry(user2, 1, gqlmodel.EntryTypeBarChart, "test", false, gqlmodel.InputStatsSelection{
Interval: "doubly",
Tags: []string{"hhol"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "",
To: "",
},
}, nil)
require.EqualError(t, err, "dashboard does not exist")
_, err = resolver.AddDashboardEntry(user1, 1, gqlmodel.EntryTypeBarChart, "test", false, gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"hhol"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-2d",
To: "now-2",
},
}, nil)
require.EqualError(t, err, "range to (now-2) invalid: expected unit at the end but got nothing")
_, err = resolver.AddDashboardEntry(user1, 1, gqlmodel.EntryTypeBarChart, "test", false, gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"hhol"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-2",
To: "now-2d",
},
}, nil)
require.EqualError(t, err, "range from (now-2) invalid: expected unit at the end but got nothing")
_, err = resolver.AddDashboardEntry(user1, 1, gqlmodel.EntryTypeBarChart, "test", false, gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-2d",
To: "now-2d",
},
}, nil)
require.EqualError(t, err, "at least one tag is required")
entry, err := resolver.AddDashboardEntry(user1, 1, gqlmodel.EntryTypeBarChart, "test", false, gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-2d",
To: "now-5d",
},
}, &gqlmodel.InputResponsiveDashboardEntryPos{Desktop: &gqlmodel.InputDashboardEntryPos{
H: 1,
W: 2,
X: 1,
Y: 3,
}})
require.NoError(t, err)
expectedEntry := &gqlmodel.DashboardEntry{
ID: 1,
Title: "test",
Total: false,
StatsSelection: &gqlmodel.StatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.RelativeOrStaticRange{
From: "now-2d",
To: "now-5d",
},
},
Pos: &gqlmodel.ResponsiveDashboardEntryPos{
Desktop: &gqlmodel.DashboardEntryPos{
W: 2,
H: 3,
X: 1,
Y: 3,
MinW: 2,
MinH: 3,
},
Mobile: &gqlmodel.DashboardEntryPos{
W: 1,
H: 2,
X: 0,
Y: 0,
MinW: 1,
MinH: 2,
},
},
EntryType: gqlmodel.EntryTypeBarChart,
}
require.Equal(t, expectedEntry, entry)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, dashboards[0].Items, []*gqlmodel.DashboardEntry{expectedEntry})
xrange, err := resolver.AddDashboardRange(user1, 1, gqlmodel.InputNamedDateRange{
Name: "range",
Editable: false,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-1d",
To: "now",
},
})
require.NoError(t, err)
_, err = resolver.AddDashboardEntry(user1, 1, gqlmodel.EntryTypeBarChart, "other", false, gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
RangeID: p(xrange.ID),
}, &gqlmodel.InputResponsiveDashboardEntryPos{Desktop: &gqlmodel.InputDashboardEntryPos{
H: 1,
W: 2,
X: 1,
Y: 3,
}})
require.NoError(t, err)
_, err = resolver.RemoveDashboardRange(user1, xrange.ID)
require.EqualError(t, err, "range is used in entries: other")
chart := gqlmodel.EntryTypePieChart
_, err = resolver.UpdateDashboardEntry(user2, 1, &chart, nil, nil, nil, nil)
require.EqualError(t, err, "dashboard does not exist")
_, err = resolver.UpdateDashboardEntry(user1, 3, &chart, nil, nil, nil, nil)
require.EqualError(t, err, "entry does not exist")
_, err = resolver.UpdateDashboardEntry(user1, 1, &chart, s("cool title"), &bVal, &gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"kek"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-2",
To: "now-5d",
},
}, nil)
require.EqualError(t, err, "range from (now-2) invalid: expected unit at the end but got nothing")
_, err = resolver.UpdateDashboardEntry(user1, 1, &chart, s("cool title"), &bVal, &gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"kek"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-2d",
To: "now-5",
},
}, nil)
require.EqualError(t, err, "range to (now-5) invalid: expected unit at the end but got nothing")
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.DashboardEntry{
{
ID: 1,
Title: "test",
Total: false,
StatsSelection: &gqlmodel.StatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.RelativeOrStaticRange{
From: "now-2d",
To: "now-5d",
},
},
Pos: &gqlmodel.ResponsiveDashboardEntryPos{
Desktop: &gqlmodel.DashboardEntryPos{
W: 2,
H: 3,
X: 1,
Y: 3,
MinW: 2,
MinH: 3,
},
Mobile: &gqlmodel.DashboardEntryPos{
W: 1,
H: 2,
X: 0,
Y: 0,
MinW: 1,
MinH: 2,
},
},
EntryType: gqlmodel.EntryTypeBarChart,
},
{
ID: 2,
Title: "other",
Total: false,
StatsSelection: &gqlmodel.StatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: p(1),
},
Pos: &gqlmodel.ResponsiveDashboardEntryPos{
Desktop: &gqlmodel.DashboardEntryPos{
W: 2,
H: 3,
X: 1,
Y: 3,
MinW: 2,
MinH: 3,
},
Mobile: &gqlmodel.DashboardEntryPos{
W: 1,
H: 2,
X: 0,
Y: 0,
MinW: 1,
MinH: 2,
},
},
EntryType: gqlmodel.EntryTypeBarChart,
},
}, dashboards[0].Items)
_, err = resolver.UpdateDashboardEntry(user1, 1, &chart, s("cool title"), &bVal, &gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"kek"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.InputRelativeOrStaticRange{
From: "now-2d",
To: "now-12d",
},
}, &gqlmodel.InputResponsiveDashboardEntryPos{Desktop: &gqlmodel.InputDashboardEntryPos{
H: 1,
W: 2,
X: 1,
Y: 3,
}})
require.NoError(t, err)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.DashboardEntry{
{
ID: 1,
Title: "cool title",
Total: false,
StatsSelection: &gqlmodel.StatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"kek"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: nil,
Range: &gqlmodel.RelativeOrStaticRange{
From: "now-2d",
To: "now-12d",
},
},
Pos: &gqlmodel.ResponsiveDashboardEntryPos{
Desktop: &gqlmodel.DashboardEntryPos{
W: 2,
H: 3,
X: 1,
Y: 3,
MinW: 2,
MinH: 3,
},
Mobile: &gqlmodel.DashboardEntryPos{
W: 1,
H: 2,
X: 0,
Y: 0,
MinW: 1,
MinH: 2,
},
},
EntryType: gqlmodel.EntryTypePieChart,
},
{
ID: 2,
Title: "other",
Total: false,
StatsSelection: &gqlmodel.StatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: p(1),
},
Pos: &gqlmodel.ResponsiveDashboardEntryPos{
Desktop: &gqlmodel.DashboardEntryPos{
W: 2,
H: 3,
X: 1,
Y: 3,
MinW: 2,
MinH: 3,
},
Mobile: &gqlmodel.DashboardEntryPos{
W: 1,
H: 2,
X: 0,
Y: 0,
MinW: 1,
MinH: 2,
},
},
EntryType: gqlmodel.EntryTypeBarChart,
},
}, dashboards[0].Items)
_, err = resolver.UpdateDashboardEntry(user1, 1, &chart, s("cool title"), &bVal, &gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"kek"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: p(5),
}, &gqlmodel.InputResponsiveDashboardEntryPos{Desktop: &gqlmodel.InputDashboardEntryPos{
H: 1,
W: 2,
X: 1,
Y: 3,
}})
require.EqualError(t, err, "dashboard range does not exist")
_, err = resolver.UpdateDashboardEntry(user1, 1, &chart, s("cool title"), &bVal, &gqlmodel.InputStatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"kek"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: p(xrange.ID),
}, &gqlmodel.InputResponsiveDashboardEntryPos{Desktop: &gqlmodel.InputDashboardEntryPos{
H: 1,
W: 2,
X: 1,
Y: 3,
}})
require.NoError(t, err)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.DashboardEntry{
{
ID: 1,
Title: "cool title",
Total: false,
StatsSelection: &gqlmodel.StatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"kek"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: p(xrange.ID),
},
Pos: &gqlmodel.ResponsiveDashboardEntryPos{
Desktop: &gqlmodel.DashboardEntryPos{
W: 2,
H: 3,
X: 1,
Y: 3,
MinW: 2,
MinH: 3,
},
Mobile: &gqlmodel.DashboardEntryPos{
W: 1,
H: 2,
X: 0,
Y: 0,
MinW: 1,
MinH: 2,
},
},
EntryType: gqlmodel.EntryTypePieChart,
},
{
ID: 2,
Title: "other",
Total: false,
StatsSelection: &gqlmodel.StatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: p(1),
},
Pos: &gqlmodel.ResponsiveDashboardEntryPos{
Desktop: &gqlmodel.DashboardEntryPos{
W: 2,
H: 3,
X: 1,
Y: 3,
MinW: 2,
MinH: 3,
},
Mobile: &gqlmodel.DashboardEntryPos{
W: 1,
H: 2,
X: 0,
Y: 0,
MinW: 1,
MinH: 2,
},
},
EntryType: gqlmodel.EntryTypeBarChart,
},
}, dashboards[0].Items)
_, err = resolver.RemoveDashboardEntry(user2, 1)
require.EqualError(t, err, "dashboard does not exist")
_, err = resolver.RemoveDashboardEntry(user1, 55)
require.EqualError(t, err, "entry does not exist")
_, err = resolver.RemoveDashboardEntry(user1, 1)
require.NoError(t, err)
dashboards, err = resolver.Dashboards(user1)
require.NoError(t, err)
require.Equal(t, []*gqlmodel.DashboardEntry{
{
ID: 2,
Title: "other",
Total: false,
StatsSelection: &gqlmodel.StatsSelection{
Interval: gqlmodel.StatsIntervalDaily,
Tags: []string{"abc"},
ExcludeTags: nil,
IncludeTags: nil,
RangeID: p(1),
},
Pos: &gqlmodel.ResponsiveDashboardEntryPos{
Desktop: &gqlmodel.DashboardEntryPos{
W: 2,
H: 3,
X: 1,
Y: 3,
MinW: 2,
MinH: 3,
},
Mobile: &gqlmodel.DashboardEntryPos{
W: 1,
H: 2,
X: 0,
Y: 0,
MinW: 1,
MinH: 2,
},
},
EntryType: gqlmodel.EntryTypeBarChart,
},
}, dashboards[0].Items)
}
func p(i int) *int {
return &i
}
func s(s string) *string {
return &s
}

34
dashboard/entry/remove.go Normal file
View File

@@ -0,0 +1,34 @@
package entry
import (
"context"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/dashboard/util"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// RemoveDashboardEntry removes a dashboard entry.
func (r *ResolverForEntry) RemoveDashboardEntry(ctx context.Context, id int) (*gqlmodel.DashboardEntry, error) {
userID := auth.GetUser(ctx).ID
entry, err := util.FindDashboardEntry(r.DB, id)
if err != nil {
return nil, err
}
if _, err := util.FindDashboard(r.DB, userID, entry.DashboardID); err != nil {
return nil, err
}
remove := r.DB.Delete(&model.DashboardEntry{}, id)
if remove.Error != nil {
return &gqlmodel.DashboardEntry{}, remove.Error
}
return convert.ToExternalEntry(entry)
}

View File

@@ -0,0 +1,33 @@
package entry
import (
"fmt"
"github.com/traggo/server/model"
)
func tagsDuplicates(tags []model.DashboardTagFilter) error {
existingTags := make(map[model.DashboardTagFilter]struct{})
for _, tag := range tags {
if _, ok := existingTags[tag]; ok {
tagType := "exclude"
if tag.Include {
tagType = "include"
}
return fmt.Errorf("%s tags: tag '%s' is present multiple times", tagType, tag.Key+":"+tag.StringValue)
} else {
copyTag := tag
copyTag.Include = !copyTag.Include
if _, ok := existingTags[copyTag]; ok {
return fmt.Errorf("tag '%s' is present in both exclude tags and include tags", tag.Key+":"+tag.StringValue)
}
}
existingTags[tag] = struct{}{}
}
return nil
}

104
dashboard/entry/update.go Normal file
View File

@@ -0,0 +1,104 @@
package entry
import (
"context"
"fmt"
"strings"
"github.com/traggo/server/time"
"github.com/traggo/server/model"
"github.com/traggo/server/auth"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/dashboard/util"
"github.com/traggo/server/generated/gqlmodel"
)
// UpdateDashboardEntry updates a dashboard entry.
func (r *ResolverForEntry) UpdateDashboardEntry(ctx context.Context, id int, entryType *gqlmodel.EntryType, title *string, total *bool, stats *gqlmodel.InputStatsSelection, pos *gqlmodel.InputResponsiveDashboardEntryPos) (*gqlmodel.DashboardEntry, error) {
userID := auth.GetUser(ctx).ID
entry, err := util.FindDashboardEntry(r.DB, id)
if err != nil {
return nil, err
}
if _, err := util.FindDashboard(r.DB, userID, entry.DashboardID); err != nil {
return nil, err
}
if title != nil {
entry.Title = *title
}
if total != nil {
entry.Total = *total
}
tx := r.DB.Begin()
if err := tx.Error; err != nil {
return nil, err
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
} else if tx != nil {
tx.Rollback()
}
}()
if stats != nil {
if stats.RangeID != nil {
if _, err := util.FindDashboardRange(tx, *stats.RangeID); err != nil {
return nil, err
}
entry.RangeID = *stats.RangeID
} else if stats.Range != nil {
entry.RangeID = model.NoRangeIDDefined
if err := time.Validate(stats.Range.From); err != nil {
return nil, fmt.Errorf("range from (%s) invalid: %s", stats.Range.From, err)
}
if err := time.Validate(stats.Range.To); err != nil {
return nil, fmt.Errorf("range to (%s) invalid: %s", stats.Range.To, err)
}
entry.RangeFrom = stats.Range.From
entry.RangeTo = stats.Range.To
}
tagFilters := convert.TagFiltersToInternal(stats.ExcludeTags, false)
tagFilters = append(tagFilters, convert.TagFiltersToInternal(stats.IncludeTags, true)...)
if err := tagsDuplicates(tagFilters); err != nil {
return nil, err
}
if err := tx.Where("dashboard_entry_id = ?", id).Delete(new(model.DashboardTagFilter)).Error; err != nil {
return nil, fmt.Errorf("failed to update tag filters: %s", err)
}
entry.TagFilters = tagFilters
entry.Keys = strings.Join(stats.Tags, ",")
entry.Interval = convert.InternalInterval(stats.Interval)
}
if entryType != nil {
entry.Type = convert.InternalEntryType(*entryType)
}
if err := convert.ApplyPos(&entry, pos); err != nil {
return &gqlmodel.DashboardEntry{}, err
}
if err := tx.Save(entry).Error; err != nil {
return nil, err
}
if err := tx.Commit().Error; err != nil {
return nil, err
}
tx = nil
return convert.ToExternalEntry(entry)
}

31
dashboard/get.go Normal file
View File

@@ -0,0 +1,31 @@
package dashboard
import (
"context"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// Dashboards returns all dashboards.
func (r *ResolverForDashboard) Dashboards(ctx context.Context) ([]*gqlmodel.Dashboard, error) {
userID := auth.GetUser(ctx).ID
dashboards := []model.Dashboard{}
q := r.DB
q = q.Preload("Entries")
q = q.Preload("Entries.TagFilters")
q = q.Preload("Ranges")
find := q.Where(&model.Dashboard{UserID: userID}).Find(&dashboards)
if find.Error != nil {
return nil, find.Error
}
return convert.ToExternalDashboards(dashboards)
}

28
dashboard/remove.go Normal file
View File

@@ -0,0 +1,28 @@
package dashboard
import (
"context"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/dashboard/util"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// RemoveDashboard removes a dashboard.
func (r *ResolverForDashboard) RemoveDashboard(ctx context.Context, id int) (*gqlmodel.Dashboard, error) {
userID := auth.GetUser(ctx).ID
dashboard, err := util.FindDashboard(r.DB, userID, id)
if err != nil {
return nil, err
}
if err := r.DB.Where(&model.Dashboard{UserID: userID, ID: id}).Delete(&model.Dashboard{}).Error; err != nil {
return &gqlmodel.Dashboard{}, err
}
return convert.ToExternalDashboard(dashboard)
}

45
dashboard/update.go Normal file
View File

@@ -0,0 +1,45 @@
package dashboard
import (
"context"
"github.com/traggo/server/dashboard/convert"
"github.com/traggo/server/dashboard/util"
"github.com/traggo/server/auth"
"github.com/traggo/server/generated/gqlmodel"
"github.com/traggo/server/model"
)
// UpdateDashboard updates a dashboard.
func (r *ResolverForDashboard) UpdateDashboard(ctx context.Context, id int, name string) (*gqlmodel.Dashboard, error) {
userID := auth.GetUser(ctx).ID
dashboard, err := util.FindDashboard(r.DB, userID, id)
if err != nil {
return nil, err
}
dashboard.Name = name
save := r.DB.Save(dashboard)
if save.Error != nil {
return nil, save.Error
}
var entries []model.DashboardEntry
if err := r.DB.Where(&model.DashboardEntry{DashboardID: dashboard.ID}).Find(&entries).Error; err != nil {
return nil, err
}
dashboard.Entries = entries
var ranges []model.DashboardRange
if err := r.DB.Where(&model.DashboardRange{DashboardID: dashboard.ID}).Find(&ranges).Error; err != nil {
return nil, err
}
dashboard.Ranges = ranges
return convert.ToExternalDashboard(dashboard)
}

View File

@@ -0,0 +1,41 @@
package util
import (
"errors"
"github.com/jinzhu/gorm"
"github.com/traggo/server/model"
)
// FindDashboard finds a dashboard.
func FindDashboard(db *gorm.DB, userID, dashboardID int) (model.Dashboard, error) {
dashboard := model.Dashboard{}
find := db.Where(&model.Dashboard{UserID: userID, ID: dashboardID}).Find(&dashboard)
if find.RecordNotFound() {
return dashboard, errors.New("dashboard does not exist")
}
return dashboard, find.Error
}
// FindDashboardRange finds a dashboard range.
func FindDashboardRange(db *gorm.DB, rangeID int) (model.DashboardRange, error) {
dashboardRange := model.DashboardRange{}
find := db.Where(&model.DashboardRange{ID: rangeID}).Find(&dashboardRange)
if find.RecordNotFound() {
return dashboardRange, errors.New("dashboard range does not exist")
}
return dashboardRange, find.Error
}
// FindDashboardEntry finds a dashboard entry.
func FindDashboardEntry(db *gorm.DB, entryID int) (model.DashboardEntry, error) {
dashboardEntry := model.DashboardEntry{}
find := db.Where(&model.DashboardEntry{ID: entryID}).Find(&dashboardEntry)
if find.RecordNotFound() {
return dashboardEntry, errors.New("entry does not exist")
}
return dashboardEntry, find.Error
}