Skip to content

Add reminder configuration to opslevel_campaign resource - #652

Open
jamescarr wants to merge 4 commits into
OpsLevel:mainfrom
jamescarr:jamescarr/campaign-reminders
Open

jamescarr wants to merge 4 commits into
OpsLevel:mainfrom
jamescarr:jamescarr/campaign-reminders

Conversation

@jamescarr

@jamescarr jamescarr commented Jun 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds a reminder single-nested attribute to the opslevel_campaign resource for configuring recurring Slack, email, and Microsoft Teams reminders on a schedule.
  • Schema validation: channel / frequency_unit / day-of-week enums, frequency >= 1, HH:MM time format, and channels non-empty. ValidateConfig enforces that days_of_week is required for a weekly cadence and rejected otherwise.
  • CRUD wiring: omit on create when absent, send an explicit null to clear on update when the block is removed, and normalize the # prefix on default_slack_channel to avoid perpetual diffs.
  • Exposes reminder as a computed attribute on the opslevel_campaign and opslevel_campaigns data sources.
  • Docs, example, and a changie entry.

Dependency

opslevel-go v2026.9.7 ships the reminder types, so this now builds against the released shapes.

The one thing still outstanding is OpsLevel/opslevel-go#623, which is down to a single change: CampaignUpdateInput.Reminder becomes *Nullable[CampaignReminderInput] so an explicit null can clear an existing reminder. Without it there is no way to express "remove the reminder" — an omitted field means "leave it alone". The go.mod replace directive and the submodules/opslevel-go pointer are temporary and should be swapped for a version bump once #623 lands.

Changes since first review

default_microsoft_teams_channel has been removed. The API has no defaultMicrosoftTeamsChannel field on the campaign reminder input or output type, so the attribute was silently dropped on write and always read back null. microsoft_teams is still a valid entry in channels — only the fallback-channel attribute is gone.

Test plan

  • task lint — gofumpt, golangci-lint (0 issues), terraform validate, terraform fmt -check
  • task test — terraform mock tests + Go unit tests, 109 passed / 0 failed
  • Coverage on the reminder helpers: campaignReminderToObject and preserveHashChannel and NewCampaignResourceModel at 100%, buildCampaignReminderInput 96%, validateReminderConfig 93%
  • Remote acceptance tests (tests/remote/campaign.tftest.hcl) cover create-with-reminder, update-to-daily, and clear-reminder; these need a live OPSLEVEL_API_TOKEN and a valid owner_id

Add a `reminder` single-nested attribute to the opslevel_campaign resource for
configuring recurring Slack, email, and Microsoft Teams reminders on a schedule.
Includes schema validation (channel/frequency_unit/day enums, frequency >= 1,
HH:MM time format, weekly cadence requires days_of_week), create/read/update
wiring with clear-on-removal, and #-prefix normalization for the Slack channel
to avoid perpetual diffs. Also exposes the reminder as a computed attribute on
the opslevel_campaign and opslevel_campaigns data sources.

Depends on OpsLevel/opslevel-go#623. The go.mod replace directive and submodule
pointer are temporary for development and will be swapped for a released
opslevel-go version before merge.

Co-authored-by: Cursor <cursoragent@cursor.com>
@jamescarr

Copy link
Copy Markdown
Contributor Author

Usage examples

Weekly Slack + email reminder

resource "opslevel_campaign" "upgrade_rails" {
  name      = "Upgrade to Rails 7"
  owner_id  = data.opslevel_team.platform.id
  filter_id = data.opslevel_filter.tier_1.id

  start_date  = "2026-07-01"
  target_date = "2026-09-30"

  reminder = {
    channels       = ["slack", "email"]
    frequency      = 1
    frequency_unit = "week"
    days_of_week   = ["monday", "thursday"] # required for a weekly cadence
    time_of_day    = "09:30"
    timezone       = "America/Chicago"
    message        = "Friendly reminder to complete your Rails 7 upgrade checks."

    # Fallback channel used when a team has no default Slack contact.
    default_slack_channel = "#platform-eng"
  }
}

Every-2-days reminder (no days_of_week)

reminder = {
  channels       = ["slack"]
  frequency      = 2
  frequency_unit = "day" # daily/monthly cadences must omit days_of_week
  time_of_day    = "14:00"
  timezone       = "America/Chicago"
}

Microsoft Teams reminder

reminder = {
  channels                        = ["microsoft_teams"]
  frequency                       = 1
  frequency_unit                  = "month"
  time_of_day                     = "08:00"
  timezone                        = "Europe/London"
  default_microsoft_teams_channel = "Platform Eng"
}

No reminder / clearing one

Omit the reminder block entirely. Removing it from a campaign that previously had one sends an explicit null to clear it.

Reading it back

data "opslevel_campaign" "upgrade_rails" {
  identifier = opslevel_campaign.upgrade_rails.id
}

output "next_reminder" {
  value = data.opslevel_campaign.upgrade_rails.reminder.next_occurrence
}

Notes

  • days_of_week is required for frequency_unit = "week" and rejected for day / month.
  • The API prepends # to default_slack_channel; the provider normalizes this so "platform-eng" and "#platform-eng" don't perpetually diff.
  • next_occurrence is computed and stays null until the campaign is in_progress or delayed.

@codecov

codecov Bot commented Jun 9, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 54.08163% with 90 lines in your changes missing coverage. Please review.
✅ Project coverage is 3.04%. Comparing base (decd87c) to head (0615124).
⚠️ Report is 130 commits behind head on main.

Files with missing lines Patch % Lines
opslevel/resource_opslevel_campaign.go 54.83% 82 Missing and 2 partials ⚠️
opslevel/datasource_opslevel_campaign.go 40.00% 3 Missing ⚠️
opslevel/datasource_opslevel_campaigns_all.go 40.00% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##            main    #652      +/-   ##
========================================
+ Coverage   0.22%   3.04%   +2.81%     
========================================
  Files         85     107      +22     
  Lines       9651   13147    +3496     
========================================
+ Hits          22     400     +378     
- Misses      9626   12713    +3087     
- Partials       3      34      +31     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

jamescarr and others added 3 commits June 10, 2026 05:58
Cover the reminder conversion, normalization, and data-source mapping helpers
with internal-package unit tests, and extract the reminder validation into a
testable validateReminderConfig helper. Addresses 0% patch coverage on the
reminder code paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
…reminders

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	docs/data-sources/campaign.md
#	opslevel/datasource_opslevel_campaign.go
#	submodules/opslevel-go
The OpsLevel API has no defaultMicrosoftTeamsChannel field on campaign reminder
input or output, so the attribute was silently ignored on write and always read
back null. microsoft_teams remains a valid entry in channels.

opslevel-go v2026.9.7 ships the reminder types, so the provider now builds
against the released shapes: Channels and DaysOfWeek are pointer slices, Message
and DefaultSlackChannel are Nullable, and Campaign.Reminder is a value that
arrives zeroed when a campaign has none.

Also covers NewCampaignResourceModel with a unit test.

Co-authored-by: Cursor <cursoragent@cursor.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant