Skip to content
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
3 changes: 3 additions & 0 deletions .changes/unreleased/Feature-20260609-150000.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Feature
body: Add CampaignReminderInput and a reminder field on CampaignCreateInput and CampaignUpdateInput to manage recurring campaign reminders (Slack, email, Microsoft Teams). Campaign.Reminder is now a pointer so a cleared reminder is distinguishable from an unset one.
time: 2026-06-09T15:00:00.000000-05:00
75 changes: 71 additions & 4 deletions campaign_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,73 @@ func TestCreateCampaign(t *testing.T) {
autopilot.Equals(t, "A test campaign", campaign.RawProjectBrief)
}

func TestCreateCampaignWithReminder(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_create_request" }}`,
`{{ template "campaign_create_reminder_request_vars" }}`,
`{{ template "campaign_create_reminder_response" }}`,
)
client := BestTestClient(t, "campaign/create_reminder", testRequest)

message := "Reminder message"
slackChannel := "platform-eng"
// Act
campaign, err := client.CreateCampaign(ol.CampaignCreateInput{
Name: "New Campaign",
OwnerId: id1,
Reminder: &ol.CampaignReminderInput{
Channels: []ol.CampaignReminderChannelEnum{ol.CampaignReminderChannelEnumSlack, ol.CampaignReminderChannelEnumEmail},
Frequency: 1,
FrequencyUnit: ol.CampaignReminderFrequencyUnitEnumWeek,
DaysOfWeek: []ol.DayOfWeekEnum{ol.DayOfWeekEnumMonday, ol.DayOfWeekEnumThursday},
TimeOfDay: "09:30",
Timezone: "America/Chicago",
Message: &message,
DefaultSlackChannel: &slackChannel,
},
})

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, "New Campaign", campaign.Name)
if campaign.Reminder == nil {
t.Fatal("expected campaign to have a reminder, got nil")
}
autopilot.Equals(t, ol.CampaignReminderFrequencyUnitEnumWeek, campaign.Reminder.FrequencyUnit)
autopilot.Equals(t, 1, campaign.Reminder.Frequency)
autopilot.Equals(t, "09:30", campaign.Reminder.TimeOfDay)
autopilot.Equals(t, "America/Chicago", campaign.Reminder.Timezone)
autopilot.Equals(t, "#platform-eng", campaign.Reminder.DefaultSlackChannel)
autopilot.Equals(t, 2, len(campaign.Reminder.Channels))
autopilot.Equals(t, 2, len(campaign.Reminder.DaysOfWeek))
}

func TestUpdateCampaignClearReminder(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`{{ template "campaign_update_request" }}`,
`{{ template "campaign_update_clear_reminder_request_vars" }}`,
`{{ template "campaign_update_response" }}`,
)
client := BestTestClient(t, "campaign/update_clear_reminder", testRequest)

name := "Updated Campaign"
// Act
campaign, err := client.UpdateCampaign(ol.CampaignUpdateInput{
Id: id1,
Name: &name,
Reminder: ol.NewNullOf[ol.CampaignReminderInput](),
})

// Assert
autopilot.Ok(t, err)
autopilot.Equals(t, id1, campaign.Id)
if campaign.Reminder != nil {
t.Fatalf("expected reminder to be cleared, got %+v", campaign.Reminder)
}
}

func TestGetCampaign(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
Expand Down Expand Up @@ -204,12 +271,12 @@ func TestListCampaignChecksEmpty(t *testing.T) {
func TestListCampaigns(t *testing.T) {
// Arrange
testRequestOne := autopilot.NewTestRequest(
`query CampaignsList($after:String!$first:Int!$sortBy:CampaignSortEnum!$status:String!){account{campaigns(first: $first, after: $after, sortBy: $sortBy, filter: [{key: status, arg: $status}]){nodes{checkStats{total,totalSuccessful},endedDate,filter{id,name},htmlUrl,id,name,owner{alias,id},projectBrief,rawProjectBrief,reminder{channels,daysOfWeek,defaultSlackChannel,frequency,frequencyUnit,message,nextOccurrence,timeOfDay,timezone},serviceStats{total,totalSuccessful},startDate,status,targetDate},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor}}}}`,
`query CampaignsList($after:String!$first:Int!$sortBy:CampaignSortEnum!$status:String!){account{campaigns(first: $first, after: $after, sortBy: $sortBy, filter: [{key: status, arg: $status}]){nodes{checkStats{total,totalSuccessful},endedDate,filter{id,name},htmlUrl,id,name,owner{alias,id},projectBrief,rawProjectBrief,reminder{channels,daysOfWeek,defaultSlackChannel,defaultMicrosoftTeamsChannel,frequency,frequencyUnit,message,nextOccurrence,timeOfDay,timezone},serviceStats{total,totalSuccessful},startDate,status,targetDate},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor}}}}`,
`{ "after": "", "first": 500, "sortBy": "start_date_DESC", "status": "in_progress" }`,
`{ "data": { "account": { "campaigns": { "nodes": [ {{ template "campaign1_response" }}, {{ template "campaign2_response" }} ], {{ template "pagination_initial_pageInfo_response" }} }}}}`,
)
testRequestTwo := autopilot.NewTestRequest(
`query CampaignsList($after:String!$first:Int!$sortBy:CampaignSortEnum!$status:String!){account{campaigns(first: $first, after: $after, sortBy: $sortBy, filter: [{key: status, arg: $status}]){nodes{checkStats{total,totalSuccessful},endedDate,filter{id,name},htmlUrl,id,name,owner{alias,id},projectBrief,rawProjectBrief,reminder{channels,daysOfWeek,defaultSlackChannel,frequency,frequencyUnit,message,nextOccurrence,timeOfDay,timezone},serviceStats{total,totalSuccessful},startDate,status,targetDate},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor}}}}`,
`query CampaignsList($after:String!$first:Int!$sortBy:CampaignSortEnum!$status:String!){account{campaigns(first: $first, after: $after, sortBy: $sortBy, filter: [{key: status, arg: $status}]){nodes{checkStats{total,totalSuccessful},endedDate,filter{id,name},htmlUrl,id,name,owner{alias,id},projectBrief,rawProjectBrief,reminder{channels,daysOfWeek,defaultSlackChannel,defaultMicrosoftTeamsChannel,frequency,frequencyUnit,message,nextOccurrence,timeOfDay,timezone},serviceStats{total,totalSuccessful},startDate,status,targetDate},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor}}}}`,
`{ "after": "OA", "first": 500, "sortBy": "start_date_DESC", "status": "in_progress" }`,
`{ "data": { "account": { "campaigns": { "nodes": [ {{ template "campaign3_response" }} ], {{ template "pagination_second_pageInfo_response" }} }}}}`,
)
Expand Down Expand Up @@ -260,7 +327,7 @@ func TestListCampaignsWithCustomVariables(t *testing.T) {
sortBy := ol.CampaignSortEnumStartDateAsc
status := ol.CampaignStatusEnumDelayed
testRequest := autopilot.NewTestRequest(
`query CampaignsList($after:String!$first:Int!$sortBy:CampaignSortEnum!$status:String!){account{campaigns(first: $first, after: $after, sortBy: $sortBy, filter: [{key: status, arg: $status}]){nodes{checkStats{total,totalSuccessful},endedDate,filter{id,name},htmlUrl,id,name,owner{alias,id},projectBrief,rawProjectBrief,reminder{channels,daysOfWeek,defaultSlackChannel,frequency,frequencyUnit,message,nextOccurrence,timeOfDay,timezone},serviceStats{total,totalSuccessful},startDate,status,targetDate},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor}}}}`,
`query CampaignsList($after:String!$first:Int!$sortBy:CampaignSortEnum!$status:String!){account{campaigns(first: $first, after: $after, sortBy: $sortBy, filter: [{key: status, arg: $status}]){nodes{checkStats{total,totalSuccessful},endedDate,filter{id,name},htmlUrl,id,name,owner{alias,id},projectBrief,rawProjectBrief,reminder{channels,daysOfWeek,defaultSlackChannel,defaultMicrosoftTeamsChannel,frequency,frequencyUnit,message,nextOccurrence,timeOfDay,timezone},serviceStats{total,totalSuccessful},startDate,status,targetDate},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor}}}}`,
`{ "after": "cursor", "first": 5, "sortBy": "start_date_ASC", "status": "delayed" }`,
`{ "data": { "account": { "campaigns": { "nodes": [ {{ template "campaign1_response" }} ], "pageInfo": { "hasNextPage": false, "hasPreviousPage": false, "startCursor": null, "endCursor": null } }}}}`,
)
Expand All @@ -281,7 +348,7 @@ func TestListCampaignsWithCustomVariables(t *testing.T) {
func TestListCampaignsEmpty(t *testing.T) {
// Arrange
testRequest := autopilot.NewTestRequest(
`query CampaignsList($after:String!$first:Int!$sortBy:CampaignSortEnum!$status:String!){account{campaigns(first: $first, after: $after, sortBy: $sortBy, filter: [{key: status, arg: $status}]){nodes{checkStats{total,totalSuccessful},endedDate,filter{id,name},htmlUrl,id,name,owner{alias,id},projectBrief,rawProjectBrief,reminder{channels,daysOfWeek,defaultSlackChannel,frequency,frequencyUnit,message,nextOccurrence,timeOfDay,timezone},serviceStats{total,totalSuccessful},startDate,status,targetDate},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor}}}}`,
`query CampaignsList($after:String!$first:Int!$sortBy:CampaignSortEnum!$status:String!){account{campaigns(first: $first, after: $after, sortBy: $sortBy, filter: [{key: status, arg: $status}]){nodes{checkStats{total,totalSuccessful},endedDate,filter{id,name},htmlUrl,id,name,owner{alias,id},projectBrief,rawProjectBrief,reminder{channels,daysOfWeek,defaultSlackChannel,defaultMicrosoftTeamsChannel,frequency,frequencyUnit,message,nextOccurrence,timeOfDay,timezone},serviceStats{total,totalSuccessful},startDate,status,targetDate},pageInfo{hasNextPage,hasPreviousPage,startCursor,endCursor}}}}`,
`{ "after": "", "first": 500, "sortBy": "start_date_DESC", "status": "in_progress" }`,
`{ "data": { "account": { "campaigns": { "nodes": [], "pageInfo": { "hasNextPage": false, "hasPreviousPage": false, "startCursor": null, "endCursor": null } }}}}`,
)
Expand Down
33 changes: 24 additions & 9 deletions input.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,24 @@ type CategoryUpdateInput struct {

// CampaignCreateInput Specifies the input fields used to create a campaign
type CampaignCreateInput struct {
Name string `json:"name" yaml:"name" example:"example_value"` // The name of the campaign (Required)
OwnerId ID `json:"ownerId" yaml:"ownerId" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The id of the team that owns the campaign (Required)
FilterId *Nullable[ID] `json:"filterId,omitempty" yaml:"filterId,omitempty" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The id of the filter applied to the campaign (Optional)
ProjectBrief *string `json:"projectBrief,omitempty" yaml:"projectBrief,omitempty" example:"example_value"` // The project brief of the campaign in Markdown (Optional)
Name string `json:"name" yaml:"name" example:"example_value"` // The name of the campaign (Required)
OwnerId ID `json:"ownerId" yaml:"ownerId" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The id of the team that owns the campaign (Required)
FilterId *Nullable[ID] `json:"filterId,omitempty" yaml:"filterId,omitempty" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The id of the filter applied to the campaign (Optional)
ProjectBrief *string `json:"projectBrief,omitempty" yaml:"projectBrief,omitempty" example:"example_value"` // The project brief of the campaign in Markdown (Optional)
Reminder *CampaignReminderInput `json:"reminder,omitempty" yaml:"reminder,omitempty"` // Configuration of an optional recurring campaign reminder (Optional)
}

// CampaignReminderInput Specifies the input fields used to configure a recurring campaign reminder
type CampaignReminderInput struct {
Channels []CampaignReminderChannelEnum `json:"channels" yaml:"channels"` // The communication channels through which the reminder will be delivered (Required)
Frequency int `json:"frequency" yaml:"frequency" example:"1"` // The interval at which reminders will be delivered (Required)
FrequencyUnit CampaignReminderFrequencyUnitEnum `json:"frequencyUnit" yaml:"frequencyUnit" example:"week"` // The time unit of the value in the 'frequency' field (Required)
TimeOfDay string `json:"timeOfDay" yaml:"timeOfDay" example:"09:30"` // The time of day at which the reminder will be delivered. Format: "HH:MM" (Required)
Timezone string `json:"timezone" yaml:"timezone" example:"America/Chicago"` // The timezone at which the timeOfDay field is evaluated (in IANA format) (Required)
DaysOfWeek []DayOfWeekEnum `json:"daysOfWeek,omitempty" yaml:"daysOfWeek,omitempty"` // A list of weekdays on which the reminders will be delivered. Only available with weekly frequency (Optional)
Message *string `json:"message,omitempty" yaml:"message,omitempty" example:"example_value"` // The message that will be delivered as the reminder (Optional)
DefaultSlackChannel *string `json:"defaultSlackChannel,omitempty" yaml:"defaultSlackChannel,omitempty" example:"example_value"` // The Slack channel notified if a team doesn't have a default Slack contact (Optional)
DefaultMicrosoftTeamsChannel *string `json:"defaultMicrosoftTeamsChannel,omitempty" yaml:"defaultMicrosoftTeamsChannel,omitempty" example:"example_value"` // The Microsoft Teams channel notified if a team doesn't have a default Teams contact (Optional)
}

// ChecksCopyToCampaignInput Specifies the input fields for copying checks to a campaign
Expand All @@ -121,11 +135,12 @@ type CampaignUnscheduleInput struct {

// CampaignUpdateInput Specifies the input fields used to update a campaign
type CampaignUpdateInput struct {
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 the campaign (Optional)
FilterId *Nullable[ID] `json:"filterId,omitempty" yaml:"filterId,omitempty" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The id of the filter applied to the campaign (Optional)
ProjectBrief *string `json:"projectBrief,omitempty" yaml:"projectBrief,omitempty" example:"example_value"` // The project brief of the campaign in Markdown (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 the campaign (Optional)
FilterId *Nullable[ID] `json:"filterId,omitempty" yaml:"filterId,omitempty" example:"Z2lkOi8vc2VydmljZS8xMjM0NTY3ODk"` // The id of the filter applied to the campaign (Optional)
ProjectBrief *string `json:"projectBrief,omitempty" yaml:"projectBrief,omitempty" example:"example_value"` // The project brief of the campaign in Markdown (Optional)
Reminder *Nullable[CampaignReminderInput] `json:"reminder,omitempty" yaml:"reminder,omitempty"` // Configuration of an optional recurring campaign reminder. Set explicitly to null to clear (Optional)
}

// CheckAlertSourceUsageCreateInput Specifies the input fields used to create an alert source usage check
Expand Down
21 changes: 11 additions & 10 deletions object.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ type Campaign struct {
Owner TeamId // The team that owns the campaign (Optional)
ProjectBrief string // The project brief of the campaign (Optional)
RawProjectBrief string // The raw unsanitized project brief of the campaign (Optional)
Reminder CampaignReminder // Configuration of an optional campaign reminder (Optional)
Reminder *CampaignReminder // Configuration of an optional campaign reminder (Optional)
ServiceStats Stats // A summary of services that completed the campaign (Optional)
StartDate iso8601.Time // The date the campaign will start (Optional)
Status CampaignStatusEnum // The status of the campaign (Required)
Expand All @@ -65,15 +65,16 @@ type Campaign struct {

// CampaignReminder Configuration of an optional campaign reminder
type CampaignReminder struct {
Channels []CampaignReminderChannelEnum // The communication channels through which the reminder will be delivered (Required)
DaysOfWeek []DayOfWeekEnum // A list of weekdays on which the reminders will be delivered. Only available with weekly frequency (Optional)
DefaultSlackChannel string // The name of the Slack channel that will be notified if a team doesn't have a default Slack contact (Optional)
Frequency int // The interval at which reminders will be delivered (Required)
FrequencyUnit CampaignReminderFrequencyUnitEnum // The time unit of the value in the 'frequency' field (Required)
Message string // The message that will be delivered as the reminder (Optional)
NextOccurrence iso8601.Time // The point in time at which the next reminder will be delivered based on the current configuration (Optional)
TimeOfDay string // The time of day at which the reminder will be delivered. Format: "HH:MM" (Required)
Timezone string // The timezone at which the timeOfDay field is evaluated (in IANA format (e.g. "America/Chicago")) (Required)
Channels []CampaignReminderChannelEnum // The communication channels through which the reminder will be delivered (Required)
DaysOfWeek []DayOfWeekEnum // A list of weekdays on which the reminders will be delivered. Only available with weekly frequency (Optional)
DefaultSlackChannel string // The name of the Slack channel that will be notified if a team doesn't have a default Slack contact (Optional)
DefaultMicrosoftTeamsChannel string // The name of the Microsoft Teams channel that will be notified if a team doesn't have a default Teams contact (Optional)
Frequency int // The interval at which reminders will be delivered (Required)
FrequencyUnit CampaignReminderFrequencyUnitEnum // The time unit of the value in the 'frequency' field (Required)
Message string // The message that will be delivered as the reminder (Optional)
NextOccurrence iso8601.Time // The point in time at which the next reminder will be delivered based on the current configuration (Optional)
TimeOfDay string // The time of day at which the reminder will be delivered. Format: "HH:MM" (Required)
Timezone string // The timezone at which the timeOfDay field is evaluated (in IANA format (e.g. "America/Chicago")) (Required)
}

// CampaignSendReminderOutcomeTeams Summarizes list of teams returned from attempt to send reminders for their failed campaigns
Expand Down
Loading
Loading