Merge pull request #18002 from influxdata/chore/update-flux

chore: update flux to latest revision
pull/18100/head
jlapacik 2020-05-07 11:06:34 -07:00 committed by GitHub
commit 8bf2d34bd7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1073 additions and 373 deletions

View File

@ -3,7 +3,8 @@
### Features
1. [17934](https://github.com/influxdata/influxdb/pull/17934): Add ability to delete a stack and all the resources associated with it
1. [17941](https://github.com/influxdata/influxdb/pull/17941): Encorce dns name compliance on all pkger resources' metadata.name field
1. [17941](https://github.com/influxdata/influxdb/pull/17941): Enforce DNS name compliance on all pkger resources' metadata.name field
1. [17989](https://github.com/influxdata/influxdb/pull/17989): Add stateful pkg management with stacks
### Bug Fixes
@ -31,6 +32,7 @@
1. [17714](https://github.com/influxdata/influxdb/pull/17714): Cloud environments no longer render markdown images, for security reasons.
1. [17321](https://github.com/influxdata/influxdb/pull/17321): Improve UI for sorting resources
1. [17740](https://github.com/influxdata/influxdb/pull/17740): Add single-color color schemes for visualizations
1. [17849](https://github.com/influxdata/influxdb/pull/17849): Move Organization navigation items to user menu.
## v2.0.0-beta.8 [2020-04-10]

View File

@ -7,6 +7,7 @@ import (
"io/ioutil"
nethttp "net/http"
"net/http/httptest"
"sort"
"testing"
"time"
@ -591,7 +592,7 @@ func TestLauncher_Pkger(t *testing.T) {
})
})
t.Run("apply a pkg with a stack", func(t *testing.T) {
t.Run("apply a pkg with a stack and all resources", func(t *testing.T) {
testStackApplyFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) {
t.Helper()
@ -1038,6 +1039,365 @@ func TestLauncher_Pkger(t *testing.T) {
}
})
})
t.Run("apply should handle cases where users have changed platform data", func(t *testing.T) {
initializeStackPkg := func(t *testing.T, pkg *pkger.Pkg) (influxdb.ID, func(), pkger.Summary) {
t.Helper()
stack, cleanup := newStackFn(t, pkger.Stack{})
defer func() {
if t.Failed() {
cleanup()
}
}()
initialSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stack.ID))
require.NoError(t, err)
return stack.ID, cleanup, initialSum
}
testValidRemoval := func(t *testing.T, stackID influxdb.ID) {
t.Helper()
_, _, err := svc.Apply(
ctx,
l.Org.ID,
l.User.ID,
newPkg( /* empty stack to remove prev resource */ ),
pkger.ApplyWithStackID(stackID),
)
require.NoError(t, err)
}
t.Run("when a user has deleted a variable that was previously created by a stack", func(t *testing.T) {
testUserDeletedVariable := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialVarObj pkger.Object, initialSum pkger.Summary)) {
t.Helper()
obj := newVariableObject("var-1", "", "")
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(obj))
defer cleanup()
require.Len(t, initialSum.Variables, 1)
require.NotZero(t, initialSum.Variables[0].ID)
resourceCheck.mustDeleteVariable(t, influxdb.ID(initialSum.Variables[0].ID))
actionFn(t, stackID, obj, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedVariable(t, func(t *testing.T, stackID influxdb.ID, initialVarObj pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialVarObj)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.Variables, 1)
initVar, updateVar := initialSum.Variables[0], updateSum.Variables[0]
assert.NotEqual(t, initVar.ID, updateVar.ID)
initVar.ID, updateVar.ID = 0, 0
assert.Equal(t, initVar, updateVar)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedVariable(t, func(t *testing.T, stackID influxdb.ID, initialVarObj pkger.Object, initialSum pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
t.Run("when a user has deleted a bucket that was previously created by a stack", func(t *testing.T) {
testUserDeletedBucket := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
t.Helper()
obj := newBucketObject("bucket-1", "", "")
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(obj))
defer cleanup()
require.Len(t, initialSum.Buckets, 1)
require.NotZero(t, initialSum.Buckets[0].ID)
resourceCheck.mustDeleteBucket(t, influxdb.ID(initialSum.Buckets[0].ID))
actionFn(t, stackID, obj, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedBucket(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialObj)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.Buckets, 1)
intial, updated := initialSum.Buckets[0], updateSum.Buckets[0]
assert.NotEqual(t, intial.ID, updated.ID)
intial.ID, updated.ID = 0, 0
assert.Equal(t, intial, updated)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedBucket(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
t.Run("when a user has deleted a check that was previously created by a stack", func(t *testing.T) {
testUserDeletedCheck := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
t.Helper()
obj := newCheckDeadmanObject(t, "check-1", "", time.Hour)
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(obj))
defer cleanup()
require.Len(t, initialSum.Checks, 1)
require.NotZero(t, initialSum.Checks[0].Check.GetID())
resourceCheck.mustDeleteCheck(t, initialSum.Checks[0].Check.GetID())
actionFn(t, stackID, obj, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedCheck(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialObj)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.Checks, 1)
intial, updated := initialSum.Checks[0].Check, updateSum.Checks[0].Check
assert.NotEqual(t, intial.GetID(), updated.GetID())
intial.SetID(0)
updated.SetID(0)
assert.Equal(t, intial, updated)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedCheck(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
t.Run("when a user has deleted a dashboard that was previously created by a stack", func(t *testing.T) {
testUserDeletedDashboard := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
t.Helper()
obj := newDashObject("dash-1", "", "")
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(obj))
defer cleanup()
require.Len(t, initialSum.Dashboards, 1)
require.NotZero(t, initialSum.Dashboards[0].ID)
resourceCheck.mustDeleteDashboard(t, influxdb.ID(initialSum.Dashboards[0].ID))
actionFn(t, stackID, obj, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedDashboard(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialObj)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.Dashboards, 1)
initial, updated := initialSum.Dashboards[0], updateSum.Dashboards[0]
assert.NotEqual(t, initial.ID, updated.ID)
initial.ID, updated.ID = 0, 0
assert.Equal(t, initial, updated)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedDashboard(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
t.Run("when a user has deleted a label that was previously created by a stack", func(t *testing.T) {
testUserDeletedLabel := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
t.Helper()
obj := newLabelObject("label-1", "", "", "")
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(obj))
defer cleanup()
require.Len(t, initialSum.Labels, 1)
require.NotZero(t, initialSum.Labels[0].ID)
resourceCheck.mustDeleteLabel(t, influxdb.ID(initialSum.Labels[0].ID))
actionFn(t, stackID, obj, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedLabel(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialObj)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.Labels, 1)
initial, updated := initialSum.Labels[0], updateSum.Labels[0]
assert.NotEqual(t, initial.ID, updated.ID)
initial.ID, updated.ID = 0, 0
assert.Equal(t, initial, updated)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedLabel(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
t.Run("when a user has deleted a notification endpoint that was previously created by a stack", func(t *testing.T) {
testUserDeletedEndpoint := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
t.Helper()
obj := newEndpointHTTP("endpoint-1", "", "")
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(obj))
defer cleanup()
require.Len(t, initialSum.NotificationEndpoints, 1)
require.NotZero(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID())
resourceCheck.mustDeleteEndpoint(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID())
actionFn(t, stackID, obj, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedEndpoint(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialObj)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.NotificationEndpoints, 1)
initial, updated := initialSum.NotificationEndpoints[0].NotificationEndpoint, updateSum.NotificationEndpoints[0].NotificationEndpoint
assert.NotEqual(t, initial.GetID(), updated.GetID())
initial.SetID(0)
updated.SetID(0)
assert.Equal(t, initial, updated)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedEndpoint(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
t.Run("when a user has deleted a notification rule that was previously created by a stack", func(t *testing.T) {
testUserDeletedRule := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialObjects []pkger.Object, initialSum pkger.Summary)) {
t.Helper()
endpointObj := newEndpointHTTP("endpoint-1", "", "")
ruleObj := newRuleObject(t, "rule-0", "", endpointObj.Name(), "")
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(endpointObj, ruleObj))
defer cleanup()
require.Len(t, initialSum.NotificationEndpoints, 1)
require.NotZero(t, initialSum.NotificationEndpoints[0].NotificationEndpoint.GetID())
require.Len(t, initialSum.NotificationRules, 1)
require.NotZero(t, initialSum.NotificationRules[0].ID)
resourceCheck.mustDeleteRule(t, influxdb.ID(initialSum.NotificationRules[0].ID))
actionFn(t, stackID, []pkger.Object{ruleObj, endpointObj}, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedRule(t, func(t *testing.T, stackID influxdb.ID, initialObjects []pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialObjects...)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.NotificationRules, 1)
initial, updated := initialSum.NotificationRules[0], updateSum.NotificationRules[0]
assert.NotEqual(t, initial.ID, updated.ID)
initial.ID, updated.ID = 0, 0
assert.Equal(t, initial, updated)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedRule(t, func(t *testing.T, stackID influxdb.ID, _ []pkger.Object, _ pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
t.Run("when a user has deleted a task that was previously created by a stack", func(t *testing.T) {
testUserDeletedTask := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
t.Helper()
obj := newTaskObject("task-1", "", "")
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(obj))
defer cleanup()
require.Len(t, initialSum.Tasks, 1)
require.NotZero(t, initialSum.Tasks[0].ID)
resourceCheck.mustDeleteTask(t, influxdb.ID(initialSum.Tasks[0].ID))
actionFn(t, stackID, obj, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedTask(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialObj)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.Tasks, 1)
initial, updated := initialSum.Tasks[0], updateSum.Tasks[0]
assert.NotEqual(t, initial.ID, updated.ID)
initial.ID, updated.ID = 0, 0
assert.Equal(t, initial, updated)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedTask(t, func(t *testing.T, stackID influxdb.ID, _ pkger.Object, _ pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
t.Run("when a user has deleted a telegraf config that was previously created by a stack", func(t *testing.T) {
testUserDeletedTelegraf := func(t *testing.T, actionFn func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary)) {
t.Helper()
obj := newTelegrafObject("tele-1", "", "")
stackID, cleanup, initialSum := initializeStackPkg(t, newPkg(obj))
defer cleanup()
require.Len(t, initialSum.TelegrafConfigs, 1)
require.NotZero(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID)
resourceCheck.mustDeleteTelegrafConfig(t, initialSum.TelegrafConfigs[0].TelegrafConfig.ID)
actionFn(t, stackID, obj, initialSum)
}
t.Run("should create new resource when attempting to update", func(t *testing.T) {
testUserDeletedTelegraf(t, func(t *testing.T, stackID influxdb.ID, initialObj pkger.Object, initialSum pkger.Summary) {
pkg := newPkg(initialObj)
updateSum, _, err := svc.Apply(ctx, l.Org.ID, l.User.ID, pkg, pkger.ApplyWithStackID(stackID))
require.NoError(t, err)
require.Len(t, updateSum.TelegrafConfigs, 1)
initial, updated := initialSum.TelegrafConfigs[0].TelegrafConfig, updateSum.TelegrafConfigs[0].TelegrafConfig
assert.NotEqual(t, initial.ID, updated.ID)
initial.ID, updated.ID = 0, 0
assert.Equal(t, initial, updated)
})
})
t.Run("should not error when attempting to remove", func(t *testing.T) {
testUserDeletedTelegraf(t, func(t *testing.T, stackID influxdb.ID, _ pkger.Object, _ pkger.Summary) {
testValidRemoval(t, stackID)
})
})
})
})
})
t.Run("errors incurred during application of package rolls back to state before package", func(t *testing.T) {
@ -1386,6 +1746,7 @@ spec:
labels := sum.Labels
require.Len(t, labels, 2)
sortLabels(labels)
assert.Equal(t, "label-1", labels[0].Name)
assert.Equal(t, "the 2nd label", labels[1].Name)
@ -1397,6 +1758,7 @@ spec:
checks := sum.Checks
require.Len(t, checks, 2)
sortChecks(checks)
assert.Equal(t, "check 0 name", checks[0].Check.GetName())
hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1")
assert.Equal(t, "check-1", checks[1].Check.GetName())
@ -1691,6 +2053,7 @@ spec:
labels := newSum.Labels
require.Len(t, labels, 2)
sortLabels(labels)
assert.Zero(t, labels[0].ID)
assert.Equal(t, "label-1", labels[0].Name)
assert.Zero(t, labels[1].ID)
@ -1704,6 +2067,7 @@ spec:
checks := newSum.Checks
require.Len(t, checks, 2)
sortChecks(checks)
assert.Equal(t, "check 0 name", checks[0].Check.GetName())
hasLabelAssociations(t, checks[0].LabelAssociations, 1, "label-1")
assert.Equal(t, "check-1", checks[1].Check.GetName())
@ -2438,6 +2802,11 @@ func (r resourceChecker) mustGetBucket(t *testing.T, getOpt getResourceOptFn) in
return bkt
}
func (r resourceChecker) mustDeleteBucket(t *testing.T, id influxdb.ID) {
t.Helper()
require.NoError(t, r.tl.BucketService(t).DeleteBucket(ctx, id))
}
func (r resourceChecker) getCheck(t *testing.T, getOpt getResourceOptFn) (influxdb.Check, error) {
t.Helper()
@ -2470,6 +2839,12 @@ func (r resourceChecker) mustGetCheck(t *testing.T, getOpt getResourceOptFn) inf
return c
}
func (r resourceChecker) mustDeleteCheck(t *testing.T, id influxdb.ID) {
t.Helper()
require.NoError(t, r.tl.CheckService().DeleteCheck(ctx, id))
}
func (r resourceChecker) getDashboard(t *testing.T, getOpt getResourceOptFn) (influxdb.Dashboard, error) {
t.Helper()
@ -2515,6 +2890,12 @@ func (r resourceChecker) mustGetDashboard(t *testing.T, getOpt getResourceOptFn)
return dash
}
func (r resourceChecker) mustDeleteDashboard(t *testing.T, id influxdb.ID) {
t.Helper()
require.NoError(t, r.tl.DashboardService(t).DeleteDashboard(ctx, id))
}
func (r resourceChecker) getEndpoint(t *testing.T, getOpt getResourceOptFn) (influxdb.NotificationEndpoint, error) {
t.Helper()
@ -2557,6 +2938,13 @@ func (r resourceChecker) mustGetEndpoint(t *testing.T, getOpt getResourceOptFn)
return e
}
func (r resourceChecker) mustDeleteEndpoint(t *testing.T, id influxdb.ID) {
t.Helper()
_, _, err := r.tl.NotificationEndpointService(t).DeleteNotificationEndpoint(ctx, id)
require.NoError(t, err)
}
func (r resourceChecker) getLabel(t *testing.T, getOpt getResourceOptFn) (influxdb.Label, error) {
t.Helper()
@ -2603,6 +2991,11 @@ func (r resourceChecker) mustGetLabel(t *testing.T, getOpt getResourceOptFn) inf
return l
}
func (r resourceChecker) mustDeleteLabel(t *testing.T, id influxdb.ID) {
t.Helper()
require.NoError(t, r.tl.LabelService(t).DeleteLabel(ctx, id))
}
func (r resourceChecker) getRule(t *testing.T, getOpt getResourceOptFn) (influxdb.NotificationRule, error) {
t.Helper()
@ -2645,6 +3038,12 @@ func (r resourceChecker) mustGetRule(t *testing.T, getOpt getResourceOptFn) infl
return rule
}
func (r resourceChecker) mustDeleteRule(t *testing.T, id influxdb.ID) {
t.Helper()
require.NoError(t, r.tl.NotificationRuleService().DeleteNotificationRule(ctx, id))
}
func (r resourceChecker) getTask(t *testing.T, getOpt getResourceOptFn) (http.Task, error) {
t.Helper()
@ -2689,6 +3088,12 @@ func (r resourceChecker) mustGetTask(t *testing.T, getOpt getResourceOptFn) http
return task
}
func (r resourceChecker) mustDeleteTask(t *testing.T, id influxdb.ID) {
t.Helper()
require.NoError(t, r.tl.TaskService(t).DeleteTask(ctx, id))
}
func (r resourceChecker) getTelegrafConfig(t *testing.T, getOpt getResourceOptFn) (influxdb.TelegrafConfig, error) {
t.Helper()
@ -2729,6 +3134,12 @@ func (r resourceChecker) mustGetTelegrafConfig(t *testing.T, getOpt getResourceO
return tele
}
func (r resourceChecker) mustDeleteTelegrafConfig(t *testing.T, id influxdb.ID) {
t.Helper()
require.NoError(t, r.tl.TelegrafService(t).DeleteTelegrafConfig(ctx, id))
}
func (r resourceChecker) getVariable(t *testing.T, getOpt getResourceOptFn) (influxdb.Variable, error) {
t.Helper()
@ -2774,3 +3185,22 @@ func (r resourceChecker) mustGetVariable(t *testing.T, getOpt getResourceOptFn)
require.NoError(t, err)
return l
}
func (r resourceChecker) mustDeleteVariable(t *testing.T, id influxdb.ID) {
t.Helper()
err := r.tl.VariableService(t).DeleteVariable(ctx, id)
require.NoError(t, err)
}
func sortChecks(checks []pkger.SummaryCheck) {
sort.Slice(checks, func(i, j int) bool {
return checks[i].Check.GetName() < checks[j].Check.GetName()
})
}
func sortLabels(labels []pkger.SummaryLabel) {
sort.Slice(labels, func(i, j int) bool {
return labels[i].Name < labels[j].Name
})
}

2
go.mod
View File

@ -42,7 +42,7 @@ require (
github.com/hashicorp/raft v1.0.0 // indirect
github.com/hashicorp/vault/api v1.0.2
github.com/influxdata/cron v0.0.0-20191203200038-ded12750aac6
github.com/influxdata/flux v0.67.1-0.20200506164116-7432bbda91d7
github.com/influxdata/flux v0.67.1-0.20200507153142-7a0c6ca988e1
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69
github.com/influxdata/influxql v0.0.0-20180925231337-1cbfca8e56b6
github.com/influxdata/pkg-config v0.2.0

4
go.sum
View File

@ -246,8 +246,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/cron v0.0.0-20191203200038-ded12750aac6 h1:OtjKkeWDjUbyMi82C7XXy7Tvm2LXMwiBBXyFIGNPaGA=
github.com/influxdata/cron v0.0.0-20191203200038-ded12750aac6/go.mod h1:XabtPPW2qsCg0tl+kjaPU+cFS+CjQXEXbT1VJvHT4og=
github.com/influxdata/flux v0.67.1-0.20200506164116-7432bbda91d7 h1:9bRTK6KToiAp4UP2cLm06NznCOdMvtnIUSwxgpaDw2s=
github.com/influxdata/flux v0.67.1-0.20200506164116-7432bbda91d7/go.mod h1:AdzL5HnjdFlcBiNz0wE69rSTGRX9CQHqtJUF8ptiDeY=
github.com/influxdata/flux v0.67.1-0.20200507153142-7a0c6ca988e1 h1:tT0L4qNrdcSnfAB4Srb/J4W9m5vZ14VUf22STppqZrc=
github.com/influxdata/flux v0.67.1-0.20200507153142-7a0c6ca988e1/go.mod h1:AdzL5HnjdFlcBiNz0wE69rSTGRX9CQHqtJUF8ptiDeY=
github.com/influxdata/goreleaser v0.97.0-influx h1:jT5OrcW7WfS0e2QxfwmTBjhLvpIC9CDLRhNgZJyhj8s=
github.com/influxdata/goreleaser v0.97.0-influx/go.mod h1:MnjA0e0Uq6ISqjG1WxxMAl+3VS1QYjILSWVnMYDxasE=
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69 h1:WQsmW0fXO4ZE/lFGIE84G6rIV5SJN3P3sjIXAP1a8eU=

View File

@ -530,7 +530,7 @@ func (p *Pkg) buckets() []*bucket {
buckets = append(buckets, b)
}
sort.Slice(buckets, func(i, j int) bool { return buckets[i].Name() < buckets[j].Name() })
sort.Slice(buckets, func(i, j int) bool { return buckets[i].PkgName() < buckets[j].PkgName() })
return buckets
}
@ -541,7 +541,7 @@ func (p *Pkg) checks() []*check {
checks = append(checks, c)
}
sort.Slice(checks, func(i, j int) bool { return checks[i].Name() < checks[j].Name() })
sort.Slice(checks, func(i, j int) bool { return checks[i].PkgName() < checks[j].PkgName() })
return checks
}
@ -574,7 +574,7 @@ func (p *Pkg) notificationEndpoints() []*notificationEndpoint {
sort.Slice(endpoints, func(i, j int) bool {
ei, ej := endpoints[i], endpoints[j]
if ei.kind == ej.kind {
return ei.Name() < ej.Name()
return ei.PkgName() < ej.PkgName()
}
return ei.kind < ej.kind
})
@ -586,7 +586,7 @@ func (p *Pkg) notificationRules() []*notificationRule {
for _, r := range p.mNotificationRules {
rules = append(rules, r)
}
sort.Slice(rules, func(i, j int) bool { return rules[i].Name() < rules[j].Name() })
sort.Slice(rules, func(i, j int) bool { return rules[i].PkgName() < rules[j].PkgName() })
return rules
}
@ -618,7 +618,7 @@ func (p *Pkg) tasks() []*task {
tasks = append(tasks, t)
}
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Name() < tasks[j].Name() })
sort.Slice(tasks, func(i, j int) bool { return tasks[i].PkgName() < tasks[j].PkgName() })
return tasks
}
@ -630,7 +630,7 @@ func (p *Pkg) telegrafs() []*telegraf {
teles = append(teles, t)
}
sort.Slice(teles, func(i, j int) bool { return teles[i].Name() < teles[j].Name() })
sort.Slice(teles, func(i, j int) bool { return teles[i].PkgName() < teles[j].PkgName() })
return teles
}
@ -641,7 +641,7 @@ func (p *Pkg) variables() []*variable {
vars = append(vars, v)
}
sort.Slice(vars, func(i, j int) bool { return vars[i].Name() < vars[j].Name() })
sort.Slice(vars, func(i, j int) bool { return vars[i].PkgName() < vars[j].PkgName() })
return vars
}

View File

@ -1199,7 +1199,7 @@ func (s sortedLabels) Len() int {
}
func (s sortedLabels) Less(i, j int) bool {
return s[i].Name() < s[j].Name()
return s[i].PkgName() < s[j].PkgName()
}
func (s sortedLabels) Swap(i, j int) {

View File

@ -27,19 +27,19 @@ func TestParse(t *testing.T) {
actual := buckets[0]
expectedBucket := SummaryBucket{
PkgName: "rucket-22",
Name: "display name",
Description: "bucket 2 description",
PkgName: "rucket-11",
Name: "rucket-11",
Description: "bucket 1 description",
RetentionPeriod: time.Hour,
LabelAssociations: []SummaryLabel{},
}
assert.Equal(t, expectedBucket, actual)
actual = buckets[1]
expectedBucket = SummaryBucket{
PkgName: "rucket-11",
Name: "rucket-11",
Description: "bucket 1 description",
RetentionPeriod: time.Hour,
PkgName: "rucket-22",
Name: "display name",
Description: "bucket 2 description",
LabelAssociations: []SummaryLabel{},
}
assert.Equal(t, expectedBucket, actual)
@ -160,19 +160,7 @@ spec:
labels := pkg.Summary().Labels
require.Len(t, labels, 3)
expectedLabel0 := SummaryLabel{
PkgName: "label-3",
Name: "display name",
Properties: struct {
Color string `json:"color"`
Description string `json:"description"`
}{
Description: "label 3 description",
},
}
assert.Equal(t, expectedLabel0, labels[0])
expectedLabel1 := SummaryLabel{
expectedLabel := SummaryLabel{
PkgName: "label-1",
Name: "label-1",
Properties: struct {
@ -183,9 +171,9 @@ spec:
Description: "label 1 description",
},
}
assert.Equal(t, expectedLabel1, labels[1])
assert.Equal(t, expectedLabel, labels[0])
expectedLabel2 := SummaryLabel{
expectedLabel = SummaryLabel{
PkgName: "label-2",
Name: "label-2",
Properties: struct {
@ -196,7 +184,19 @@ spec:
Description: "label 2 description",
},
}
assert.Equal(t, expectedLabel2, labels[2])
assert.Equal(t, expectedLabel, labels[1])
expectedLabel = SummaryLabel{
PkgName: "label-3",
Name: "display name",
Properties: struct {
Color string `json:"color"`
Description string `json:"description"`
}{
Description: "label 3 description",
},
}
assert.Equal(t, expectedLabel, labels[2])
})
})
@ -3669,6 +3669,21 @@ spec:
assert.Equal(t, vals, v.Arguments.Values)
}
// validates we support all known variable types
varEquals(t,
"var-const-3",
"constant",
influxdb.VariableConstantValues([]string{"first val"}),
sum.Variables[0],
)
varEquals(t,
"var-map-4",
"map",
influxdb.VariableMapValues{"k1": "v1"},
sum.Variables[1],
)
varEquals(t,
"query var",
"query",
@ -3676,21 +3691,6 @@ spec:
Query: `buckets() |> filter(fn: (r) => r.name !~ /^_/) |> rename(columns: {name: "_value"}) |> keep(columns: ["_value"])`,
Language: "flux",
},
sum.Variables[0],
)
// validates we support all known variable types
varEquals(t,
"var-const-3",
"constant",
influxdb.VariableConstantValues([]string{"first val"}),
sum.Variables[1],
)
varEquals(t,
"var-map-4",
"map",
influxdb.VariableMapValues{"k1": "v1"},
sum.Variables[2],
)

View File

@ -12,6 +12,7 @@ import (
"github.com/influxdata/influxdb/v2"
ierrors "github.com/influxdata/influxdb/v2/kit/errors"
icheck "github.com/influxdata/influxdb/v2/notification/check"
"github.com/influxdata/influxdb/v2/notification/rule"
"github.com/influxdata/influxdb/v2/snowflake"
"github.com/influxdata/influxdb/v2/task/options"
@ -957,11 +958,9 @@ func (s *Service) dryRunVariables(ctx context.Context, orgID influxdb.ID, vars m
}
for _, v := range vars {
var existing *influxdb.Variable
existing := mNames[v.parserVar.Name()]
if v.ID() != 0 {
existing = mIDs[v.ID()]
} else {
existing = mNames[v.parserVar.Name()]
}
if IsNew(v.stateStatus) && existing != nil {
v.stateStatus = StateStatusExists
@ -1095,8 +1094,9 @@ func (s *Service) dryRunResourceLabelMapping(ctx context.Context, state *stateCo
ResourceID: ident.id,
ResourceType: ident.resourceType,
})
if err != nil {
return nil, err
if err != nil && influxdb.ErrorCode(err) != influxdb.ENotFound {
msgFmt := fmt.Sprintf("failed to find labels mappings for %s resource[%q]", ident.resourceType, ident.id)
return nil, ierrors.Wrap(err, msgFmt)
}
pkgLabels := labelSlcToMap(pkgResourceLabels)
@ -1235,7 +1235,7 @@ func (s *Service) Apply(ctx context.Context, orgID, userID influxdb.ID, pkg *Pkg
func (s *Service) applyState(ctx context.Context, coordinator *rollbackCoordinator, orgID, userID influxdb.ID, state *stateCoordinator, missingSecrets map[string]string) (e error) {
endpointApp, ruleApp, err := s.applyNotificationGenerator(ctx, userID, state.rules(), state.endpoints())
if err != nil {
return err
return ierrors.Wrap(err, "failed to setup notification generator")
}
// each grouping here runs for its entirety, then returns an error that
@ -1340,15 +1340,18 @@ func (s *Service) applyBuckets(ctx context.Context, buckets []*stateBucket) appl
func (s *Service) rollbackBuckets(ctx context.Context, buckets []*stateBucket) error {
rollbackFn := func(b *stateBucket) error {
if !IsNew(b.stateStatus) && b.existing == nil {
return nil
}
var err error
switch b.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(b.stateStatus):
err = ierrors.Wrap(s.bucketSVC.CreateBucket(ctx, b.existing), "rolling back removed bucket")
case StateStatusExists:
rp := b.parserBkt.RetentionRules.RP()
case IsExisting(b.stateStatus):
_, err = s.bucketSVC.UpdateBucket(ctx, b.ID(), influxdb.BucketUpdate{
Description: &b.parserBkt.Description,
RetentionPeriod: &rp,
Description: &b.existing.Description,
RetentionPeriod: &b.existing.RetentionPeriod,
})
err = ierrors.Wrap(err, "rolling back existing bucket to previous state")
default:
@ -1373,13 +1376,16 @@ func (s *Service) rollbackBuckets(ctx context.Context, buckets []*stateBucket) e
}
func (s *Service) applyBucket(ctx context.Context, b *stateBucket) (influxdb.Bucket, error) {
switch b.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(b.stateStatus):
if err := s.bucketSVC.DeleteBucket(ctx, b.ID()); err != nil {
if influxdb.ErrorCode(err) == influxdb.ENotFound {
return influxdb.Bucket{}, nil
}
return influxdb.Bucket{}, fmt.Errorf("failed to delete bucket[%q]: %w", b.ID(), err)
}
return *b.existing, nil
case StateStatusExists:
case IsExisting(b.stateStatus) && b.existing != nil:
rp := b.parserBkt.RetentionRules.RP()
newName := b.parserBkt.Name()
influxBucket, err := s.bucketSVC.UpdateBucket(ctx, b.ID(), influxdb.BucketUpdate{
@ -1451,8 +1457,8 @@ func (s *Service) applyChecks(ctx context.Context, checks []*stateCheck) applier
func (s *Service) rollbackChecks(ctx context.Context, checks []*stateCheck) error {
rollbackFn := func(c *stateCheck) error {
var err error
switch c.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(c.stateStatus):
err = s.checkSVC.CreateCheck(
ctx,
influxdb.CheckCreate{
@ -1462,13 +1468,16 @@ func (s *Service) rollbackChecks(ctx context.Context, checks []*stateCheck) erro
c.existing.GetOwnerID(),
)
c.id = c.existing.GetID()
case StateStatusNew:
err = s.checkSVC.DeleteCheck(ctx, c.ID())
default:
case IsExisting(c.stateStatus):
if c.existing == nil {
return nil
}
_, err = s.checkSVC.UpdateCheck(ctx, c.ID(), influxdb.CheckCreate{
Check: c.summarize().Check,
Status: influxdb.Status(c.parserCheck.status),
})
default:
err = s.checkSVC.DeleteCheck(ctx, c.ID())
}
return err
}
@ -1488,13 +1497,16 @@ func (s *Service) rollbackChecks(ctx context.Context, checks []*stateCheck) erro
}
func (s *Service) applyCheck(ctx context.Context, c *stateCheck, userID influxdb.ID) (influxdb.Check, error) {
switch c.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(c.stateStatus):
if err := s.checkSVC.DeleteCheck(ctx, c.ID()); err != nil {
if influxdb.ErrorCode(err) == influxdb.ENotFound {
return &icheck.Threshold{Base: icheck.Base{ID: c.ID()}}, nil
}
return nil, fmt.Errorf("failed to delete check[%q]: %w", c.ID(), err)
}
return c.existing, nil
case StateStatusExists:
case IsExisting(c.stateStatus) && c.existing != nil:
influxCheck, err := s.checkSVC.UpdateCheck(ctx, c.ID(), influxdb.CheckCreate{
Check: c.summarize().Check,
Status: c.parserCheck.Status(),
@ -1559,13 +1571,16 @@ func (s *Service) applyDashboards(ctx context.Context, dashboards []*stateDashbo
}
func (s *Service) applyDashboard(ctx context.Context, d *stateDashboard) (influxdb.Dashboard, error) {
switch d.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(d.stateStatus):
if err := s.dashSVC.DeleteDashboard(ctx, d.ID()); err != nil {
if influxdb.ErrorCode(err) == influxdb.ENotFound {
return influxdb.Dashboard{}, nil
}
return influxdb.Dashboard{}, fmt.Errorf("failed to delete dashboard[%q]: %w", d.ID(), err)
}
return *d.existing, nil
case StateStatusExists:
case IsExisting(d.stateStatus) && d.existing != nil:
name := d.parserDash.Name()
cells := convertChartsToCells(d.parserDash.Charts)
dash, err := s.dashSVC.UpdateDashboard(ctx, d.ID(), influxdb.DashboardUpdate{
@ -1595,11 +1610,15 @@ func (s *Service) applyDashboard(ctx context.Context, d *stateDashboard) (influx
func (s *Service) rollbackDashboards(ctx context.Context, dashs []*stateDashboard) error {
rollbackFn := func(d *stateDashboard) error {
if !IsNew(d.stateStatus) && d.existing == nil {
return nil
}
var err error
switch d.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(d.stateStatus):
err = ierrors.Wrap(s.dashSVC.CreateDashboard(ctx, d.existing), "rolling back removed dashboard")
case StateStatusExists:
case IsExisting(d.stateStatus):
_, err := s.dashSVC.UpdateDashboard(ctx, d.ID(), influxdb.DashboardUpdate{
Name: &d.existing.Name,
Description: &d.existing.Description,
@ -1693,11 +1712,15 @@ func (s *Service) applyLabels(ctx context.Context, labels []*stateLabel) applier
func (s *Service) rollbackLabels(ctx context.Context, labels []*stateLabel) error {
rollbackFn := func(l *stateLabel) error {
if !IsNew(l.stateStatus) && l.existing == nil {
return nil
}
var err error
switch l.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(l.stateStatus):
err = s.labelSVC.CreateLabel(ctx, l.existing)
case StateStatusExists:
case IsExisting(l.stateStatus):
_, err = s.labelSVC.UpdateLabel(ctx, l.ID(), influxdb.LabelUpdate{
Name: l.parserLabel.Name(),
Properties: l.existing.Properties,
@ -1727,10 +1750,10 @@ func (s *Service) applyLabel(ctx context.Context, l *stateLabel) (influxdb.Label
influxLabel *influxdb.Label
err error
)
switch l.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(l.stateStatus):
influxLabel, err = l.existing, s.labelSVC.DeleteLabel(ctx, l.ID())
case StateStatusExists:
case IsExisting(l.stateStatus) && l.existing != nil:
influxLabel, err = s.labelSVC.UpdateLabel(ctx, l.ID(), influxdb.LabelUpdate{
Name: l.parserLabel.Name(),
Properties: l.properties(),
@ -1741,6 +1764,9 @@ func (s *Service) applyLabel(ctx context.Context, l *stateLabel) (influxdb.Label
influxLabel = &creatLabel
err = ierrors.Wrap(s.labelSVC.CreateLabel(ctx, &creatLabel), "creating")
}
if influxdb.ErrorCode(err) == influxdb.ENotFound {
return influxdb.Label{}, nil
}
if err != nil || influxLabel == nil {
return influxdb.Label{}, err
}
@ -1768,29 +1794,31 @@ func (s *Service) applyNotificationEndpoints(ctx context.Context, userID influxd
}
mutex.Do(func() {
endpoints[i].id = influxEndpoint.GetID()
for _, secret := range influxEndpoint.SecretFields() {
switch {
case strings.HasSuffix(secret.Key, "-routing-key"):
if endpoints[i].parserEndpoint.routingKey == nil {
endpoints[i].parserEndpoint.routingKey = new(references)
if influxEndpoint != nil {
endpoints[i].id = influxEndpoint.GetID()
for _, secret := range influxEndpoint.SecretFields() {
switch {
case strings.HasSuffix(secret.Key, "-routing-key"):
if endpoints[i].parserEndpoint.routingKey == nil {
endpoints[i].parserEndpoint.routingKey = new(references)
}
endpoints[i].parserEndpoint.routingKey.Secret = secret.Key
case strings.HasSuffix(secret.Key, "-token"):
if endpoints[i].parserEndpoint.token == nil {
endpoints[i].parserEndpoint.token = new(references)
}
endpoints[i].parserEndpoint.token.Secret = secret.Key
case strings.HasSuffix(secret.Key, "-username"):
if endpoints[i].parserEndpoint.username == nil {
endpoints[i].parserEndpoint.username = new(references)
}
endpoints[i].parserEndpoint.username.Secret = secret.Key
case strings.HasSuffix(secret.Key, "-password"):
if endpoints[i].parserEndpoint.password == nil {
endpoints[i].parserEndpoint.password = new(references)
}
endpoints[i].parserEndpoint.password.Secret = secret.Key
}
endpoints[i].parserEndpoint.routingKey.Secret = secret.Key
case strings.HasSuffix(secret.Key, "-token"):
if endpoints[i].parserEndpoint.token == nil {
endpoints[i].parserEndpoint.token = new(references)
}
endpoints[i].parserEndpoint.token.Secret = secret.Key
case strings.HasSuffix(secret.Key, "-username"):
if endpoints[i].parserEndpoint.username == nil {
endpoints[i].parserEndpoint.username = new(references)
}
endpoints[i].parserEndpoint.username.Secret = secret.Key
case strings.HasSuffix(secret.Key, "-password"):
if endpoints[i].parserEndpoint.password == nil {
endpoints[i].parserEndpoint.password = new(references)
}
endpoints[i].parserEndpoint.password.Secret = secret.Key
}
}
rollbackEndpoints = append(rollbackEndpoints, endpoints[i])
@ -1817,14 +1845,14 @@ func (s *Service) applyNotificationEndpoints(ctx context.Context, userID influxd
}
func (s *Service) applyNotificationEndpoint(ctx context.Context, e *stateEndpoint, userID influxdb.ID) (influxdb.NotificationEndpoint, error) {
switch e.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(e.stateStatus):
_, _, err := s.endpointSVC.DeleteNotificationEndpoint(ctx, e.ID())
if err != nil {
if err != nil && influxdb.ErrorCode(err) != influxdb.ENotFound {
return nil, err
}
return e.existing, nil
case StateStatusExists:
case IsExisting(e.stateStatus) && e.existing != nil:
// stub out userID since we're always using hte http client which will fill it in for us with the token
// feels a bit broken that is required.
// TODO: look into this userID requirement
@ -1847,6 +1875,9 @@ func (s *Service) applyNotificationEndpoint(ctx context.Context, e *stateEndpoin
func (s *Service) rollbackNotificationEndpoints(ctx context.Context, userID influxdb.ID, endpoints []*stateEndpoint) error {
rollbackFn := func(e *stateEndpoint) error {
if !IsNew(e.stateStatus) && e.existing == nil {
return nil
}
var err error
switch e.stateStatus {
case StateStatusRemove:
@ -1942,7 +1973,9 @@ func (s *Service) applyNotificationRules(ctx context.Context, userID influxdb.ID
}
mutex.Do(func() {
rules[i].id = influxRule.GetID()
if influxRule != nil {
rules[i].id = influxRule.GetID()
}
rollbackEndpoints = append(rollbackEndpoints, rules[i])
})
@ -1965,13 +1998,16 @@ func (s *Service) applyNotificationRules(ctx context.Context, userID influxdb.ID
}
func (s *Service) applyNotificationRule(ctx context.Context, r *stateRule, userID influxdb.ID) (influxdb.NotificationRule, error) {
switch r.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(r.stateStatus):
if err := s.ruleSVC.DeleteNotificationRule(ctx, r.ID()); err != nil {
if influxdb.ErrorCode(err) == influxdb.ENotFound {
return nil, nil
}
return nil, ierrors.Wrap(err, "failed to remove notification rule")
}
return r.existing, nil
case StateStatusExists:
case IsExisting(r.stateStatus) && r.existing != nil:
ruleCreate := influxdb.NotificationRuleCreate{
NotificationRule: r.toInfluxRule(),
Status: r.parserRule.Status(),
@ -1996,6 +2032,10 @@ func (s *Service) applyNotificationRule(ctx context.Context, r *stateRule, userI
func (s *Service) rollbackNotificationRules(ctx context.Context, userID influxdb.ID, rules []*stateRule) error {
rollbackFn := func(r *stateRule) error {
if !IsNew(r.stateStatus) && r.existing == nil {
return nil
}
existingRuleFn := func(endpointID influxdb.ID) influxdb.NotificationRule {
switch rr := r.existing.(type) {
case *rule.HTTP:
@ -2143,13 +2183,16 @@ func (s *Service) applyTasks(ctx context.Context, tasks []*stateTask) applier {
}
func (s *Service) applyTask(ctx context.Context, userID influxdb.ID, t *stateTask) (influxdb.Task, error) {
switch t.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(t.stateStatus):
if err := s.taskSVC.DeleteTask(ctx, t.ID()); err != nil {
if influxdb.ErrorCode(err) == influxdb.ENotFound {
return influxdb.Task{}, nil
}
return influxdb.Task{}, ierrors.Wrap(err, "failed to delete task")
}
return *t.existing, nil
case StateStatusExists:
case IsExisting(t.stateStatus) && t.existing != nil:
newFlux := t.parserTask.flux()
newStatus := string(t.parserTask.Status())
opt := options.Options{
@ -2191,6 +2234,10 @@ func (s *Service) applyTask(ctx context.Context, userID influxdb.ID, t *stateTas
func (s *Service) rollbackTasks(ctx context.Context, tasks []*stateTask) error {
rollbackFn := func(t *stateTask) error {
if !IsNew(t.stateStatus) && t.existing == nil {
return nil
}
var err error
switch t.stateStatus {
case StateStatusRemove:
@ -2293,13 +2340,16 @@ func (s *Service) applyTelegrafs(ctx context.Context, userID influxdb.ID, teles
}
func (s *Service) applyTelegrafConfig(ctx context.Context, userID influxdb.ID, t *stateTelegraf) (influxdb.TelegrafConfig, error) {
switch t.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(t.stateStatus):
if err := s.teleSVC.DeleteTelegrafConfig(ctx, t.ID()); err != nil {
if influxdb.ErrorCode(err) == influxdb.ENotFound {
return influxdb.TelegrafConfig{}, nil
}
return influxdb.TelegrafConfig{}, ierrors.Wrap(err, "failed to delete config")
}
return *t.existing, nil
case StateStatusExists:
case IsExisting(t.stateStatus) && t.existing != nil:
cfg := t.summarize().TelegrafConfig
updatedConfig, err := s.teleSVC.UpdateTelegrafConfig(ctx, t.ID(), &cfg, userID)
if err != nil {
@ -2318,6 +2368,10 @@ func (s *Service) applyTelegrafConfig(ctx context.Context, userID influxdb.ID, t
func (s *Service) rollbackTelegrafConfigs(ctx context.Context, userID influxdb.ID, cfgs []*stateTelegraf) error {
rollbackFn := func(t *stateTelegraf) error {
if !IsNew(t.stateStatus) && t.existing == nil {
return nil
}
var err error
switch t.stateStatus {
case StateStatusRemove:
@ -2390,14 +2444,20 @@ func (s *Service) applyVariables(ctx context.Context, vars []*stateVariable) app
func (s *Service) rollbackVariables(ctx context.Context, variables []*stateVariable) error {
rollbackFn := func(v *stateVariable) error {
var err error
switch v.stateStatus {
case StateStatusRemove:
switch {
case IsRemoval(v.stateStatus):
if v.existing == nil {
return nil
}
err = ierrors.Wrap(s.varSVC.CreateVariable(ctx, v.existing), "rolling back removed variable")
case StateStatusExists:
case IsExisting(v.stateStatus):
if v.existing == nil {
return nil
}
_, err = s.varSVC.UpdateVariable(ctx, v.ID(), &influxdb.VariableUpdate{
Name: v.parserVar.Name(),
Description: v.parserVar.Description,
Arguments: v.parserVar.influxVarArgs(),
Name: v.existing.Name,
Description: v.existing.Description,
Arguments: v.existing.Arguments,
})
err = ierrors.Wrap(err, "rolling back updated variable")
default:
@ -2421,23 +2481,28 @@ func (s *Service) rollbackVariables(ctx context.Context, variables []*stateVaria
}
func (s *Service) applyVariable(ctx context.Context, v *stateVariable) (influxdb.Variable, error) {
switch v.stateStatus {
case StateStatusRemove:
if err := s.varSVC.DeleteVariable(ctx, v.id); err != nil {
return influxdb.Variable{}, err
switch {
case IsRemoval(v.stateStatus):
if err := s.varSVC.DeleteVariable(ctx, v.id); err != nil && influxdb.ErrorCode(err) != influxdb.ENotFound {
return influxdb.Variable{}, ierrors.Wrap(err, "removing existing variable")
}
if v.existing == nil {
return influxdb.Variable{}, nil
}
return *v.existing, nil
case StateStatusExists:
case IsExisting(v.stateStatus) && v.existing != nil:
updatedVar, err := s.varSVC.UpdateVariable(ctx, v.ID(), &influxdb.VariableUpdate{
Name: v.parserVar.Name(),
Description: v.parserVar.Description,
Arguments: v.parserVar.influxVarArgs(),
})
if err != nil {
return influxdb.Variable{}, err
return influxdb.Variable{}, ierrors.Wrap(err, "updating existing variable")
}
return *updatedVar, nil
default:
// when an existing variable (referenced in stack) has been deleted by a user
// then the resource is created anew to get it back to the expected state.
influxVar := influxdb.Variable{
OrganizationID: v.orgID,
Name: v.parserVar.Name(),
@ -2446,9 +2511,8 @@ func (s *Service) applyVariable(ctx context.Context, v *stateVariable) (influxdb
}
err := s.varSVC.CreateVariable(ctx, &influxVar)
if err != nil {
return influxdb.Variable{}, err
return influxdb.Variable{}, ierrors.Wrap(err, "creating new variable")
}
return influxVar, nil
}
}

View File

@ -7,6 +7,7 @@ import (
"fmt"
"math/rand"
"regexp"
"sort"
"strconv"
"testing"
"time"
@ -1482,6 +1483,12 @@ func TestService(t *testing.T) {
}
}
sortLabelsByName := func(labels []SummaryLabel) {
sort.Slice(labels, func(i, j int) bool {
return labels[i].Name < labels[j].Name
})
}
t.Run("with existing resources", func(t *testing.T) {
encodeAndDecode := func(t *testing.T, pkg *Pkg) *Pkg {
t.Helper()
@ -2659,6 +2666,9 @@ func TestService(t *testing.T) {
sum := newPkg.Summary()
teles := sum.TelegrafConfigs
sort.Slice(teles, func(i, j int) bool {
return teles[i].TelegrafConfig.Name < teles[j].TelegrafConfig.Name
})
require.Len(t, teles, len(resourcesToClone))
for i := range resourcesToClone {
@ -2853,9 +2863,13 @@ func TestService(t *testing.T) {
sum := newPkg.Summary()
bkts := sum.Buckets
sort.Slice(bkts, func(i, j int) bool {
return bkts[i].Name < bkts[j].Name
})
require.Len(t, bkts, 2)
for i, actual := range bkts {
sortLabelsByName(actual.LabelAssociations)
assert.Equal(t, strconv.Itoa((i+1)*10), actual.Name)
require.Len(t, actual.LabelAssociations, 2)
assert.Equal(t, "label_1", actual.LabelAssociations[0].Name)
@ -2863,6 +2877,7 @@ func TestService(t *testing.T) {
}
labels := sum.Labels
sortLabelsByName(labels)
require.Len(t, labels, 2)
assert.Equal(t, "label_1", labels[0].Name)
assert.Equal(t, "label_2", labels[1].Name)

View File

@ -12,7 +12,7 @@ require (
github.com/go-kit/kit v0.10.0 // indirect
github.com/google/go-cmp v0.4.0
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/influxdata/flux v0.67.1-0.20200506164116-7432bbda91d7
github.com/influxdata/flux v0.67.1-0.20200507153142-7a0c6ca988e1
github.com/influxdata/influxdb/v2 v2.0.0-00010101000000-000000000000
github.com/influxdata/influxql v1.0.1 // indirect
github.com/influxdata/promql/v2 v2.12.0

View File

@ -321,8 +321,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/cron v0.0.0-20191203200038-ded12750aac6 h1:OtjKkeWDjUbyMi82C7XXy7Tvm2LXMwiBBXyFIGNPaGA=
github.com/influxdata/cron v0.0.0-20191203200038-ded12750aac6/go.mod h1:XabtPPW2qsCg0tl+kjaPU+cFS+CjQXEXbT1VJvHT4og=
github.com/influxdata/flux v0.67.1-0.20200506164116-7432bbda91d7 h1:9bRTK6KToiAp4UP2cLm06NznCOdMvtnIUSwxgpaDw2s=
github.com/influxdata/flux v0.67.1-0.20200506164116-7432bbda91d7/go.mod h1:AdzL5HnjdFlcBiNz0wE69rSTGRX9CQHqtJUF8ptiDeY=
github.com/influxdata/flux v0.67.1-0.20200507153142-7a0c6ca988e1 h1:tT0L4qNrdcSnfAB4Srb/J4W9m5vZ14VUf22STppqZrc=
github.com/influxdata/flux v0.67.1-0.20200507153142-7a0c6ca988e1/go.mod h1:AdzL5HnjdFlcBiNz0wE69rSTGRX9CQHqtJUF8ptiDeY=
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69 h1:WQsmW0fXO4ZE/lFGIE84G6rIV5SJN3P3sjIXAP1a8eU=
github.com/influxdata/httprouter v1.3.1-0.20191122104820-ee83e2772f69/go.mod h1:pwymjR6SrP3gD3pRj9RJwdl1j5s3doEEV8gS4X9qSzA=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=

View File

@ -27,7 +27,8 @@ describe('Orgs', () => {
})
it('should be able to rename the org', () => {
const extraText = '_my_renamed_org_in_e2e'
cy.getByTestID('nav-item-org').click()
cy.getByTestID('user-nav').click()
cy.getByTestID('user-nav-item-about').click()
cy.get('span:contains("About")').click()
cy.get('span:contains("Rename")').click()
cy.get('button.cf-button.cf-button-danger').click()

View File

@ -34,6 +34,7 @@ import {
Dashboard,
} from 'src/types'
import {reportError} from 'src/shared/utils/errors'
import {getErrorMessage} from 'src/utils/api'
export type Actions =
| ReturnType<typeof setDemoDataStatus>
@ -124,7 +125,7 @@ export const getDemoDataBucketMembership = ({
dispatch(notify(demoDataSucceeded(bucketName, url)))
} catch (error) {
dispatch(notify(demoDataAddBucketFailed(error)))
dispatch(notify(demoDataAddBucketFailed(getErrorMessage(error))))
reportError(error, {
name: 'getDemoDataBucketMembership function',

View File

@ -0,0 +1,46 @@
// API
import {fetchOrgSettings} from 'src/cloud/apis/orgsettings'
// Constants
import {FREE_ORG_HIDE_UPGRADE_SETTING} from 'src/cloud/constants'
// Types
import {GetState, OrgSetting} from 'src/types'
// Selectors
import {getOrg} from 'src/organizations/selectors'
export const SET_ORG_SETTINGS = 'SET_ORG_SETTINGS'
export type Action = ReturnType<typeof setOrgSettings>
export const setOrgSettings = (settings: OrgSetting[] = []) =>
({
type: SET_ORG_SETTINGS,
payload: {orgSettings: settings},
} as const)
export const setFreeOrgSettings = () =>
({
type: SET_ORG_SETTINGS,
payload: {orgSettings: [FREE_ORG_HIDE_UPGRADE_SETTING]},
} as const)
export const getOrgSettings = () => async (dispatch, getState: GetState) => {
try {
const org = getOrg(getState())
const response = await fetchOrgSettings(org.id)
if (response.status !== 200) {
throw new Error(
`Unable to get organization settings: ${response.statusText}`
)
}
const result = await response.json()
dispatch(setOrgSettings(result.settings))
} catch (error) {
dispatch(setFreeOrgSettings())
console.error(error)
}
}

View File

@ -0,0 +1,7 @@
import {getAPIBasepath} from 'src/utils/basepath'
import {OrgSettingsResponse} from 'src/types'
export const fetchOrgSettings = async (
orgID: string
): Promise<OrgSettingsResponse> =>
await fetch(`${getAPIBasepath()}/api/v2private/orgs/${orgID}/settings`)

View File

@ -0,0 +1,34 @@
// Libraries
import {PureComponent} from 'react'
import {connect} from 'react-redux'
// Constants
import {CLOUD} from 'src/shared/constants'
// Actions
import {getOrgSettings as getOrgSettingsAction} from 'src/cloud/actions/orgsettings'
interface DispatchProps {
getOrgSettings: typeof getOrgSettingsAction
}
class OrgSettings extends PureComponent<DispatchProps> {
public componentDidMount() {
if (CLOUD) {
this.props.getOrgSettings()
}
}
public render() {
return this.props.children
}
}
const mdtp: DispatchProps = {
getOrgSettings: getOrgSettingsAction,
}
export default connect<{}, DispatchProps, {}>(
null,
mdtp
)(OrgSettings)

View File

@ -25,3 +25,13 @@ export const DemoDataTemplates = {
DemoDataDashboards[WebsiteMonitoringBucket]
),
}
export const HIDE_UPGRADE_CTA_KEY = 'hide_upgrade_cta'
export const FREE_ORG_HIDE_UPGRADE_SETTING = {
key: HIDE_UPGRADE_CTA_KEY,
value: 'false',
}
export const PAID_ORG_HIDE_UPGRADE_SETTING = {
key: HIDE_UPGRADE_CTA_KEY,
value: 'true',
}

View File

@ -0,0 +1,25 @@
import {produce} from 'immer'
import {Action, SET_ORG_SETTINGS} from 'src/cloud/actions/orgsettings'
import {OrgSetting} from 'src/types'
import {PAID_ORG_HIDE_UPGRADE_SETTING} from 'src/cloud/constants'
export interface OrgSettingsState {
settings: OrgSetting[]
}
export const defaultState: OrgSettingsState = {
settings: [PAID_ORG_HIDE_UPGRADE_SETTING],
}
export const orgSettingsReducer = (
state: OrgSettingsState = defaultState,
action: Action
): OrgSettingsState =>
produce(state, draftState => {
if (action.type === SET_ORG_SETTINGS) {
draftState.settings = action.payload.orgSettings
}
return
})

View File

@ -44,7 +44,7 @@ class OrgNavigation extends PureComponent<Props> {
link: `/orgs/${orgID}/members`,
},
{
text: 'Members',
text: 'Users',
id: 'members-quartz',
cloudOnly: true,
link: `${CLOUD_URL}/organizations/${orgID}${CLOUD_USERS_PATH}`,

View File

@ -16,6 +16,7 @@ import {
CLOUD_URL,
CLOUD_USAGE_PATH,
CLOUD_BILLING_PATH,
CLOUD_USERS_PATH,
} from 'src/shared/constants'
// Types
@ -24,6 +25,8 @@ import {MeState} from 'src/shared/reducers/me'
// Selectors
import {getOrg} from 'src/organizations/selectors'
import {getNavItemActivation} from '../utils'
import {FeatureFlag} from 'src/shared/utils/featureFlag'
interface StateProps {
org: Organization
@ -51,12 +54,15 @@ const UserWidget: FC<Props> = ({
handleShowOverlay('switch-organizations', {}, handleDismissOverlay)
}
const orgPrefix = `/orgs/${org.id}`
return (
<TreeNav.User username={me.name} team={org.name}>
<TreeNav.User username={me.name} team={org.name} testID="user-nav">
<CloudOnly>
<TreeNav.UserItem
id="usage"
label="Usage"
testID="user-nav-item-usage"
linkElement={className => (
<a className={className} href={`${CLOUD_URL}${CLOUD_USAGE_PATH}`} />
)}
@ -64,6 +70,7 @@ const UserWidget: FC<Props> = ({
<TreeNav.UserItem
id="billing"
label="Billing"
testID="user-nav-item-billing"
linkElement={className => (
<a
className={className}
@ -71,16 +78,57 @@ const UserWidget: FC<Props> = ({
/>
)}
/>
<FeatureFlag name="multiUser" equals={true}>
<TreeNav.UserItem
id="users"
label="Users"
testID="user-nav-item-users"
linkElement={className => (
<a
className={className}
href={`${CLOUD_URL}/organizations/${org.id}${CLOUD_USERS_PATH}`}
/>
)}
/>
</FeatureFlag>
<TreeNav.UserItem
id="about"
label="About"
testID="user-nav-item-about"
linkElement={className => (
<Link className={className} to={`${orgPrefix}/about`} />
)}
/>
</CloudOnly>
<CloudExclude>
<TreeNav.UserItem
id="members"
label="Members"
testID="user-nav-item-members"
active={getNavItemActivation(['members'], location.pathname)}
linkElement={className => (
<Link className={className} to={`${orgPrefix}/members`} />
)}
/>
<TreeNav.UserItem
id="about"
label="About"
testID="user-nav-item-about"
active={getNavItemActivation(['about'], location.pathname)}
linkElement={className => (
<Link className={className} to={`${orgPrefix}/about`} />
)}
/>
<TreeNav.UserItem
id="switch-orgs"
label="Switch Organizations"
testID="user-nav-item-switch-orgs"
onClick={handleSwitchOrganizations}
/>
<TreeNav.UserItem
id="create-org"
label="Create Organization"
testID="user-nav-item-create-orgs"
linkElement={className => (
<Link className={className} to="/orgs/new" />
)}
@ -89,6 +137,7 @@ const UserWidget: FC<Props> = ({
<TreeNav.UserItem
id="logout"
label="Logout"
testID="user-nav-item-logout"
linkElement={className => <Link className={className} to="/logout" />}
/>
</TreeNav.User>

View File

@ -1,6 +1,4 @@
import {IconFont} from '@influxdata/clockface'
import {CLOUD_URL, CLOUD_USERS_PATH} from 'src/shared/constants'
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
export interface NavItemLink {
type: 'link' | 'href'
@ -36,18 +34,6 @@ export interface NavItem {
export const generateNavItems = (orgID: string): NavItem[] => {
const orgPrefix = `/orgs/${orgID}`
const isMultiUserEnabled = isFlagEnabled('multiUser')
const quartzMembersHeaderLink: NavItemLink = isMultiUserEnabled
? {
type: 'href',
location: `${CLOUD_URL}/organizations/${orgID}${CLOUD_USERS_PATH}`,
}
: {
type: 'link',
location: `${orgPrefix}/about`,
}
return [
{
id: 'load-data',
@ -121,70 +107,6 @@ export const generateNavItems = (orgID: string): NavItem[] => {
},
activeKeywords: ['data-explorer'],
},
{
id: 'org',
testID: 'nav-item-org',
icon: IconFont.UsersTrio,
label: 'Organization',
shortLabel: 'Org',
link: {
type: 'link',
location: `${orgPrefix}/members`,
},
cloudExclude: true,
activeKeywords: ['members', 'about'],
menu: [
{
id: 'members',
testID: 'nav-subitem-members',
label: 'Members',
link: {
type: 'link',
location: `${orgPrefix}/members`,
},
},
{
id: 'about',
testID: 'nav-subitem-about',
label: 'About',
link: {
type: 'link',
location: `${orgPrefix}/about`,
},
},
],
},
{
id: 'org-quartz',
testID: 'nav-item-quartz-org',
icon: IconFont.UsersTrio,
label: 'Organization',
shortLabel: 'Org',
cloudOnly: true,
link: quartzMembersHeaderLink,
activeKeywords: ['members', 'about'],
menu: [
{
id: 'users',
testID: 'nav-subitem-users',
label: 'Members',
featureFlag: 'multiUser',
link: {
type: 'href',
location: `${CLOUD_URL}/organizations/${orgID}${CLOUD_USERS_PATH}`,
},
},
{
id: 'about',
testID: 'nav-subitem-about',
label: 'About',
link: {
type: 'link',
location: `${orgPrefix}/about`,
},
},
],
},
{
id: 'dashboards',
testID: 'nav-item-dashboards',

View File

@ -11,6 +11,7 @@ import NavHeader from 'src/pageLayout/components/NavHeader'
import CloudUpgradeNavBanner from 'src/shared/components/CloudUpgradeNavBanner'
import CloudExclude from 'src/shared/components/cloud/CloudExclude'
import CloudOnly from 'src/shared/components/cloud/CloudOnly'
import OrgSettings from 'src/cloud/components/OrgSettings'
import {FeatureFlag} from 'src/shared/utils/featureFlag'
// Constants
@ -67,132 +68,134 @@ class TreeSidebar extends PureComponent<Props> {
const navItems = generateNavItems(orgID)
return (
<TreeNav
expanded={isExpanded}
headerElement={<NavHeader link={orgPrefix} />}
userElement={<UserWidget />}
onToggleClick={handleToggleNavExpansion}
bannerElement={<CloudUpgradeNavBanner />}
>
{navItems.map(item => {
const linkElement = (className: string): JSX.Element => {
if (item.link.type === 'href') {
return <a href={item.link.location} className={className} />
}
<OrgSettings>
<TreeNav
expanded={isExpanded}
headerElement={<NavHeader link={orgPrefix} />}
userElement={<UserWidget />}
onToggleClick={handleToggleNavExpansion}
bannerElement={<CloudUpgradeNavBanner />}
>
{navItems.map(item => {
const linkElement = (className: string): JSX.Element => {
if (item.link.type === 'href') {
return <a href={item.link.location} className={className} />
}
return <Link to={item.link.location} className={className} />
}
let navItemElement = (
<TreeNav.Item
key={item.id}
id={item.id}
testID={item.testID}
icon={<Icon glyph={item.icon} />}
label={item.label}
shortLabel={item.shortLabel}
active={getNavItemActivation(
item.activeKeywords,
location.pathname
)}
linkElement={linkElement}
>
{Boolean(item.menu) && (
<TreeNav.SubMenu>
{item.menu.map(menuItem => {
const linkElement = (className: string): JSX.Element => {
if (menuItem.link.type === 'href') {
return (
<a
href={menuItem.link.location}
className={className}
/>
)
}
return <Link to={item.link.location} className={className} />
}
let navItemElement = (
<TreeNav.Item
key={item.id}
id={item.id}
testID={item.testID}
icon={<Icon glyph={item.icon} />}
label={item.label}
shortLabel={item.shortLabel}
active={getNavItemActivation(
item.activeKeywords,
location.pathname
)}
linkElement={linkElement}
>
{Boolean(item.menu) && (
<TreeNav.SubMenu>
{item.menu.map(menuItem => {
const linkElement = (className: string): JSX.Element => {
if (menuItem.link.type === 'href') {
return (
<a
href={menuItem.link.location}
<Link
to={menuItem.link.location}
className={className}
/>
)
}
return (
<Link
to={menuItem.link.location}
className={className}
let navSubItemElement = (
<TreeNav.SubItem
key={menuItem.id}
id={menuItem.id}
testID={menuItem.testID}
active={getNavItemActivation(
[menuItem.id],
location.pathname
)}
label={menuItem.label}
linkElement={linkElement}
/>
)
}
let navSubItemElement = (
<TreeNav.SubItem
key={menuItem.id}
id={menuItem.id}
testID={menuItem.testID}
active={getNavItemActivation(
[menuItem.id],
location.pathname
)}
label={menuItem.label}
linkElement={linkElement}
/>
)
if (menuItem.cloudExclude) {
navSubItemElement = (
<CloudExclude key={menuItem.id}>
{navSubItemElement}
</CloudExclude>
)
}
if (menuItem.cloudExclude) {
navSubItemElement = (
<CloudExclude key={menuItem.id}>
{navSubItemElement}
</CloudExclude>
)
}
if (menuItem.cloudOnly) {
navSubItemElement = (
<CloudOnly key={menuItem.id}>
{navSubItemElement}
</CloudOnly>
)
}
if (menuItem.cloudOnly) {
navSubItemElement = (
<CloudOnly key={menuItem.id}>
{navSubItemElement}
</CloudOnly>
)
}
if (menuItem.featureFlag) {
navSubItemElement = (
<FeatureFlag
key={menuItem.id}
name={menuItem.featureFlag}
equals={menuItem.featureFlagValue}
>
{navSubItemElement}
</FeatureFlag>
)
}
if (menuItem.featureFlag) {
navSubItemElement = (
<FeatureFlag
key={menuItem.id}
name={menuItem.featureFlag}
equals={menuItem.featureFlagValue}
>
{navSubItemElement}
</FeatureFlag>
)
}
return navSubItemElement
})}
</TreeNav.SubMenu>
)}
</TreeNav.Item>
)
if (item.cloudExclude) {
navItemElement = (
<CloudExclude key={item.id}>{navItemElement}</CloudExclude>
return navSubItemElement
})}
</TreeNav.SubMenu>
)}
</TreeNav.Item>
)
}
if (item.cloudOnly) {
navItemElement = (
<CloudOnly key={item.id}>{navItemElement}</CloudOnly>
)
}
if (item.cloudExclude) {
navItemElement = (
<CloudExclude key={item.id}>{navItemElement}</CloudExclude>
)
}
if (item.featureFlag) {
navItemElement = (
<FeatureFlag
key={item.id}
name={item.featureFlag}
equals={item.featureFlagValue}
>
{navItemElement}
</FeatureFlag>
)
}
if (item.cloudOnly) {
navItemElement = (
<CloudOnly key={item.id}>{navItemElement}</CloudOnly>
)
}
return navItemElement
})}
</TreeNav>
if (item.featureFlag) {
navItemElement = (
<FeatureFlag
key={item.id}
name={item.featureFlag}
equals={item.featureFlagValue}
>
{navItemElement}
</FeatureFlag>
)
}
return navItemElement
})}
</TreeNav>
</OrgSettings>
)
}
}

View File

@ -1,25 +1,56 @@
// Libraries
import React, {FC} from 'react'
import {Link} from 'react-router'
import {connect} from 'react-redux'
// Components
import CloudOnly from 'src/shared/components/cloud/CloudOnly'
// Constants
import {CLOUD_URL, CLOUD_CHECKOUT_PATH} from 'src/shared/constants'
import {
HIDE_UPGRADE_CTA_KEY,
PAID_ORG_HIDE_UPGRADE_SETTING,
} from 'src/cloud/constants'
const CloudUpgradeButton: FC = () => {
// Types
import {AppState, OrgSetting} from 'src/types'
interface StateProps {
inView: boolean
}
const CloudUpgradeButton: FC<StateProps> = ({inView}) => {
return (
<CloudOnly>
<Link
className="cf-button cf-button-sm cf-button-success upgrade-payg--button"
to={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
target="_self"
>
Upgrade Now
</Link>
{inView ? (
<Link
className="cf-button cf-button-sm cf-button-success upgrade-payg--button"
to={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
target="_self"
>
Upgrade Now
</Link>
) : null}
</CloudOnly>
)
}
export default CloudUpgradeButton
const mstp = ({
cloud: {
orgSettings: {settings},
},
}: AppState) => {
const hideUpgradeButtonSetting = settings.find(
(setting: OrgSetting) => setting.key === HIDE_UPGRADE_CTA_KEY
)
if (
!hideUpgradeButtonSetting ||
hideUpgradeButtonSetting.value !== PAID_ORG_HIDE_UPGRADE_SETTING.value
) {
return {inView: true}
}
return {inView: false}
}
export default connect<StateProps>(mstp)(CloudUpgradeButton)

View File

@ -1,6 +1,7 @@
// Libraries
import React, {FC} from 'react'
import {Link} from 'react-router'
import {connect} from 'react-redux'
// Components
import {
@ -17,44 +18,74 @@ import CloudOnly from 'src/shared/components/cloud/CloudOnly'
// Constants
import {CLOUD_URL, CLOUD_CHECKOUT_PATH} from 'src/shared/constants'
import {
HIDE_UPGRADE_CTA_KEY,
PAID_ORG_HIDE_UPGRADE_SETTING,
} from 'src/cloud/constants'
const CloudUpgradeNavBanner: FC = () => {
// Types
import {AppState, OrgSetting} from 'src/types'
interface StateProps {
inView: boolean
}
const CloudUpgradeNavBanner: FC<StateProps> = ({inView}) => {
return (
<>
<CloudOnly>
<Panel
gradient={Gradients.HotelBreakfast}
className="cloud-upgrade-banner"
>
<Panel.Header
size={ComponentSize.ExtraSmall}
justifyContent={JustifyContent.Center}
{inView ? (
<CloudOnly>
<Panel
gradient={Gradients.HotelBreakfast}
className="cloud-upgrade-banner"
>
<Heading element={HeadingElement.H5}>
Need more wiggle room?
</Heading>
</Panel.Header>
<Panel.Footer size={ComponentSize.ExtraSmall}>
<Link
className="cf-button cf-button-md cf-button-primary cf-button-stretch cloud-upgrade-banner--button"
to={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
target="_self"
<Panel.Header
size={ComponentSize.ExtraSmall}
justifyContent={JustifyContent.Center}
>
Upgrade Now
</Link>
</Panel.Footer>
</Panel>
<Link
className="cloud-upgrade-banner__collapsed"
to={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
target="_self"
>
<Icon glyph={IconFont.Star} />
<Heading element={HeadingElement.H5}>Upgrade Now</Heading>
</Link>
</CloudOnly>
<Heading element={HeadingElement.H5}>
Need more wiggle room?
</Heading>
</Panel.Header>
<Panel.Footer size={ComponentSize.ExtraSmall}>
<Link
className="cf-button cf-button-md cf-button-primary cf-button-stretch cloud-upgrade-banner--button"
to={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
target="_self"
>
Upgrade Now
</Link>
</Panel.Footer>
</Panel>
<Link
className="cloud-upgrade-banner__collapsed"
to={`${CLOUD_URL}${CLOUD_CHECKOUT_PATH}`}
target="_self"
>
<Icon glyph={IconFont.Star} />
<Heading element={HeadingElement.H5}>Upgrade Now</Heading>
</Link>
</CloudOnly>
) : null}
</>
)
}
export default CloudUpgradeNavBanner
const mstp = ({
cloud: {
orgSettings: {settings},
},
}: AppState) => {
const hideUpgradeButtonSetting = settings.find(
(setting: OrgSetting) => setting.key === HIDE_UPGRADE_CTA_KEY
)
if (
!hideUpgradeButtonSetting ||
hideUpgradeButtonSetting.value !== PAID_ORG_HIDE_UPGRADE_SETTING.value
) {
return {inView: true}
}
return {inView: false}
}
export default connect<StateProps>(mstp)(CloudUpgradeNavBanner)

View File

@ -37,6 +37,10 @@ import {membersReducer} from 'src/members/reducers'
import {autoRefreshReducer} from 'src/shared/reducers/autoRefresh'
import {limitsReducer, LimitsState} from 'src/cloud/reducers/limits'
import {demoDataReducer, DemoDataState} from 'src/cloud/reducers/demodata'
import {
orgSettingsReducer,
OrgSettingsState,
} from 'src/cloud/reducers/orgsettings'
import checksReducer from 'src/checks/reducers'
import rulesReducer from 'src/notifications/rules/reducers'
import endpointsReducer from 'src/notifications/endpoints/reducers'
@ -58,9 +62,14 @@ export const rootReducer = combineReducers<ReducerState>({
...sharedReducers,
autoRefresh: autoRefreshReducer,
alertBuilder: alertBuilderReducer,
cloud: combineReducers<{limits: LimitsState; demoData: DemoDataState}>({
cloud: combineReducers<{
limits: LimitsState
demoData: DemoDataState
orgSettings: OrgSettingsState
}>({
limits: limitsReducer,
demoData: demoDataReducer,
orgSettings: orgSettingsReducer,
}),
currentPage: currentPageReducer,
currentDashboard: currentDashboardReducer,

View File

@ -39,3 +39,18 @@ export interface LimitsStatus {
status: string
}
}
export interface OrgSetting {
key: string
value: string
}
export interface OrgSettings {
orgID: string
settings: OrgSetting[]
}
export interface OrgSettingsResponse {
status: number
statusText: string
json: () => Promise<OrgSettings>
}

View File

@ -25,6 +25,7 @@ import {LimitsState} from 'src/cloud/reducers/limits'
import {AlertBuilderState} from 'src/alerting/reducers/alertBuilder'
import {CurrentPage} from 'src/shared/reducers/currentPage'
import {DemoDataState} from 'src/cloud/reducers/demodata'
import {OrgSettingsState} from 'src/cloud/reducers/orgsettings'
import {ResourceState} from 'src/types'
@ -32,7 +33,11 @@ export interface AppState {
alertBuilder: AlertBuilderState
app: AppPresentationState
autoRefresh: AutoRefreshState
cloud: {limits: LimitsState; demoData: DemoDataState}
cloud: {
limits: LimitsState
demoData: DemoDataState
orgSettings: OrgSettingsState
}
currentPage: CurrentPage
currentDashboard: CurrentDashboardState
dataLoading: DataLoadingState