pullrequest

package
v0.15.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package pullrequest holds the domain model for GitHub pull-request events and the handlers that update Slack in response. Adding a new event trigger means: write a new file in this package implementing EventHandler, register it in the composition root, add a unit test. The dispatcher and the rest of the pipeline do not change.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ApproveHandler

type ApproveHandler struct {
	// contains filtered or unexported fields
}

ApproveHandler adds a reaction when a review is submitted with state "approved".

func NewApproveHandler

func NewApproveHandler(
	messages SlackMessages,
	mappings RepoMappings,
	slackClient SlackClient,
	logger *slog.Logger,
	emoji string,
	botEmoji string,
	detector *aireview.Detector,
) *ApproveHandler

NewApproveHandler builds an ApproveHandler. detector must be non-nil; pass aireview.NewDetector(false) for the disabled state.

func (*ApproveHandler) Applicable

func (h *ApproveHandler) Applicable(e Event) bool

func (*ApproveHandler) Handle

func (h *ApproveHandler) Handle(ctx context.Context, e Event) error

type CloseHandler

type CloseHandler struct {
	// contains filtered or unexported fields
}

CloseHandler reacts to a PR being closed (merged or not). It updates the original Slack message with a [Merged]/[Closed] decoration and, if enabled, adds the corresponding reaction emoji.

func NewCloseHandler

func NewCloseHandler(
	messages SlackMessages,
	mappings RepoMappings,
	slackClient SlackClient,
	composer *slack.Composer,
	logger *slog.Logger,
	opts CloseOptions,
) *CloseHandler

NewCloseHandler builds a CloseHandler.

func (*CloseHandler) Applicable

func (h *CloseHandler) Applicable(e Event) bool

Applicable returns true when the action is "closed".

func (*CloseHandler) Handle

func (h *CloseHandler) Handle(ctx context.Context, e Event) error

Handle updates the stored Slack message and optionally adds a reaction.

type CloseOptions

type CloseOptions struct {
	ReactionsEnabled bool
	MergedEmoji      string
	ClosedEmoji      string
}

CloseOptions tunes the CloseHandler. Reactions on close are toggleable because that's how the legacy PHP service exposed them.

type CommentedHandler

type CommentedHandler struct {
	// contains filtered or unexported fields
}

CommentedHandler adds a reaction when a review is submitted or edited with state "commented".

func NewCommentedHandler

func NewCommentedHandler(
	messages SlackMessages,
	mappings RepoMappings,
	slackClient SlackClient,
	logger *slog.Logger,
	emoji string,
	botEmoji string,
	detector *aireview.Detector,
) *CommentedHandler

NewCommentedHandler builds a CommentedHandler. detector must be non-nil; pass aireview.NewDetector(false) for the disabled state.

func (*CommentedHandler) Applicable

func (h *CommentedHandler) Applicable(e Event) bool

func (*CommentedHandler) Handle

func (h *CommentedHandler) Handle(ctx context.Context, e Event) error

type Dispatcher

type Dispatcher struct {
	// contains filtered or unexported fields
}

Dispatcher routes an Event to the first registered EventHandler whose Applicable returns true. Handlers are checked in the order they were registered. If no handler matches, Dispatch logs a Debug line and returns nil (no error) so the HTTP layer still responds 200 OK.

func NewDispatcher

func NewDispatcher(logger *slog.Logger, handlers ...EventHandler) *Dispatcher

NewDispatcher builds a Dispatcher with the given handlers. Registration order is preserved. The logger receives one Debug record per event with no applicable handler — operators enable LOG_LEVEL=debug to triage silent 200 OK deliveries.

func (*Dispatcher) Dispatch

func (d *Dispatcher) Dispatch(ctx context.Context, e Event) error

Dispatch finds the first applicable handler and runs it. Errors from the chosen handler are wrapped and returned.

type DraftHandler

type DraftHandler struct {
	// contains filtered or unexported fields
}

DraftHandler reacts to a PR being converted back to draft. It removes the Slack notification and forgets the message TS — the PR will be re-announced when it's marked ready_for_review again.

func NewDraftHandler

func NewDraftHandler(
	messages SlackMessages,
	mappings RepoMappings,
	slackClient SlackClient,
	logger *slog.Logger,
) *DraftHandler

NewDraftHandler builds a DraftHandler.

func (*DraftHandler) Applicable

func (h *DraftHandler) Applicable(e Event) bool

Applicable returns true when the action is "converted_to_draft".

func (*DraftHandler) Handle

func (h *DraftHandler) Handle(ctx context.Context, e Event) error

Handle deletes the Slack message and the stored message row.

type Event

type Event struct {
	GitHubEvent string
	Action      string
	Repository  string

	PR PR

	// Review is non-nil only for pull_request_review events.
	Review *Review

	// PRComment is true for issue_comment events fired on a pull request (the
	// payload carried an issue.pull_request reference). False for comments on
	// plain issues, which CommentedHandler ignores.
	PRComment bool

	// Sender identifies the actor that fired the webhook. Type is "User"
	// for humans and "Bot" for GitHub Apps (Copilot, dependabot, …).
	Sender Sender
}

Event is the immutable record of an incoming pull-request-related webhook, detached from any HTTP payload type. Handlers receive Event and decide via Applicable whether they should run.

type EventHandler

type EventHandler interface {
	Applicable(Event) bool
	Handle(context.Context, Event) error
}

EventHandler is implemented by each PR-lifecycle handler.

Applicable inspects an event and returns true if Handle should run. The dispatcher invokes the first handler whose Applicable returns true and skips the rest — handlers are mutually exclusive.

type OpenHandler

type OpenHandler struct {
	// contains filtered or unexported fields
}

OpenHandler reacts to a PR being opened (non-draft) or marked ready_for_review. It posts the first Slack message for the PR and records the message TS for later updates.

func NewOpenHandler

func NewOpenHandler(
	messages SlackMessages,
	mappings RepoMappings,
	slackClient SlackClient,
	composer *slack.Composer,
	logger *slog.Logger,
	dependabotFormat bool,
) *OpenHandler

NewOpenHandler builds an OpenHandler. When dependabotFormat is true, PRs opened by dependabot[bot]/renovate[bot] get the compact composer template; when false they fall back to the standard "please review" message.

func (*OpenHandler) Applicable

func (h *OpenHandler) Applicable(e Event) bool

Applicable returns true for "ready_for_review", or "opened" on a non-draft PR.

func (*OpenHandler) Handle

func (h *OpenHandler) Handle(ctx context.Context, e Event) error

Handle posts the initial Slack message and stores its TS.

Idempotency: if a SlackMessage already exists for this PR (same composite key) the handler returns silently — we never post twice for the same PR.

type PR

type PR struct {
	Number int
	Title  string
	URL    string
	Author string
	Merged bool
	Draft  bool
	// Body is the PR description, consulted by OpenHandler to tell a
	// Dependabot/Renovate security advisory from a routine bump.
	Body string
	// CreatedAt is the PR's open time (pull_request.created_at), rendered as a
	// localized date token in the Slack context line. Zero when the payload
	// omits it.
	CreatedAt time.Time
}

PR holds the PR fields needed across handlers and the message composer.

type RepoMappings

type RepoMappings interface {
	Get(ctx context.Context, repository string) (store.RepoMapping, error)
}

RepoMappings reads the per-repository routing to a Slack channel.

type RequestChangeHandler

type RequestChangeHandler struct {
	// contains filtered or unexported fields
}

RequestChangeHandler adds a reaction when a review is submitted with state "changes_requested".

func NewRequestChangeHandler

func NewRequestChangeHandler(
	messages SlackMessages,
	mappings RepoMappings,
	slackClient SlackClient,
	logger *slog.Logger,
	emoji string,
	botEmoji string,
	detector *aireview.Detector,
) *RequestChangeHandler

NewRequestChangeHandler builds a RequestChangeHandler. detector must be non-nil; pass aireview.NewDetector(false) for the disabled state.

func (*RequestChangeHandler) Applicable

func (h *RequestChangeHandler) Applicable(e Event) bool

func (*RequestChangeHandler) Handle

func (h *RequestChangeHandler) Handle(ctx context.Context, e Event) error

type Review

type Review struct {
	State string
}

Review carries the review state (approved | commented | changes_requested).

type Sender added in v0.5.0

type Sender struct {
	Login string
	Type  string
}

Sender identifies the actor on the webhook payload (the reviewer for review events, the PR author for `pull_request` opened events, etc.).

type SlackClient

type SlackClient interface {
	PostMessage(ctx context.Context, channel string, msg slack.Message) (ts string, err error)
	UpdateMessage(ctx context.Context, channel, ts string, msg slack.Message) error
	DeleteMessage(ctx context.Context, channel, ts string) error
	AddReaction(ctx context.Context, channel, ts, name string) error
}

SlackClient is the subset of the slack package's client that handlers use.

type SlackMessages

type SlackMessages interface {
	Save(ctx context.Context, m store.SlackMessage) error
	Get(ctx context.Context, repository string, prNumber int) (store.SlackMessage, error)
	Delete(ctx context.Context, repository string, prNumber int) error
}

SlackMessages reads and writes the per-PR Slack message TS record.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL