Skip to content

WIP: feat(slack-bot): automate support-request Jira workflow#5111

Open
jmguzik wants to merge 1 commit intoopenshift:mainfrom
jmguzik:jira-ops
Open

WIP: feat(slack-bot): automate support-request Jira workflow#5111
jmguzik wants to merge 1 commit intoopenshift:mainfrom
jmguzik:jira-ops

Conversation

@jmguzik
Copy link
Copy Markdown
Contributor

@jmguzik jmguzik commented Apr 17, 2026

Add support-request handling for long forum threads by creating DPTP Jira tickets, posting thread guidance, and closing linked tickets from :closed: reactions with replica-safe locking and transition-aware Jira updates.

Summary by CodeRabbit

  • New Features

    • Added support-request mode for forum threads that automatically creates Jira issues when thread message count exceeds a configurable threshold
    • Added ability to close Jira issues via emoji reactions on threads
    • Added Jira issue status and resolution management capabilities
  • Tests

    • Added comprehensive test coverage for support-request event handling
    • Added tests for cross-replica lock mechanism
  • Documentation

    • Updated documentation describing new support-request mode behavior and configuration

Add support-request handling for long forum threads by creating DPTP Jira tickets, posting thread guidance, and closing linked tickets from :closed: reactions with replica-safe locking and transition-aware Jira updates.

Signed-off-by: Jakub Guzik <jguzik@redhat.com>
@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: automatic mode

@openshift-ci openshift-ci bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Apr 17, 2026
@openshift-ci openshift-ci bot requested review from Prucek and hector-vido April 17, 2026 13:13
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci bot commented Apr 17, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: jmguzik

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Apr 17, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

Walkthrough

This pull request introduces a new "support-request" feature for the Slack bot. The implementation includes Jira issue lifecycle management (closing/status updates), distributed locking via Kubernetes ConfigMaps for cross-replica coordination, a new Slack event handler that monitors threads and automatically files Jira issues when message thresholds are exceeded, and related CLI configuration options.

Changes

Cohort / File(s) Summary
Documentation & Configuration
cmd/slack-bot/README.md, cmd/slack-bot/main.go
Added documentation describing support-request mode behavior and introduced two new CLI flags: support-request-channel-id (default CBN38N3MW) and support-request-threshold (default 5). Added validation ensuring threshold ≥ 1 when channel ID is configured.
Jira Client Enhancements
pkg/jira/fake.go, pkg/jira/issues.go, pkg/jira/issues_test.go
Extended IssueFiler interface with SetIssueStatus and CloseIssue methods to transition Jira issues. Added transition-selection logic with case-insensitive matching and fallback priorities. Updated fake client to track close/status behaviors separately with validation. Added test utilities GetTransitions and DoTransitionWithPayload to jiraClient interface.
Event Router Wiring
pkg/slack/events/router/router.go
Updated ForEvents signature to accept IssueFiler, support-request channel ID, and message threshold parameters. Wired in new supportrequest.HandlerWithLock alongside existing handlers.
Support Request Handler
pkg/slack/events/supportrequest/handler.go, pkg/slack/events/supportrequest/handler_test.go
Implemented Slack event handler processing message and reaction events. For messages in the configured channel: validates thread eligibility (reply count exceeds threshold, no existing Jira reference), acquires distributed lock, files Jira issue, and posts Slack notification. For closed emoji reactions: extracts Jira key from thread and closes the issue. Includes Slack retry logic with exponential backoff and rate-limit awareness.
Distributed Lock Mechanism
pkg/slack/events/supportrequest/lock.go, pkg/slack/events/supportrequest/lock_test.go
Implemented Kubernetes ConfigMap-based distributed lock client supporting Acquire, Release, and MarkProcessed operations with TTL-based expiration. Prevents duplicate issue creation across replicas and maintains durable "processed" state to block future lock acquisition.

Sequence Diagram(s)

sequenceDiagram
    actor Slack as Slack User
    participant SB as Slack Bot
    participant LC as Lock Client
    participant Jira as Jira
    participant KV as K8s ConfigMap

    Slack->>SB: Message in thread (exceeds threshold)
    SB->>SB: Check thread eligibility
    SB->>LC: Acquire lock for thread
    LC->>KV: Read/write lock state
    KV-->>LC: Lock acquired
    LC-->>SB: Acquired
    SB->>Jira: FileIssue (with thread link)
    Jira-->>SB: Issue created
    SB->>Slack: Post Jira link in thread
    Slack-->>SB: ACK
    SB->>LC: MarkProcessed
    LC->>KV: Set processed marker
    KV-->>LC: OK
    LC-->>SB: Marked
    
    Slack->>SB: Reaction :closed: on root message
    SB->>SB: Extract Jira key from thread
    SB->>Jira: CloseIssue(key, resolution)
    Jira-->>SB: Issue closed
    SB->>Slack: Post close confirmation
    Slack-->>SB: ACK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 9 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: automating the Jira workflow for support-request forum threads, which is the primary focus of the changeset across all modified and added files.
Stable And Deterministic Test Names ✅ Passed All test names in the pull request are stable and deterministic with descriptive static strings.
Test Structure And Quality ✅ Passed PR uses Go's standard testing package with well-structured tests demonstrating high quality: single responsibilities, clear names, proper helper setup, meaningful assertions, and consistent patterns.
Microshift Test Compatibility ✅ Passed This PR adds only standard Go unit tests using the testing package, not Ginkgo e2e tests. The check is not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The pull request adds three test files using standard Go unit testing conventions, not Ginkgo e2e tests. The SNO compatibility check applies only to Ginkgo e2e tests, which are not present.
Topology-Aware Scheduling Compatibility ✅ Passed PR contains only Go application code without deployment manifests or topology-specific assumptions; ConfigMap-based locking works across all OpenShift topologies.
Ote Binary Stdout Contract ✅ Passed PR introduces no violations of the OTE Binary Stdout Contract; all logging routed through logrus via logrusutil.ComponentInit(), all fmt usage for string formatting within event handler logic only.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PR adds standard Go unit tests using testing.T with mocked clients; no external network calls, IPv4-specific code, or assumptions that would fail in IPv6-only disconnected environments.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
pkg/slack/events/supportrequest/lock.go (1)

75-98: Consider documenting the cleanup strategy for processed entries.

The processed state is permanent and entries are never removed, which could cause the ConfigMap to grow unbounded over time as more threads are processed. Consider adding a comment about the expected growth rate and whether periodic cleanup (e.g., a separate job) is planned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/slack/events/supportrequest/lock.go` around lines 75 - 98, MarkProcessed
currently writes permanent entries (lockStateProcessed) into the ConfigMap
(threadLockMapName) via configMapLockClient.MarkProcessed which can cause
unbounded growth; add a concise comment above the MarkProcessed method (or near
the constants lockStateProcessed/threadLockMapName) documenting that processed
entries are retained, the expected growth rate, and the intended cleanup
strategy (e.g., periodic GC job, TTL pruning, or compaction) and, if applicable,
mention where that job will live or how retention is bounded so reviewers know
this is intentional.
pkg/jira/issues.go (1)

115-139: Case-sensitive comparison may miss valid transitions.

SetIssueStatus uses case-sensitive comparison (==) while closingTransitionPriority uses strings.EqualFold. Consider using case-insensitive matching here for consistency and robustness against varying Jira workflow configurations.

♻️ Proposed fix for case-insensitive matching
 	var transitionID string
 	for _, transition := range transitions {
-		if transition.To.Name == status || transition.Name == status {
+		if strings.EqualFold(transition.To.Name, status) || strings.EqualFold(transition.Name, status) {
 			transitionID = transition.ID
 			break
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/jira/issues.go` around lines 115 - 139, SetIssueStatus currently compares
transition names using case-sensitive == which can miss valid transitions;
change the comparisons in SetIssueStatus to use case-insensitive matching
(strings.EqualFold) when comparing transition.To.Name and transition.Name to the
provided status, and add the strings import if not already present so the
function consistently matches closingTransitionPriority's behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/jira/fake.go`:
- Around line 45-50: NewFake currently only accepts
map[IssueRequest]IssueResponse and the internal maps closeBehavior and
statusBehavior are unexported, so external callers cannot configure CloseIssue
or SetIssueStatus behavior; update the API by either extending NewFake to accept
additional parameters (map[CloseIssueRequest]CloseIssueResponse and
map[SetIssueStatusRequest]SetIssueStatusResponse) or add exported setter methods
on the Fake type (e.g.,
SetCloseBehavior(map[CloseIssueRequest]CloseIssueResponse) and
SetStatusBehavior(map[SetIssueStatusRequest]SetIssueStatusResponse)) that
populate the internal closeBehavior and statusBehavior fields so Fake.CloseIssue
and Fake.SetIssueStatus can be configured from outside pkg/jira.

In `@pkg/slack/events/supportrequest/handler.go`:
- Around line 154-174: The thread is marked processed too early
(locker.MarkProcessed with event.ThreadTimeStamp), making subsequent actions
best-effort; either persist the thread→issue mapping before calling
locker.MarkProcessed or move the MarkProcessed call to after the durable
operations complete. Specifically update the logic around locker.MarkProcessed,
filer.SetIssueStatus(issue.Key, ...), and the postMessageWithRetry(...) that
posts supportRequestPrefix so that the thread→issue mapping (used by
handleReactionAdded) is written durably first or MarkProcessed is invoked only
after SetIssueStatus and the Slack post both succeed (or after you persist the
mapping elsewhere) to guarantee the transition and link-back are reliable.

---

Nitpick comments:
In `@pkg/jira/issues.go`:
- Around line 115-139: SetIssueStatus currently compares transition names using
case-sensitive == which can miss valid transitions; change the comparisons in
SetIssueStatus to use case-insensitive matching (strings.EqualFold) when
comparing transition.To.Name and transition.Name to the provided status, and add
the strings import if not already present so the function consistently matches
closingTransitionPriority's behavior.

In `@pkg/slack/events/supportrequest/lock.go`:
- Around line 75-98: MarkProcessed currently writes permanent entries
(lockStateProcessed) into the ConfigMap (threadLockMapName) via
configMapLockClient.MarkProcessed which can cause unbounded growth; add a
concise comment above the MarkProcessed method (or near the constants
lockStateProcessed/threadLockMapName) documenting that processed entries are
retained, the expected growth rate, and the intended cleanup strategy (e.g.,
periodic GC job, TTL pruning, or compaction) and, if applicable, mention where
that job will live or how retention is bounded so reviewers know this is
intentional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 936c293c-db53-4703-85d6-e28ce8c971df

📥 Commits

Reviewing files that changed from the base of the PR and between fe055db and d52801d.

📒 Files selected for processing (10)
  • cmd/slack-bot/README.md
  • cmd/slack-bot/main.go
  • pkg/jira/fake.go
  • pkg/jira/issues.go
  • pkg/jira/issues_test.go
  • pkg/slack/events/router/router.go
  • pkg/slack/events/supportrequest/handler.go
  • pkg/slack/events/supportrequest/handler_test.go
  • pkg/slack/events/supportrequest/lock.go
  • pkg/slack/events/supportrequest/lock_test.go

Comment thread pkg/jira/fake.go
Comment on lines +45 to +50
behavior map[IssueRequest]IssueResponse
closeBehavior map[CloseIssueRequest]CloseIssueResponse
statusBehavior map[SetIssueStatusRequest]SetIssueStatusResponse
unwanted []IssueRequest
unwantedCloseCalls []CloseIssueRequest
unwantedStatusCalls []SetIssueStatusRequest
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- pkg/slack/events/supportrequest/handler_test.go package ---'
sed -n '1,40p' pkg/slack/events/supportrequest/handler_test.go

printf '\n%s\n' '--- jira fake construction / setup call sites ---'
rg -n 'jira\.NewFake|NewFake\(|CloseIssueRequest|SetIssueStatusRequest|closeBehavior|statusBehavior' \
  pkg/slack/events/supportrequest/handler_test.go pkg/jira

printf '\n%s\n' '--- exported helpers on pkg/jira.Fake ---'
rg -n 'func \(f \*Fake\) (With|Add|Expect|Register).*(CloseIssue|SetIssueStatus)' pkg/jira

Repository: openshift/ci-tools

Length of output: 3585


🏁 Script executed:

cat -n pkg/jira/fake.go

Repository: openshift/ci-tools

Length of output: 4910


🏁 Script executed:

rg -n 'func.*Fake' pkg/jira/fake.go | head -20

Repository: openshift/ci-tools

Length of output: 473


🏁 Script executed:

rg -l 'jira\.NewFake\|\.NewFake' --type go | head -10

Repository: openshift/ci-tools

Length of output: 44


🏁 Script executed:

rg -n 'NewFake|&Fake\{|jira\.Fake' --type go | grep -v '^pkg/jira/fake.go'

Repository: openshift/ci-tools

Length of output: 42890


🏁 Script executed:

sed -n '55,75p' pkg/slack/modals/bug/view_test.go

Repository: openshift/ci-tools

Length of output: 470


🏁 Script executed:

cat -n pkg/slack/modals/modaltesting/submission.go

Repository: openshift/ci-tools

Length of output: 1866


Expose public methods to configure close/status behavior.

CloseIssueRequest, SetIssueStatusRequest, and their response types are exported, and Fake.CloseIssue() and Fake.SetIssueStatus() methods are exported. However, NewFake only accepts map[IssueRequest]IssueResponse, and the behavior maps remain unexported with no public setters. Callers outside pkg/jira cannot configure behavior for close or status operations, making the added functionality unreachable.

Suggested API additions
 func NewFake(calls map[IssueRequest]IssueResponse) *Fake {
 	return &Fake{
 		behavior:       calls,
 		closeBehavior:  map[CloseIssueRequest]CloseIssueResponse{},
 		statusBehavior: map[SetIssueStatusRequest]SetIssueStatusResponse{},
 	}
 }
+
+func (f *Fake) WithCloseBehavior(calls map[CloseIssueRequest]CloseIssueResponse) *Fake {
+	f.closeBehavior = calls
+	return f
+}
+
+func (f *Fake) WithStatusBehavior(calls map[SetIssueStatusRequest]SetIssueStatusResponse) *Fake {
+	f.statusBehavior = calls
+	return f
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/jira/fake.go` around lines 45 - 50, NewFake currently only accepts
map[IssueRequest]IssueResponse and the internal maps closeBehavior and
statusBehavior are unexported, so external callers cannot configure CloseIssue
or SetIssueStatus behavior; update the API by either extending NewFake to accept
additional parameters (map[CloseIssueRequest]CloseIssueResponse and
map[SetIssueStatusRequest]SetIssueStatusResponse) or add exported setter methods
on the Fake type (e.g.,
SetCloseBehavior(map[CloseIssueRequest]CloseIssueResponse) and
SetStatusBehavior(map[SetIssueStatusRequest]SetIssueStatusResponse)) that
populate the internal closeBehavior and statusBehavior fields so Fake.CloseIssue
and Fake.SetIssueStatus can be configured from outside pkg/jira.

Comment on lines +154 to +174
// Jira issue is created, so keep this thread lock permanently by
// converting it to a durable "processed" marker.
shouldReleaseLock = false
if err := locker.MarkProcessed(event.ThreadTimeStamp); err != nil {
return true, err
}
if err := filer.SetIssueStatus(issue.Key, jira.StatusInProgress, logger); err != nil {
logger.WithError(err).WithField("issue", issue.Key).Warn("Failed to transition support request to In Progress")
}

issueURL := fmt.Sprintf("%s%s", issuesRedHatBrowseBase, issue.Key)
_, _, postErr := postMessageWithRetry(client, channelID, slack.MsgOptionText(
fmt.Sprintf(
"%s <%s|%s>\nThis ticket was created automatically after this thread exceeded the threshold of %d messages. No user action is needed and conversation can continue in this forum thread.",
supportRequestPrefix, issueURL, issue.Key, threadMessageThreshold,
),
false,
), slack.MsgOptionTS(event.ThreadTimeStamp))
if postErr != nil {
logger.WithError(postErr).Warn("Failed to post support request Jira link in Slack thread")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the post-create steps durable before MarkProcessed.

After Line 157 succeeds, this thread will not be revisited. That makes both follow-up steps effectively best-effort: a SetIssueStatus failure leaves the new Jira ticket in its default state, and a Slack post failure means handleReactionAdded can no longer find the issue key because it only scans replies for the bot’s supportRequestPrefix message. Please either persist the thread→issue mapping somewhere durable or move MarkProcessed to the point where the transition/link-back is guaranteed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/slack/events/supportrequest/handler.go` around lines 154 - 174, The
thread is marked processed too early (locker.MarkProcessed with
event.ThreadTimeStamp), making subsequent actions best-effort; either persist
the thread→issue mapping before calling locker.MarkProcessed or move the
MarkProcessed call to after the durable operations complete. Specifically update
the logic around locker.MarkProcessed, filer.SetIssueStatus(issue.Key, ...), and
the postMessageWithRetry(...) that posts supportRequestPrefix so that the
thread→issue mapping (used by handleReactionAdded) is written durably first or
MarkProcessed is invoked only after SetIssueStatus and the Slack post both
succeed (or after you persist the mapping elsewhere) to guarantee the transition
and link-back are reliable.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci bot commented Apr 17, 2026

@jmguzik: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/breaking-changes d52801d link false /test breaking-changes
ci/prow/security d52801d link false /test security
ci/prow/lint d52801d link true /test lint
ci/prow/images d52801d link true /test images

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant