diff --git a/.changes/unreleased/Feature-20260916-090000.yaml b/.changes/unreleased/Feature-20260916-090000.yaml new file mode 100644 index 00000000..15f2bb40 --- /dev/null +++ b/.changes/unreleased/Feature-20260916-090000.yaml @@ -0,0 +1,4 @@ +kind: Feature +body: Make `CampaignUpdateInput.Reminder` nullable so an existing campaign reminder + can be cleared by sending an explicit `null` +time: 2026-09-16T09:00:00.000000000-05:00 diff --git a/campaign_test.go b/campaign_test.go index c28990b9..999e2120 100644 --- a/campaign_test.go +++ b/campaign_test.go @@ -80,6 +80,70 @@ func TestUpdateCampaign(t *testing.T) { autopilot.Equals(t, id2, campaign.Owner.Id) } +func TestUpdateCampaignSetReminder(t *testing.T) { + // Arrange + testRequest := autopilot.NewTestRequest( + `{{ template "campaign_update_request" }}`, + `{{ template "campaign_update_reminder_request_vars" }}`, + `{{ template "campaign_update_reminder_response" }}`, + ) + client := BestTestClient(t, "campaign/update_reminder", testRequest) + + // Act + campaign, err := client.UpdateCampaign(ol.CampaignUpdateInput{ + Id: id1, + Reminder: ol.NewNullableFrom(ol.CampaignReminderInput{ + Channels: &[]ol.CampaignReminderChannelEnum{ + ol.CampaignReminderChannelEnumSlack, + ol.CampaignReminderChannelEnumMicrosoftTeams, + }, + DaysOfWeek: &[]ol.DayOfWeekEnum{ol.DayOfWeekEnumMonday}, + DefaultSlackChannel: ol.RefOf("#engineering"), + Frequency: 1, + FrequencyUnit: ol.CampaignReminderFrequencyUnitEnumWeek, + Message: ol.RefOf("Please complete your campaign checks"), + TimeOfDay: "09:00", + Timezone: "America/Chicago", + }), + }) + + // Assert + autopilot.Ok(t, err) + autopilot.Equals(t, id1, campaign.Id) + autopilot.Equals(t, []ol.CampaignReminderChannelEnum{ + ol.CampaignReminderChannelEnumSlack, + ol.CampaignReminderChannelEnumMicrosoftTeams, + }, campaign.Reminder.Channels) + autopilot.Equals(t, []ol.DayOfWeekEnum{ol.DayOfWeekEnumMonday}, campaign.Reminder.DaysOfWeek) + autopilot.Equals(t, "#engineering", campaign.Reminder.DefaultSlackChannel) + autopilot.Equals(t, 1, campaign.Reminder.Frequency) + autopilot.Equals(t, ol.CampaignReminderFrequencyUnitEnumWeek, campaign.Reminder.FrequencyUnit) + autopilot.Equals(t, "Please complete your campaign checks", campaign.Reminder.Message) + autopilot.Equals(t, "09:00", campaign.Reminder.TimeOfDay) + autopilot.Equals(t, "America/Chicago", campaign.Reminder.Timezone) +} + +func TestUpdateCampaignClearReminder(t *testing.T) { + // Arrange + testRequest := autopilot.NewTestRequest( + `{{ template "campaign_update_request" }}`, + `{{ template "campaign_clear_reminder_request_vars" }}`, + `{{ template "campaign_update_response" }}`, + ) + client := BestTestClient(t, "campaign/clear_reminder", testRequest) + + // Act + campaign, err := client.UpdateCampaign(ol.CampaignUpdateInput{ + Id: id1, + Reminder: ol.NewNullOf[ol.CampaignReminderInput](), + }) + + // Assert + autopilot.Ok(t, err) + autopilot.Equals(t, id1, campaign.Id) + autopilot.Equals(t, ol.CampaignReminder{}, campaign.Reminder) +} + func TestDeleteCampaign(t *testing.T) { // Arrange testRequest := autopilot.NewTestRequest( diff --git a/input.go b/input.go index 66f9a9c6..cdc2760c 100644 --- a/input.go +++ b/input.go @@ -158,12 +158,12 @@ type CampaignUnscheduleInput struct { // CampaignUpdateInput Specifies the input fields used to update a campaign type CampaignUpdateInput struct { - FilterId *Nullable[ID] `json:"filterId,omitempty" yaml:"filterId,omitempty" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The ID of the filter applied to this campaign (Optional) - Id ID `json:"id" yaml:"id" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The id of the campaign to be updated (Required) - Name *string `json:"name,omitempty" yaml:"name,omitempty" example:"example_value"` // The name of the campaign (Optional) - OwnerId *Nullable[ID] `json:"ownerId,omitempty" yaml:"ownerId,omitempty" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The ID of the team that owns this campaign (Optional) - ProjectBrief *string `json:"projectBrief,omitempty" yaml:"projectBrief,omitempty" example:"example_value"` // The project brief of the campaign (Optional) - Reminder *CampaignReminderInput `json:"reminder,omitempty" yaml:"reminder,omitempty"` // Configuration of an optional campaign reminder (Optional) + FilterId *Nullable[ID] `json:"filterId,omitempty" yaml:"filterId,omitempty" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The ID of the filter applied to this campaign (Optional) + Id ID `json:"id" yaml:"id" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The id of the campaign to be updated (Required) + Name *string `json:"name,omitempty" yaml:"name,omitempty" example:"example_value"` // The name of the campaign (Optional) + OwnerId *Nullable[ID] `json:"ownerId,omitempty" yaml:"ownerId,omitempty" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The ID of the team that owns this campaign (Optional) + ProjectBrief *string `json:"projectBrief,omitempty" yaml:"projectBrief,omitempty" example:"example_value"` // The project brief of the campaign (Optional) + Reminder *Nullable[CampaignReminderInput] `json:"reminder,omitempty" yaml:"reminder,omitempty"` // Configuration of an optional campaign reminder (Optional) } // CheckAlertSourceUsageCreateInput Specifies the input fields used to create an alert source usage check diff --git a/templates/inputObjects.tpl b/templates/inputObjects.tpl index 9a21da27..a93b88b5 100644 --- a/templates/inputObjects.tpl +++ b/templates/inputObjects.tpl @@ -3,6 +3,7 @@ type {{ .Name }} struct { {{ range .Values }} {{ title .Name.Name }} {{ if and (eq $.Name "AliasCreateInput") (eq .Name.Name "ownerId") }}ID {{- else if and (eq $.Name "CheckPackageVersionUpdateInput") (eq .Name.Name "versionConstraintPredicate") }}*PredicateUpdateInput + {{- else if and (eq $.Name "CampaignUpdateInput") (eq .Name.Name "reminder") }}*Nullable[CampaignReminderInput] {{- else if and (eq $.Name "ManualCheckFrequencyUpdateInput") (eq .Name.Name "frequencyValue") }}*Nullable[int] {{- else if eq $.Name "IdentifierInput" }} {{- if eq .Type.String "String" }}*string diff --git a/testdata/templates/campaigns.tpl b/testdata/templates/campaigns.tpl index d55b1ffd..7e3102bf 100644 --- a/testdata/templates/campaigns.tpl +++ b/testdata/templates/campaigns.tpl @@ -163,6 +163,40 @@ mutation CampaignUpdate($input:CampaignUpdateInput!){campaignUpdate(input: $inpu },"errors":[]}} }{{ end }} +{{- define "campaign_update_reminder_request_vars" }} +{"input":{"id":"{{ template "id1_string" }}","reminder":{"channels":["slack","microsoft_teams"],"daysOfWeek":["monday"],"defaultSlackChannel":"#engineering","frequency":1,"frequencyUnit":"week","message":"Please complete your campaign checks","timeOfDay":"09:00","timezone":"America/Chicago"}}} +{{ end }} + +{{- define "campaign_update_reminder_response" }}{ + "data":{"campaignUpdate":{"campaign":{ + {{ template "id1" }}, + "name":"Updated Campaign", + "htmlUrl":"https://app.opslevel.com/campaigns/updated", + "status":"draft", + "checkStats":{"total":0,"totalSuccessful":0}, + "serviceStats":{"total":0,"totalSuccessful":0}, + "owner":{ {{ template "id2" }}, "alias":"staff" }, + "projectBrief":"A test campaign", + "rawProjectBrief":"A test campaign", + "filter":null, + "reminder":{ + "channels":["slack","microsoft_teams"], + "daysOfWeek":["monday"], + "defaultSlackChannel":"#engineering", + "frequency":1, + "frequencyUnit":"week", + "message":"Please complete your campaign checks", + "nextOccurrence":"2026-05-04T09:00:00Z", + "timeOfDay":"09:00", + "timezone":"America/Chicago" + } + },"errors":[]}} +}{{ end }} + +{{- define "campaign_clear_reminder_request_vars" }} +{"input":{"id":"{{ template "id1_string" }}","reminder":null}} +{{ end }} + {{- define "campaign_delete_request" }} mutation CampaignDelete($input:DeleteInput!){campaignDelete(input: $input){deletedId,errors{message,path}}} {{ end }}