pullrequest

package
v0.21.1 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2026 License: MIT Imports: 10 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(
	store Store,
	behavior RepoBehavior,
	messenger Messenger,
	composer *slack.Composer,
	logger *slog.Logger,
	detector *aireview.Detector,
	reviews ReviewSessions,
) *ApproveHandler

NewApproveHandler builds an ApproveHandler.

func (*ApproveHandler) Applicable

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

func (*ApproveHandler) Handle

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

type ChangedFiles added in v0.20.0

type ChangedFiles interface {
	ListPullRequestFiles(ctx context.Context, owner, repo string, number int) ([]string, error)
}

ChangedFiles fetches the repo-relative paths a PR touches.

type CloseHandler

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

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

func NewCloseHandler

func NewCloseHandler(
	store Store,
	behavior RepoBehavior,
	messenger Messenger,
	composer *slack.Composer,
	logger *slog.Logger,
	reviews ReviewSessions,
) *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 every stored Slack message and optionally adds a reaction to each.

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(
	store Store,
	behavior RepoBehavior,
	messenger Messenger,
	composer *slack.Composer,
	logger *slog.Logger,
	detector *aireview.Detector,
	reviews ReviewSessions,
) *CommentedHandler

NewCommentedHandler builds a CommentedHandler.

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 every stored Slack message and deletes the PR row — the PR will be re-announced when it's marked ready_for_review again.

func NewDraftHandler

func NewDraftHandler(store Store, messenger Messenger, 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 every stored Slack message and the PR 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 Messenger added in v0.20.0

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

Messenger is the subset of a chat messenger the handlers use. Slack is the only implementation today (slack.Client satisfies it).

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 fans out one Slack message per resolved target channel and records each message for later updates.

func NewOpenHandler

func NewOpenHandler(
	store Store,
	resolver TargetResolver,
	messenger Messenger,
	composer *slack.Composer,
	logger *slog.Logger,
) *OpenHandler

NewOpenHandler builds an OpenHandler.

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 one message per resolved target channel and records each. It is idempotent per channel: an existing message for a channel is skipped, so a redelivery or a partial-failure retry only posts the missing channels.

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 PathMappings added in v0.20.0

type PathMappings interface {
	Get(ctx context.Context, repository string) (store.RepoMapping, error)
	RepoHasPathRules(repository string) bool
	TargetsForFiles(repository string, files []string) []store.Target
}

PathMappings is the slice of the mappings provider the Router consumes.

type RepoBehavior added in v0.20.0

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

RepoBehavior resolves a repository's per-repo behavioral config (reactions, review flags). Close/draft/review need it but not the per-channel targets.

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(
	store Store,
	behavior RepoBehavior,
	messenger Messenger,
	composer *slack.Composer,
	logger *slog.Logger,
	detector *aireview.Detector,
	reviews ReviewSessions,
) *RequestChangeHandler

NewRequestChangeHandler builds a RequestChangeHandler.

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 ReviewSessions added in v0.21.0

type ReviewSessions interface {
	GetActive(ctx context.Context, repository string, prNumber int) (store.CodeReview, error)
	Finish(ctx context.Context, repository string, prNumber int) error
	Reviewers(ctx context.Context, repository string, prNumber int) ([]store.CodeReview, error)
}

ReviewSessions is the review-session view the review and close handlers use. store.CodeReviews satisfies it.

type Router added in v0.20.0

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

Router resolves routing, layering per-path rules over the base repo/org tier when the repository configures `paths:` and a changed-files fetcher is available. With no fetcher (no GitHub token) or no path rules for the repo it is exactly the provider's Get. A fetch error is treated softly: it logs and falls back to the repo tier, so a GitHub hiccup never drops a notification.

func NewRouter added in v0.20.0

func NewRouter(mappings PathMappings, files ChangedFiles, logger *slog.Logger) *Router

NewRouter builds a Router. files may be nil (no token) — path routing is then inert and every PR resolves to its repo/org tier.

func (*Router) ResolveTargets added in v0.20.0

func (r *Router) ResolveTargets(ctx context.Context, repository string, prNumber int) (store.RepoMapping, []store.Target, error)

ResolveTargets returns the per-repo behavior plus the fan-out targets for a PR. With no fetcher (no token) or no path rules it returns a single base target. A files-API error is soft: it logs and returns the base target.

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 Store added in v0.20.0

type Store interface {
	AddMessage(ctx context.Context, repository string, prNumber int, channel, messageID string) error
	Messages(ctx context.Context, repository string, prNumber int) ([]store.Message, error)
	Touch(ctx context.Context, repository string, prNumber int) error
	MarkClosed(ctx context.Context, repository string, prNumber int) error
	Delete(ctx context.Context, repository string, prNumber int) error
}

Store persists tracked PRs and their per-channel messages.

type TargetResolver added in v0.20.0

type TargetResolver interface {
	ResolveTargets(ctx context.Context, repository string, prNumber int) (store.RepoMapping, []store.Target, error)
}

TargetResolver resolves the open fan-out: per-repo behavior + per-channel targets.

Jump to

Keyboard shortcuts

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