From 3dd427abf3336141a7d6124c2b5fbee80372d890 Mon Sep 17 00:00:00 2001 From: James Carr Date: Wed, 16 Sep 2026 04:54:46 -0500 Subject: [PATCH] Allow clearing a campaign reminder on update CampaignUpdateInput.Reminder is now *Nullable[CampaignReminderInput] so an explicit null can be sent to remove an existing reminder. Sending the field unset still leaves the current reminder untouched. The input object generator template carries the same override so the type survives the next schema regeneration. Co-authored-by: Cursor --- .../unreleased/Feature-20260916-090000.yaml | 4 ++ campaign_test.go | 64 +++++++++++++++++++ input.go | 12 ++-- templates/inputObjects.tpl | 1 + testdata/templates/campaigns.tpl | 34 ++++++++++ 5 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 .changes/unreleased/Feature-20260916-090000.yaml 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 }}