jira

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Jun 2, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package jira — Board service for /rest/agile/1.0.

Wire shapes and field semantics live in the boards HTTP contract and data-model.md. This file holds the Board struct, BoardScope helper, BoardService interface, and the drain/resolve implementations.

Index

Constants

View Source
const DefaultWorkdaySeconds = 28800

Variables

View Source
var ErrBoardNotFound = errors.New("board not found in cache")

ErrBoardNotFound is returned by ResolveOne when the cache contains no board matching the supplied name (case-insensitive exact match). CLI maps to exit 2 (not_found).

View Source
var ErrUserNotFound = errors.New("user not found")

ErrUserNotFound is returned by ResolveUser when /user/search yields zero matches for the supplied query. The CLI surface maps this to exit 2 per the constitution error contract (resource-not-found).

View Source
var IssueListFields = append([]string(nil), defaultIssueListFields...)

Functions

func Bool

func Bool(v bool) *bool

func DefaultBoardMissingMessage

func DefaultBoardMissingMessage(profile, name string) string

DefaultBoardMissingMessage returns the pinned wording the CLI emits when `default_board` references a name that does not resolve in the boards cache. Both arguments are interpolated verbatim — `profile` is the active profile name (e.g. "default" / "work"), `name` is the configured default_board value. Wording is pinned so the contract test can assert on it; any change here is a spec change too.

func DefaultIssueListFields

func DefaultIssueListFields() []string

func DrainSearch

func DrainSearch(ctx context.Context, svc SearchService, req *SearchRequest, opts DrainOptions) ([]*Issue, DrainInfo, error)

DrainSearch pulls every page from a SearchService.JQL call, respecting the configured bounds. Unbounded=true bypasses the bounds entirely; this is what the CLI's --unbounded flag wires.

func Int

func Int(v int) *int

func JQLValue

func JQLValue(value string) string

func MaxAttachmentUploadBytes

func MaxAttachmentUploadBytes() int64

func NormalizeBoardName

func NormalizeBoardName(name string) string

NormalizeBoardName strips leading/trailing whitespace from board names while preserving internal whitespace and Unicode verbatim. Called by the cache prime path before persisting each board, so the cache file holds clean names while the wire shape is left untouched.

func ParseDuration

func ParseDuration(input string, workdaySeconds int) (int, error)

func RESTPath

func RESTPath(parts ...string) string
func SortIssueLinks(links []IssueLinkView)

func StatusCounts

func StatusCounts(issues []*Issue) map[string]int

func String

func String(v string) *string

Types

type APIError

type APIError struct {
	StatusCode int
	Type       ErrorType
	Message    string
	// ErrorMessages mirrors the Jira ErrorCollection.errorMessages array
	// (general, non-field-scoped human messages). Nil when the body
	// carried none. Never used as a branch target — wording is not
	// contractual.
	ErrorMessages []string
	// FieldErrors mirrors the Jira ErrorCollection.errors map
	// (field-name -> single human message). Nil when the body carried
	// none.
	FieldErrors map[string]string
	// UpstreamStatus is the integer ErrorCollection.status field when the
	// body carried one. Zero when absent.
	UpstreamStatus int
	// RetryAfterSeconds is the Retry-After header value on a 429
	// response. Zero when the header was absent — callers fall back to
	// exponential backoff with jitter.
	RetryAfterSeconds int
	// RateLimitRemaining is the X-RateLimit-Remaining header value when
	// Jira sends it. Zero can mean either absent or exhausted; callers use
	// it as diagnostic context only.
	RateLimitRemaining int
	// Cause preserves transport/body/JSON failures for errors.Is/As while
	// keeping the public APIError message stable.
	Cause error
}

APIError is the typed error returned for any non-2xx Jira REST response or transport failure. Beyond the HTTP status and a display message it preserves the schema-backed fields of Jira Cloud's ErrorCollection body (errorMessages[], errors map, status) and the rate-limit Retry-After header, all as optional metadata. Jira exposes no stable machine error code, so APIError carries none — callers derive a normalized code from the HTTP status.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

type AmbiguousBoardError

type AmbiguousBoardError struct {
	Query      string
	Candidates []Board
}

AmbiguousBoardError is returned by ResolveOne when 2+ boards match the supplied name (e.g. same name in different projects). The cmd layer renders Candidates in the disambiguation envelope. CLI maps to exit 3 (validation).

func (*AmbiguousBoardError) Error

func (e *AmbiguousBoardError) Error() string

type AmbiguousUserError

type AmbiguousUserError struct {
	Query      string
	Candidates []*User
}

AmbiguousUserError is returned by ResolveUser when /user/search yields 2+ matches and we refuse to pick a winner. Candidates carries every returned User so the CLI can render the disambiguation envelope.

func (*AmbiguousUserError) Error

func (e *AmbiguousUserError) Error() string

type Attachment

type Attachment struct {
	ID       *string `json:"id,omitempty"`
	Self     *string `json:"self,omitempty"`
	Filename *string `json:"filename,omitempty"`
	MimeType *string `json:"mimeType,omitempty"`
	Size     *int64  `json:"size,omitempty"`
	Author   *User   `json:"author,omitempty"`
	Created  *string `json:"created,omitempty"`
	// Content is Atlassian's signed download URL. The CLI doesn't expose
	// this in the --json envelope (the download command goes through
	// /attachment/content/{id} instead), but it's preserved for callers
	// that want to embed the URL directly.
	Content *string `json:"content,omitempty"`
}

Attachment is a file attached to a Jira issue. Pointer-typed nullable fields follow the rest of pkg/jira (Issue, Comment, …) so JSON round-tripping preserves "field absent" vs "field present and empty".

Wire shape: GET /rest/api/3/issue/{key}?fields=attachment returns `fields.attachment[]` populated with this shape; POST .../attachments returns an array of these directly.

type AttachmentService

type AttachmentService interface {
	List(ctx context.Context, key string) ([]Attachment, *Response, error)
	Add(ctx context.Context, key string, files []FileSource) ([]Attachment, *Response, error)
	Delete(ctx context.Context, attachmentID string) (*Response, error)
	Download(ctx context.Context, attachmentID string) (io.ReadCloser, *Response, error)
}

AttachmentService exposes Atlassian's attachment endpoints. Mutation methods follow the same pointer-based response convention as IssueService so callers can distinguish "no field" from "empty field".

Design notes:

  • Add uses multipart/form-data with X-Atlassian-Token: no-check and form field name "file" — both required by Atlassian.
  • Delete is global by attachment id (Atlassian's endpoint is /attachment/{id}, not nested under the issue).
  • Download streams via io.Copy in the caller; this method returns the raw response body.

func NewAttachmentService

func NewAttachmentService(client *Client) AttachmentService

NewAttachmentService binds an AttachmentService to the given client.

type AvatarURLs

type AvatarURLs struct {
	Size16 string `json:"16x16,omitempty"`
	Size24 string `json:"24x24,omitempty"`
	Size32 string `json:"32x32,omitempty"`
	Size48 string `json:"48x48,omitempty"`
}

AvatarURLs maps Jira's standard avatar size keys to URLs.

type Board

type Board struct {
	ID          *int     `json:"id,omitempty"`
	Self        *string  `json:"self,omitempty"`
	Name        *string  `json:"name,omitempty"`
	Type        *string  `json:"type,omitempty"`
	ProjectKeys []string `json:"project_keys"`
}

Board is a Jira agile board fetched via /rest/agile/1.0/board and (for the project list) /rest/agile/1.0/board/{id}/project. Pointer-typed nullable fields mirror Issue/Comment/Attachment: distinguishes "field absent" from "present and zero".

func DecodeBoardsCache

func DecodeBoardsCache(data []byte) ([]Board, error)

DecodeBoardsCache parses the on-disk boards-cache payload. The earlier resolver tests stored the boards as a bare JSON array; the 003 prime path persists the BoardsCache envelope. This helper accepts both so the migration is invisible to callers.

type BoardDrainOptions

type BoardDrainOptions struct {
	PageSize   int
	MaxPages   int
	MaxResults int
	Unbounded  bool
}

BoardDrainOptions configures BoardService.ListAll. Defaults: PageSize 50, MaxPages 100, MaxResults 10 000. Set Unbounded to disable both bounds.

type BoardDrainResult

type BoardDrainResult struct {
	Boards          []*Board
	LastResp        *Response
	PagesFetched    int
	RateLimitHit    *APIError
	Truncated       bool
	TruncatedReason string
}

BoardDrainResult carries the outcome of ListAll: every board fetched, the rate-limit APIError that aborted the walk if one fired (partial-success), and the truncation reason if a bound stopped the walk short of isLast.

type BoardScope

type BoardScope struct {
	Board      Board
	Precedence string
}

BoardScope is what BoardService.ResolveOne returns: the resolved board plus the precedence path that selected it (one of "flag", "default_board", "none").

func (BoardScope) JQLClause

func (s BoardScope) JQLClause() (string, bool)

JQLClause emits `project in (P1, P2, ...)` from the board's cached project keys. The bool result is the canonical "did this scope produce a clause?" signal — every consumer that needs to branch on emission (envelope's `applied` flag, JQL prepender) reads it from here so no caller has to re-derive `len(ProjectKeys) > 0`.

type BoardService

type BoardService interface {
	List(ctx context.Context, opts *ListOptions) ([]Board, *Response, error)
	ListAll(ctx context.Context, opts BoardDrainOptions) (BoardDrainResult, error)
	ProjectsForBoard(ctx context.Context, boardID int) ([]string, *Response, error)
	ResolveOne(ctx context.Context, profile, name string) (BoardScope, error)
}

BoardService groups the agile board endpoints used by the boards cache primer, the `boards list` command, and the --board resolver.

func NewBoardService

func NewBoardService(client *Client) BoardService

NewBoardService binds a BoardService to the supplied client.

type BoardsCacheFile

type BoardsCacheFile struct {
	FetchedAt         string  `json:"fetched_at"`
	TTLSeconds        int     `json:"ttl_seconds"`
	Truncated         bool    `json:"truncated"`
	TruncatedReason   string  `json:"truncated_reason"`
	PagesFetched      int     `json:"pages_fetched,omitempty"`
	RetryAfterSeconds int     `json:"retry_after_seconds,omitempty"`
	Items             []Board `json:"items"`
}

BoardsCacheFile is the on-disk envelope persisted under `${XDG_CACHE_HOME:-~/.cache}/jira-cli/<profile>/boards.json` per data-model.md > BoardsCache. Carried inside the generic cache.Entry's Data field so the existing internal/cache machinery (atomic write, 0600 perms, TTL freshness) round-trips it unchanged.

type Client

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

func NewClient

func NewClient(opts ...Option) *Client

func NewClientE

func NewClientE(opts ...Option) (*Client, error)

func (*Client) BaseURL

func (c *Client) BaseURL() *url.URL

BaseURL exposes the resolved base URL so services that need to build fully-qualified URLs without going through NewRequest (multipart, streaming download) can ResolveReference against the same root the rest of the package uses.

func (*Client) Do

func (c *Client) Do(req *http.Request, out any) (*Response, error)

func (*Client) NewRawRequest

func (c *Client) NewRawRequest(ctx context.Context, method, path string, body io.Reader) (*http.Request, error)

func (*Client) NewRequest

func (c *Client) NewRequest(ctx context.Context, method, path string, body any) (*http.Request, error)

func (*Client) SignRequest

func (c *Client) SignRequest(req *http.Request)

SignRequest applies the active profile's Jira Cloud auth to req: HTTP Basic with the account email and API token. Services that build requests outside NewRequest (multipart upload, raw-body posts, streaming download) MUST call this rather than re-implementing the basic-auth selection inline so the auth surface stays consistent.

type Comment

type Comment struct {
	ID           *string       `json:"id,omitempty"`
	Self         *string       `json:"self,omitempty"`
	Body         *adf.Document `json:"body,omitempty"`
	Author       *User         `json:"author,omitempty"`
	UpdateAuthor *User         `json:"updateAuthor,omitempty"`
	Created      *string       `json:"created,omitempty"`
	Updated      *string       `json:"updated,omitempty"`
	Visibility   *Visibility   `json:"visibility,omitempty"`
}

type CommentAddRequest

type CommentAddRequest struct {
	Body   adf.Document `json:"body"`
	DryRun bool         `json:"-"`
}

type CommentBody

type CommentBody struct {
	ADF *adf.Document
	// DryRun short-circuits the HTTP call. The service returns a synthetic
	// {ID: "DRY-RUN"} Comment so callers can keep their envelope flow.
	DryRun bool
}

CommentBody carries the comment payload. Exactly one of ADF or Markdown must be supplied; the cmd-layer binding converts Markdown → ADF before constructing the body.

type CommentDrainOptions

type CommentDrainOptions struct {
	PageSize   int
	MaxPages   int
	MaxResults int
	Unbounded  bool
}

CommentDrainOptions configures CommentService.ListAll. Mirrors DrainOptions on the search side so consumer-side opt-in walks have consistent runaway protection. Defaults: PageSize 50, MaxPages 100, MaxResults 10_000. Set Unbounded to disable both bounds (the CLI requires --unbounded; the TUI never sets it).

type CommentDrainResult

type CommentDrainResult struct {
	Comments        []*Comment
	LastResp        *Response
	PagesFetched    int
	RateLimitHit    *APIError
	Truncated       bool
	TruncatedReason string
}

CommentDrainResult carries the outcome of ListAll: every comment fetched, the final page's response (for envelope pagination metadata), and the rate-limit APIError that aborted the walk if one fired (partial-success semantics — exit 0 with a structured warning). Truncated reports whether one of the configured bounds stopped the walk short of isLast.

type CommentPage

type CommentPage struct {
	Comments []*Comment `json:"comments,omitempty"`
}

type CommentService

type CommentService interface {
	List(ctx context.Context, key string, opts *ListCommentsOptions) ([]*Comment, *Response, error)
	ListAll(ctx context.Context, key string, opts CommentDrainOptions) (CommentDrainResult, error)
	Add(ctx context.Context, key string, body *CommentBody) (*Comment, *Response, error)
	AddWithVisibility(ctx context.Context, key string, body *CommentBody, vis VisibilityChange) (*Comment, *Response, error)
	Edit(ctx context.Context, key, commentID string, body *CommentBody, vis VisibilityChange) (*Comment, *Response, error)
	Delete(ctx context.Context, key, commentID string) (*Response, error)
}

CommentService groups the Jira-issue comment endpoints. Split out of IssueService per plan.md "Service Architecture" — comments deserve a service of their own now that the lifecycle covers list / add / edit / delete instead of just add.

All methods use the underlying *Client's auth, debug, retry, and rate-limit hooks. Pagination on List follows the same ListOptions shape the rest of pkg/jira already exposes.

func NewCommentService

func NewCommentService(client *Client) CommentService

NewCommentService constructs a CommentService bound to the given client.

type Component

type Component struct {
	Name *string `json:"name,omitempty"`
}

type CurrentUser

type CurrentUser struct {
	Self         string     `json:"self,omitempty"`
	AccountID    string     `json:"accountId,omitempty"`
	AccountType  string     `json:"accountType,omitempty"`
	EmailAddress string     `json:"emailAddress,omitempty"`
	DisplayName  string     `json:"displayName,omitempty"`
	Active       bool       `json:"active,omitempty"`
	TimeZone     string     `json:"timeZone,omitempty"`
	Locale       string     `json:"locale,omitempty"`
	AvatarURLs   AvatarURLs `json:"avatarUrls,omitempty"`
}

User identity returned by /rest/api/3/myself. Subset of the full response — only the fields we use for assign-to-me, profile bootstrap and `whoami`.

type DrainInfo

type DrainInfo struct {
	PagesFetched    int
	Truncated       bool
	TruncatedReason string
}

DrainInfo describes how DrainSearch terminated. Truncated reports whether a bound stopped consumption short of isLast.

type DrainOptions

type DrainOptions struct {
	MaxPages   int
	MaxResults int
	Unbounded  bool
}

DrainOptions configures DrainSearch — the bounded `--all` consumer.

Defaults: max-pages 100, max-results-total 10,000. Override either with explicit positive values; set Unbounded to disable both (CLI requires --unbounded; the TUI never sets it).

type Epic

type Epic struct {
	Key     *string `json:"key,omitempty"`
	Summary *string `json:"summary,omitempty"`
	Status  *string `json:"status,omitempty"`
}

type EpicService

type EpicService interface {
	List(context.Context, *ListOptions) ([]*Issue, *Response, error)
	Get(context.Context, string, *IssueGetOptions) (*Issue, *Response, error)
	AddIssue(context.Context, string, string) (*Response, error)
	RemoveIssue(context.Context, string) (*Response, error)
	IssuesInEpic(context.Context, string) ([]*Issue, *Response, error)
}

func NewEpicService

func NewEpicService(client *Client) EpicService

type ErrorType

type ErrorType string
const (
	ErrorTypeAuth       ErrorType = "auth"
	ErrorTypeNotFound   ErrorType = "not_found"
	ErrorTypeValidation ErrorType = "validation"
	ErrorTypeRateLimit  ErrorType = "rate_limit"
	ErrorTypeServer     ErrorType = "server"
)

type FieldSchema

type FieldSchema struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Required bool   `json:"required"`
	Type     string `json:"type"`
	// Custom is the schema.custom token Jira reports for a custom
	// field — the trailing identifier of
	// com.atlassian.jira.plugin.system.customfieldtypes:* (e.g.
	// "select", "datepicker", "float"). Empty for system fields and for
	// custom fields whose type Jira does not expose. It is the branch
	// key the mutation pipeline uses to encode a custom-field value.
	Custom string `json:"custom,omitempty"`
}

type FileSource

type FileSource struct {
	Name   string
	Size   int64
	Reader io.Reader
}

FileSource is what AttachmentService.Add accepts per file: a name (the filename Jira sees) plus a Reader carrying the bytes. Using io.Reader lets callers stream from *os.File without slurping into memory; the service implementation routes large payloads through io.Pipe .

type Issue

type Issue struct {
	ID           *string      `json:"id,omitempty"`
	Key          *string      `json:"key,omitempty"`
	Self         *string      `json:"self,omitempty"`
	Fields       *IssueFields `json:"fields,omitempty"`
	Comments     []*Comment   `json:"comments,omitempty"`
	Worklogs     []*Worklog   `json:"worklogs,omitempty"`
	LinkedIssues []*Issue     `json:"linked_issues,omitempty"`
	Subtasks     []*Issue     `json:"subtasks,omitempty"`
}

func (*Issue) UnmarshalJSON

func (i *Issue) UnmarshalJSON(data []byte) error

type IssueCloneRequest

type IssueCloneRequest struct {
	Fields map[string]any `json:"fields,omitempty"`
	DryRun bool           `json:"-"`
}

type IssueCreateRequest

type IssueCreateRequest struct {
	Project   string         `json:"project,omitempty"`
	IssueType string         `json:"issuetype,omitempty"`
	Summary   string         `json:"summary,omitempty"`
	Fields    map[string]any `json:"fields,omitempty"`
	DryRun    bool           `json:"-"`
}

type IssueDeleteOptions

type IssueDeleteOptions struct {
	DeleteSubtasks bool
}

IssueDeleteOptions controls deletion behavior. DeleteSubtasks=true removes the issue and all its subtasks atomically (Jira refuses the delete otherwise when subtasks exist).

type IssueFields

type IssueFields struct {
	Summary      *string                    `json:"summary,omitempty"`
	Description  *adf.Document              `json:"description,omitempty"`
	Status       *Status                    `json:"status,omitempty"`
	Assignee     *User                      `json:"assignee,omitempty"`
	Reporter     *User                      `json:"reporter,omitempty"`
	Priority     *Priority                  `json:"priority,omitempty"`
	Labels       []string                   `json:"labels,omitempty"`
	Components   []Component                `json:"components,omitempty"`
	Updated      *string                    `json:"updated,omitempty"`
	Comment      *CommentPage               `json:"comment,omitempty"`
	Worklog      *WorklogPage               `json:"worklog,omitempty"`
	IssueLinks   []IssueLink                `json:"issuelinks,omitempty"`
	Subtasks     []*Issue                   `json:"subtasks,omitempty"`
	CustomFields map[string]json.RawMessage `json:"-"`
}

func (IssueFields) MarshalJSON

func (f IssueFields) MarshalJSON() ([]byte, error)

func (*IssueFields) UnmarshalJSON

func (f *IssueFields) UnmarshalJSON(data []byte) error

type IssueGetOptions

type IssueGetOptions struct {
	Expand []string
}
type IssueLink struct {
	InwardIssue  *Issue `json:"inwardIssue,omitempty"`
	OutwardIssue *Issue `json:"outwardIssue,omitempty"`
}

type IssueLinkRequest

type IssueLinkRequest struct {
	Type         string
	InwardIssue  string
	OutwardIssue string
}

IssueLinkRequest creates an issue link via POST /rest/api/3/issueLink. Type is the link-type name (e.g., "Blocks", "Relates", "Cloners"). InwardIssue and OutwardIssue are issue keys; semantics depend on the type ("Blocks" → outwardIssue is blocked by inwardIssue).

type IssueLinkService

type IssueLinkService interface {
	List(context.Context, string) ([]IssueLinkView, *Response, error)
	Create(context.Context, *IssueLinkRequest) (*Response, error)
	Delete(context.Context, string) (*Response, error)
}

func NewIssueLinkService

func NewIssueLinkService(client *Client) IssueLinkService

type IssueLinkType

type IssueLinkType struct {
	ID      string `json:"id"`
	Name    string `json:"name"`
	Inward  string `json:"inward"`
	Outward string `json:"outward"`
	Self    string `json:"self,omitempty"`
}

type IssueLinkTypeService

type IssueLinkTypeService interface {
	List(context.Context) ([]IssueLinkType, *Response, error)
}

func NewIssueLinkTypeService

func NewIssueLinkTypeService(client *Client) IssueLinkTypeService

type IssueLinkView

type IssueLinkView struct {
	ID         string        `json:"id"`
	Self       string        `json:"self,omitempty"`
	Type       IssueLinkType `json:"type"`
	Direction  string        `json:"direction"`
	OtherIssue IssueRef      `json:"other_issue"`
}

type IssueListOptions

type IssueListOptions struct {
	ListOptions
	JQL    string
	Fields []string
	Expand []string
}

type IssueMoveRequest

type IssueMoveRequest struct {
	Fields map[string]any `json:"fields,omitempty"`
	DryRun bool           `json:"-"`
}

type IssueRef

type IssueRef struct {
	Key     string `json:"key"`
	Summary string `json:"summary,omitempty"`
	Status  string `json:"status,omitempty"`
}

type IssueService

type IssueService interface {
	List(context.Context, *IssueListOptions) ([]*Issue, *Response, error)
	Get(context.Context, string, *IssueGetOptions) (*Issue, *Response, error)
	Create(context.Context, *IssueCreateRequest) (*Issue, *Response, error)
	Update(context.Context, string, *IssueUpdateRequest) (*Issue, *Response, error)
	Delete(context.Context, string, *IssueDeleteOptions) (*Response, error)
	Clone(context.Context, string, *IssueCloneRequest) (*Issue, *Response, error)
	Move(context.Context, string, *IssueMoveRequest) (*Issue, *Response, error)
	Transitions(context.Context, string) ([]*Transition, *Response, error)
	Transition(context.Context, string, *TransitionRequest) (*Response, error)
	AddComment(context.Context, string, *CommentAddRequest) (*Comment, *Response, error)
	// Link creates an issue link between two issues (Blocks, Relates,
	// etc.). Jira's bulk-edit endpoint rejects issuelinks updates;
	// links require this dedicated endpoint.
	Link(context.Context, *IssueLinkRequest) (*Response, error)
	// AddRemoteLink attaches a web link (URL + title) to an issue —
	// the Jira "Web links" feature. Different endpoint from Link.
	AddRemoteLink(context.Context, string, *RemoteLinkRequest) (*Response, error)
}

func NewIssueService

func NewIssueService(client *Client) IssueService

type IssueUpdateRequest

type IssueUpdateRequest struct {
	Fields map[string]any `json:"fields"`
	DryRun bool           `json:"-"`
}

type LabelService

type LabelService interface {
	List(context.Context, *ListOptions) ([]string, *Response, error)
}

LabelService surfaces Jira's global label list. Labels in Jira are not scoped to a project — they're a global string pool — so callers generally fetch once per profile and reuse via the local cache.

func NewLabelService

func NewLabelService(client *Client) LabelService

NewLabelService constructs a LabelService bound to the given client.

type ListCommentsOptions

type ListCommentsOptions struct {
	ListOptions
	// OrderBy lets callers override the default ordering. Empty string =
	// "created" (oldest-first), matching Atlassian's native default.
	OrderBy string
}

ListCommentsOptions controls pagination and ordering for comment list. Atlassian's `/issue/{key}/comment` endpoint returns oldest-first by default when `orderBy=created` is supplied; we send it explicitly so the contract is stable across instances.

type ListOptions

type ListOptions struct {
	StartAt       int
	MaxResults    int
	NextPageToken string
}

func (ListOptions) QueryValues

func (o ListOptions) QueryValues() url.Values

type LossyCommentWarning

type LossyCommentWarning struct {
	Type            string   `json:"type"`
	CommentID       string   `json:"comment_id"`
	LossyConstructs []string `json:"lossy_constructs"`
}

LossyCommentWarning is the structured warning shape emitted under envelope.warnings[] when a comment's ADF body lost fidelity during the Markdown render path. CommentID lets the consumer cross-reference the affected comment in data.comments[]; LossyConstructs lists the dropped node/mark types in sorted-unique order.

func CollectLossyCommentWarnings

func CollectLossyCommentWarnings(comments []*Comment) []LossyCommentWarning

CollectLossyCommentWarnings walks comments[] and builds one warning per comment whose ADF body included nodes/marks the Markdown renderer couldn't fully express. Comments without a body are skipped silently — they're not lossy, just empty.

type Option

type Option func(*Client)

func WithBaseURL

func WithBaseURL(raw string) Option

func WithBasicAuth

func WithBasicAuth(email, token string) Option

func WithDebug

func WithDebug(debug bool) Option

func WithDryRun

func WithDryRun(dryRun bool) Option

WithDryRun causes Do to refuse any state-changing HTTP method (POST / PUT / PATCH / DELETE) so a --dry-run invocation cannot mutate Jira. Reads still pass through, so the same client can serve a dry-run preview that renders live data. This is the service-level safety net behind the command-layer dry-run branches: even a command path that forgets to gate a submission is stopped here.

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

func WithHTTPTimeout

func WithHTTPTimeout(timeout time.Duration) Option

func WithReadOnly

func WithReadOnly(readOnly bool) Option

WithReadOnly causes Do to refuse any non-safe HTTP method (POST / PUT / PATCH / DELETE) with a validation-typed APIError. Single point of control — the CLI sets this once based on env / per-profile config and every mutation in every command path is automatically gated.

type PermissionEntry

type PermissionEntry struct {
	ID             string `json:"id,omitempty"`
	Key            string `json:"key,omitempty"`
	Name           string `json:"name,omitempty"`
	Type           string `json:"type,omitempty"`
	Description    string `json:"description,omitempty"`
	HavePermission bool   `json:"havePermission"`
}

PermissionEntry is a single result of /rest/api/3/mypermissions.

type PermissionsResponse

type PermissionsResponse struct {
	Permissions map[string]PermissionEntry `json:"permissions"`
}

PermissionsResponse wraps the /mypermissions envelope.

type Priority

type Priority struct {
	Name *string `json:"name,omitempty"`
}

type ProjectFieldSchema

type ProjectFieldSchema struct {
	ProjectKey string        `json:"project_key"`
	IssueType  string        `json:"issue_type"`
	Fields     []FieldSchema `json:"fields,omitempty"`
}

type ProjectSchemaCache

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

func NewProjectSchemaCache

func NewProjectSchemaCache(ttl time.Duration) *ProjectSchemaCache

func (*ProjectSchemaCache) Get

func (c *ProjectSchemaCache) Get(profile, projectKey, issueType string) (*ProjectFieldSchema, bool)

func (*ProjectSchemaCache) InvalidateAll

func (c *ProjectSchemaCache) InvalidateAll()

func (*ProjectSchemaCache) InvalidateProfile

func (c *ProjectSchemaCache) InvalidateProfile(profile string)

func (*ProjectSchemaCache) Set

func (c *ProjectSchemaCache) Set(profile, projectKey, issueType string, schema *ProjectFieldSchema)

type ProjectService

type ProjectService interface {
	GetFieldSchema(context.Context, string, string) (*ProjectFieldSchema, *Response, error)
	GetFieldSchemaForProfile(context.Context, string, string, string) (*ProjectFieldSchema, *Response, error)
	// GetEditSchemaForProfile resolves the edit-screen field schema for
	// one issue via GET /rest/api/3/issue/{idOrKey}/editmeta. The
	// edit/move flows validate against this; createmeta covers
	// create/clone. Cached per profile + issue key.
	GetEditSchemaForProfile(context.Context, string, string) (*ProjectFieldSchema, *Response, error)
	List(context.Context, *ListOptions) ([]ProjectSummary, *Response, error)
}

func NewProjectService

func NewProjectService(client *Client, ttl time.Duration) ProjectService

type ProjectSummary

type ProjectSummary struct {
	ID          string `json:"id"`
	Key         string `json:"key"`
	Name        string `json:"name"`
	ProjectType string `json:"project_type,omitempty"`
	Lead        string `json:"lead,omitempty"`
}

ProjectSummary is the discovery shape for `jira cache projects` (and shell completion). Subset of /rest/api/3/project/search; only the keys agents and humans need.

type QueryOptions

type QueryOptions struct {
	ListOptions
	JQL string
}

type Rate

type Rate struct {
	Remaining         int
	RetryAfterSeconds int
	Reset             time.Time
}

type RemoteLinkRequest

type RemoteLinkRequest struct {
	URL   string
	Title string
}

RemoteLinkRequest creates a "web link" via POST /rest/api/3/issue/{key}/remotelink. Title is what the user sees; URL is where the link points.

type Response

type Response struct {
	Response      *http.Response
	StartAt       int
	MaxResults    int
	Total         int
	IsLast        bool
	NextPageToken string
	TokenPage     bool
	Rate          Rate
	RawBody       json.RawMessage
}

func (Response) NextCursor

func (r Response) NextCursor() string

type SearchRequest

type SearchRequest struct {
	JQL           string `json:"jql,omitempty"`
	MaxResults    int    `json:"maxResults,omitempty"`
	NextPageToken string `json:"nextPageToken,omitempty"`
	Fields        []string
	Expand        []string
	ListOptions   `json:"-"`
}

type SearchResult

type SearchResult struct {
	Issues        []*Issue `json:"issues,omitempty"`
	IsLast        bool     `json:"isLast,omitempty"`
	NextPageToken string   `json:"nextPageToken,omitempty"`
}

type SearchService

type SearchService interface {
	JQL(context.Context, *SearchRequest) ([]*Issue, *Response, error)
}

func NewSearchService

func NewSearchService(client *Client) SearchService

type Status

type Status struct {
	Name           *string         `json:"name,omitempty"`
	StatusCategory *StatusCategory `json:"statusCategory,omitempty"`
}

type StatusCategory added in v0.3.0

type StatusCategory struct {
	Key  *string `json:"key,omitempty"`
	Name *string `json:"name,omitempty"`
}

StatusCategory is the workflow bucket a status belongs to. Key is one of "new", "indeterminate", or "done" — stable across projects and used to color statuses by category in human output.

type Transition

type Transition struct {
	ID   *string `json:"id,omitempty"`
	Name *string `json:"name,omitempty"`
}

type TransitionRequest

type TransitionRequest struct {
	ID     string
	Fields map[string]any
	DryRun bool
}

type User

type User struct {
	AccountID    *string `json:"accountId,omitempty"`
	AccountType  *string `json:"accountType,omitempty"`
	DisplayName  *string `json:"displayName,omitempty"`
	EmailAddress *string `json:"emailAddress,omitempty"`
	Active       *bool   `json:"active,omitempty"`
	Deleted      *bool   `json:"deleted,omitempty"`
}

type UserService

type UserService interface {
	Myself(context.Context) (*CurrentUser, *Response, error)
	MyPermissions(ctx context.Context, projectKey string, keys []string) (*PermissionsResponse, *Response, error)
	Search(ctx context.Context, query string) ([]*User, *Response, error)
	ResolveAccountID(ctx context.Context, accountID string) (string, error)
	ResolveUser(ctx context.Context, query string) (string, error)
}

UserService exposes user-identity endpoints.

func NewUserService

func NewUserService(client *Client) UserService

NewUserService constructs a UserService bound to the given client.

type Visibility

type Visibility struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

Visibility restricts a comment to a Jira role or a Jira group. Atlassian's data model treats Type/Value as mutually exclusive across role and group; the CLI's --visibility-role and --visibility-group flags enforce that exclusivity locally before any HTTP call ().

type VisibilityChange

type VisibilityChange struct {
	Mode  VisibilityMode
	Type  string // "role" or "group"; only meaningful when Mode == VisibilityReplace
	Value string // role/group name; only meaningful when Mode == VisibilityReplace
}

VisibilityChange is the typed, validated input to CommentService.Edit. Construct via ParseVisibilityChange so the mutex-flag rules are enforced before the wire body is built.

func ParseVisibilityChange

func ParseVisibilityChange(flags VisibilityFlags) (VisibilityChange, error)

ParseVisibilityChange validates the flag combination and produces a VisibilityChange. Returns an error (mapped to exit 3 by the cmd layer) when:

  • --visibility-role and --visibility-group are both supplied
  • --clear-visibility is combined with either visibility-role or visibility-group
  • --visibility-role is supplied with an empty value
  • --visibility-group is supplied with an empty value

All three flags omitted → VisibilityKeep (preserve-when-omitted).

type VisibilityFlags

type VisibilityFlags struct {
	RoleSet  bool
	Role     string
	GroupSet bool
	Group    string
	Clear    bool
}

VisibilityFlags is the cmd-layer's view of the three CLI flags. The *Set fields distinguish "flag was supplied" from "flag was supplied with empty value" — cobra's IsChanged() drives the booleans.

type VisibilityMode

type VisibilityMode int

VisibilityMode is the discriminator on a VisibilityChange.

const (
	// VisibilityKeep leaves the existing visibility untouched. The wire
	// body MUST omit the visibility key entirely so Atlassian preserves
	// whatever was previously set.
	VisibilityKeep VisibilityMode = iota
	// VisibilityReplace sets a new restriction (role or group). The wire
	// body sends `"visibility": {"type": ..., "value": ...}`.
	VisibilityReplace
	// VisibilityClear removes the existing restriction. The wire body
	// sends `"visibility": null` explicitly so Atlassian clears it.
	VisibilityClear
)

type WatcherService

type WatcherService interface {
	List(ctx context.Context, issueKey string) (*WatchersResponse, *Response, error)
	Add(ctx context.Context, issueKey, accountID string) (*Response, error)
	Remove(ctx context.Context, issueKey, accountID string) (*Response, error)
}

WatcherService manages watchers on an issue.

func NewWatcherService

func NewWatcherService(client *Client) WatcherService

NewWatcherService constructs a WatcherService bound to the given client.

type WatchersResponse

type WatchersResponse struct {
	IsWatching bool    `json:"isWatching"`
	WatchCount int     `json:"watchCount"`
	Watchers   []*User `json:"watchers"`
}

WatchersResponse is the shape Atlassian returns from GET /rest/api/3/issue/{key}/watchers — the watcher list plus the caller-perspective `IsWatching` flag and instance-level `WatchCount`.

type Worklog

type Worklog struct {
	ID               *string       `json:"id,omitempty"`
	TimeSpentSeconds *int          `json:"timeSpentSeconds,omitempty"`
	Started          *string       `json:"started,omitempty"`
	Comment          *adf.Document `json:"comment,omitempty"`
}

type WorklogAddRequest

type WorklogAddRequest struct {
	TimeSpentSeconds int           `json:"timeSpentSeconds"`
	Started          string        `json:"started,omitempty"`
	Comment          *adf.Document `json:"comment,omitempty"`
	DryRun           bool          `json:"-"`
}

type WorklogPage

type WorklogPage struct {
	Worklogs []*Worklog `json:"worklogs,omitempty"`
}

type WorklogService

type WorklogService interface {
	List(context.Context, string, *ListOptions) ([]*Worklog, *Response, error)
	Add(context.Context, string, *WorklogAddRequest) (*Worklog, *Response, error)
	Delete(context.Context, string, string) (*Response, error)
}

func NewWorklogService

func NewWorklogService(client *Client) WorklogService

Directories

Path Synopsis
Package customfield is the Jira custom-field type registry.
Package customfield is the Jira custom-field type registry.

Jump to

Keyboard shortcuts

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