Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(alerts): adds support for signal_seasonality for NRQL baseline conditions #2844

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/newrelic/go-agent/v3 v3.30.0
github.com/newrelic/go-insights v1.0.3
github.com/newrelic/newrelic-client-go/v2 v2.55.3
github.com/newrelic/newrelic-client-go/v2 v2.56.0
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@ github.com/newrelic/go-agent/v3 v3.30.0 h1:ZXHCT/Cot4iIPwcegCZURuRQOsfmGA6wilW+S
github.com/newrelic/go-agent/v3 v3.30.0/go.mod h1:9utrgxlSryNqRrTvII2XBL+0lpofXbqXApvVWPpbzUg=
github.com/newrelic/go-insights v1.0.3 h1:zSNp1CEZnXktzSIEsbHJk8v6ZihdPFP2WsO/fzau3OQ=
github.com/newrelic/go-insights v1.0.3/go.mod h1:A20BoT8TNkqPGX2nS/Z2fYmKl3Cqa3iKZd4whzedCY4=
github.com/newrelic/newrelic-client-go/v2 v2.55.3 h1:7oFmlW7g2eNflm4IxvXv2C0fpe1FViAichRsdhJStBQ=
github.com/newrelic/newrelic-client-go/v2 v2.55.3/go.mod h1:+RRjI3nDGWT3kLm9Oi3QxpBm70uu8q1upEHBVWCZFpo=
github.com/newrelic/newrelic-client-go/v2 v2.56.0 h1:Kpok+iKbQwiSP5vOseeOwkDUoJkdZyUuIOCqyfTctXY=
github.com/newrelic/newrelic-client-go/v2 v2.56.0/go.mod h1:+RRjI3nDGWT3kLm9Oi3QxpBm70uu8q1upEHBVWCZFpo=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
Expand Down
9 changes: 9 additions & 0 deletions newrelic/resource_newrelic_nrql_alert_condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,15 @@ func resourceNewRelicNrqlAlertCondition() *schema.Resource {
return strings.EqualFold(old, new) // Case fold this attribute when diffing
},
},
"signal_seasonality": {
Type: schema.TypeString,
Optional: true,
Description: "Seasonality under which a condition's signal(s) are evaluated. Valid values are: 'HOURLY', 'DAILY', 'WEEKLY', 'NONE', or null. To have New Relic calculate seasonality automatically, set to null (default). To turn off seasonality completely, set to 'NONE'.",
ValidateFunc: validation.StringInSlice([]string{"HOURLY", "DAILY", "WEEKLY", "NONE"}, true),
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return strings.EqualFold(old, new) // Case fold this attribute when diffing
},
},
},
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(30 * time.Second),
Expand Down
104 changes: 104 additions & 0 deletions newrelic/resource_newrelic_nrql_alert_condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,39 @@ func TestAccNewRelicNrqlAlertCondition_TitleTemplate(t *testing.T) {
})
}

func TestAccNewRelicNrqlAlertCondition_SignalSeasonality(t *testing.T) {
resourceName := "newrelic_nrql_alert_condition.foo"
rName := acctest.RandString(5)
signalSeasonality := "daily"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheckEnvVars(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckNewRelicNrqlAlertConditionDestroy,
Steps: []resource.TestStep{
// Test: Create baseline condition with signal seasonality
{
Config: testAccNewRelicNrqlAlertConditionWithSignalSeasonality(
rName,
signalSeasonality,
),
Check: resource.ComposeTestCheckFunc(
testAccCheckNewRelicNrqlAlertConditionExists(resourceName),
),
},
// Test: Signal seasonality is nullable
{
Config: testAccNewRelicNrqlAlertConditionNullSignalSeasonality(
rName,
),
Check: resource.ComposeTestCheckFunc(
testAccCheckNewRelicNrqlAlertConditionExists(resourceName),
),
},
},
})
}

func testAccCheckNewRelicNrqlAlertConditionDestroy(s *terraform.State) error {
providerConfig := testAccProvider.Meta().(*ProviderConfig)
client := providerConfig.NewClient
Expand Down Expand Up @@ -1423,3 +1456,74 @@ resource "newrelic_nrql_alert_condition" "foo" {
}
`, name, predictBy)
}

func testAccNewRelicNrqlAlertConditionWithSignalSeasonality(
name string,
signalSeasonality string,
) string {
return fmt.Sprintf(`
resource "newrelic_alert_policy" "foo" {
name = "tf-test-%[1]s"
}

resource "newrelic_nrql_alert_condition" "foo" {
policy_id = newrelic_alert_policy.foo.id

name = "tf-test-%[1]s"
type = "baseline"
enabled = false
signal_seasonality = "%[2]s"
violation_time_limit_seconds = 3600
baseline_direction = "lower_only"
aggregation_delay = 120
aggregation_method = "event_flow"

nrql {
query = "SELECT uniqueCount(hostname) FROM ComputeSample"
}

critical {
operator = "above"
threshold = 1.0
threshold_duration = 120
threshold_occurrences = "ALL"
}
}

`, name, signalSeasonality)
}

func testAccNewRelicNrqlAlertConditionNullSignalSeasonality(
name string,
) string {
return fmt.Sprintf(`
resource "newrelic_alert_policy" "foo" {
name = "tf-test-%[1]s"
}

resource "newrelic_nrql_alert_condition" "foo" {
policy_id = newrelic_alert_policy.foo.id

name = "tf-test-%[1]s"
type = "baseline"
enabled = false
signal_seasonality = null
violation_time_limit_seconds = 3600
baseline_direction = "lower_only"
aggregation_delay = 120
aggregation_method = "event_flow"

nrql {
query = "SELECT uniqueCount(hostname) FROM ComputeSample"
}

critical {
operator = "above"
threshold = 1.0
threshold_duration = 120
threshold_occurrences = "ALL"
}
}

`, name)
}
18 changes: 18 additions & 0 deletions newrelic/structures_newrelic_nrql_alert_condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ func expandNrqlAlertConditionCreateInput(d *schema.ResourceData) (*alerts.NrqlCo
}
}

if conditionType == "baseline" {
if attr, ok := d.GetOk("signal_seasonality"); ok {
seasonality := alerts.NrqlSignalSeasonality(strings.ToUpper(attr.(string)))
input.SignalSeasonality = &seasonality
}
}

if runbookURL, ok := d.GetOk("runbook_url"); ok {
input.RunbookURL = runbookURL.(string)
}
Expand Down Expand Up @@ -132,6 +139,13 @@ func expandNrqlAlertConditionUpdateInput(d *schema.ResourceData) (*alerts.NrqlCo
}
}

if conditionType == "baseline" {
if attr, ok := d.GetOk("signal_seasonality"); ok {
seasonality := alerts.NrqlSignalSeasonality(strings.ToUpper(attr.(string)))
input.SignalSeasonality = &seasonality
}
}

if runbookURL, ok := d.GetOk("runbook_url"); ok {
input.RunbookURL = runbookURL.(string)
}
Expand Down Expand Up @@ -569,6 +583,10 @@ func flattenNrqlAlertCondition(accountID int, condition *alerts.NrqlAlertConditi

if conditionType == "baseline" {
_ = d.Set("baseline_direction", string(*condition.BaselineDirection))

if condition.SignalSeasonality != nil {
_ = d.Set("signal_seasonality", string(*condition.SignalSeasonality))
}
}

configuredNrql := d.Get("nrql.0").(map[string]interface{})
Expand Down
22 changes: 22 additions & 0 deletions newrelic/structures_newrelic_nrql_alert_condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func TestExpandNrqlAlertConditionInput(t *testing.T) {

titleTemplate := "Title {{template}}"

signalSeasonality := alerts.NrqlSignalSeasonalities.Daily

cases := map[string]struct {
Data map[string]interface{}
ExpectErr bool
Expand Down Expand Up @@ -398,6 +400,25 @@ func TestExpandNrqlAlertConditionInput(t *testing.T) {
},
},
},
"signal seasonality not nil": {
Data: map[string]interface{}{
"nrql": []interface{}{nrql},
"signal_seasonality": "daily",
},
Expanded: &alerts.NrqlConditionCreateInput{
NrqlConditionCreateBase: alerts.NrqlConditionCreateBase{},
SignalSeasonality: &signalSeasonality,
},
},
"signal seasonality nil": {
Data: map[string]interface{}{
"nrql": []interface{}{nrql},
"signal_seasonality": nil,
},
Expanded: &alerts.NrqlConditionCreateInput{
NrqlConditionCreateBase: alerts.NrqlConditionCreateBase{},
},
},
}

r := resourceNewRelicNrqlAlertCondition()
Expand Down Expand Up @@ -606,6 +627,7 @@ func TestFlattenNrqlAlertCondition(t *testing.T) {
nrqlConditionBaseline.Type = alerts.NrqlConditionTypes.Baseline
nrqlConditionBaseline.BaselineDirection = &alerts.NrqlBaselineDirections.LowerOnly
nrqlConditionBaseline.EntityGUID = common.EntityGUID("NDAwMzA0fEFPTkRJVElPTnwxNDMzNjc3")
nrqlConditionBaseline.SignalSeasonality = &alerts.NrqlSignalSeasonalities.Daily

// Static
nrqlConditionStatic := nrqlCondition
Expand Down
2 changes: 2 additions & 0 deletions website/docs/r/nrql_alert_condition.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ The following arguments are supported:
- `aggregation_timer` - (Optional) How long we wait after each data point arrives to make sure we've processed the whole batch. Use `aggregation_timer` with the `event_timer` method. The timer value can range from 0 seconds to 1200 seconds (20 minutes); the default is 60 seconds. `aggregation_timer` cannot be set with `nrql.evaluation_offset`.
- `evaluation_delay` - (Optional) How long we wait until the signal starts evaluating. The maximum delay is 7200 seconds (120 minutes).
- `slide_by` - (Optional) Gathers data in overlapping time windows to smooth the chart line, making it easier to spot trends. The `slide_by` value is specified in seconds and must be smaller than and a factor of the `aggregation_window`.
- `signal_seasonality` - (Optional) Seasonality under which a condition's signal(s) are evaluated. Only available for baseline conditions. Valid values are: `HOURLY`, `DAILY`, `WEEKLY`, `NONE`, or `null`. To have New Relic calculate seasonality automatically, set to `null`. To turn off seasonality completely, set to `NONE`.

## NRQL

Expand Down Expand Up @@ -180,6 +181,7 @@ resource "newrelic_nrql_alert_condition" "foo" {

# baseline type only
baseline_direction = "upper_only"
signal_seasonality = "none"

nrql {
query = "SELECT percentile(duration, 95) FROM Transaction WHERE appName = 'ExampleAppName'"
Expand Down
Loading