Documentation
¶
Overview ¶
Package jira is the Atlassian Cloud REST client: read paths plus user-initiated writes.
The token lives only in the Authorization header. It is never put in an error, a log line or a URL (constitution article 8), which is why transport reports the method and path but never the request itself.
Index ¶
- Constants
- Variables
- func Category(key string) string
- func Doc(text string, mentions map[string]string) json.RawMessage
- func DocWithMedia(text string, mentions map[string]string, media []Media) json.RawMessage
- func ISOTime(s string) string
- func PlainText(raw json.RawMessage) string
- type APIError
- type Attachment
- type Changelog
- type Client
- func (c *Client) AddComment(ctx context.Context, key string, adf json.RawMessage) (Comment, error)
- func (c *Client) BaseURL() string
- func (c *Client) Changelog(ctx context.Context, key string) ([]History, error)
- func (c *Client) Comments(ctx context.Context, key string) ([]Comment, error)
- func (c *Client) Count(ctx context.Context, jql string) (int, error)
- func (c *Client) CreateIssue(ctx context.Context, fields map[string]any) (string, error)
- func (c *Client) CreateMeta(ctx context.Context, projects []string) ([]CreateMetaProject, error)
- func (c *Client) EditIssue(ctx context.Context, key string, fields, update map[string]any) error
- func (c *Client) EditMeta(ctx context.Context, key string) (map[string]FieldMeta, error)
- func (c *Client) Fields(ctx context.Context) ([]FieldInfo, error)
- func (c *Client) MediaRef(ctx context.Context, attachmentID string) (mediaID, filename string, err error)
- func (c *Client) MyFilters(ctx context.Context) ([]SavedFilter, error)
- func (c *Client) Myself(ctx context.Context) (User, error)
- func (c *Client) Priorities(ctx context.Context) ([]string, error)
- func (c *Client) PriorityCatalog(ctx context.Context) ([]NamedID, error)
- func (c *Client) Projects(ctx context.Context, limit int) (list []Project, truncated bool, err error)
- func (c *Client) Raw(ctx context.Context, method, path string, body []byte, mutating bool) (status int, out []byte, err error)
- func (c *Client) Search(ctx context.Context, jql string, fields []string, withChangelog bool, ...) error
- func (c *Client) SearchUsers(ctx context.Context, query string) ([]User, error)
- func (c *Client) SetAssignee(ctx context.Context, key, accountID string) error
- func (c *Client) Statuses(ctx context.Context) (map[string]string, error)
- func (c *Client) TakeUsage() Usage
- func (c *Client) Transition(ctx context.Context, key, transitionID string) error
- func (c *Client) Transitions(ctx context.Context, key string) ([]Transition, error)
- func (c *Client) UpdateFields(ctx context.Context, key string, fields map[string]any) error
- func (c *Client) Upload(ctx context.Context, key, filename string, file io.Reader) ([]Attachment, error)
- func (c *Client) Usage() Usage
- type Comment
- type CommentPage
- type CreateMetaProject
- type FieldInfo
- type FieldMeta
- type Fields
- type History
- type HistoryItem
- type Issue
- type IssueLink
- type Media
- type NamedID
- type Project
- type SavedFilter
- type Status
- type Transition
- type Usage
- type User
Constants ¶
const Layout = "2006-01-02T15:04:05.000-0700"
Layout is how Jira stamps timestamps: ISO-8601 with a numeric offset and no colon in it, which is why time.RFC3339 does not parse them.
Variables ¶
var ErrAuth = atlhttp.Auth("jira")
ErrAuth is the Jira-named rejected credential. It unwraps to atlhttp.ErrAuth so Watch detects it without a per-source branch. Error() keeps the "jira:" prefix so last_error names the source. Callers keep using errors.Is(err, jira.ErrAuth).
Functions ¶
func Category ¶
Category maps Jira's statusCategory key onto the three values data-model.md documents. An unknown key becomes "new", which can only ever miss a reopen, never invent one.
func Doc ¶
func Doc(text string, mentions map[string]string) json.RawMessage
Doc builds the ADF document a comment body has to be. The composer sends plain text with `@Display Name` typed into it plus the account ids it resolved, so this is where those become real mention nodes — a mention that stays plain text notifies nobody, which is the whole point of typing it.
mentions maps display name to account id.
func DocWithMedia ¶
DocWithMedia is Doc plus inline images, appended after the text — where a screenshot belongs in a comment that describes it.
func ISOTime ¶
ISOTime normalizes a Jira timestamp to the ISO-8601 UTC form every stored column uses (data-model.md, "Conventions"), so string comparison sorts chronologically. An unparseable value passes through untouched.
func PlainText ¶
func PlainText(raw json.RawMessage) string
PlainText flattens an ADF document to plain text: it is what FTS indexes and what makes a repro-steps custom field searchable. A field that holds a bare string (the older wiki-markup shape) passes through unchanged.
Types ¶
type APIError ¶
APIError is a non-2xx answer with its body parsed. Errors is Jira's per-field rejection map, which the server passes to the client as `jira_errors` so the message lands on the input that caused it. Neither the request nor the credential is ever recorded here (constitution article 8).
type Attachment ¶
type Changelog ¶
type Changelog struct {
Total int `json:"total"`
MaxResults int `json:"maxResults"`
Histories []History `json:"histories"`
}
Changelog as returned inline by expand=changelog. Total above len(Histories) means it was truncated and the dedicated endpoint has to be paged.
type Client ¶
type Client struct {
HTTP *http.Client
// Retries is the total number of attempts per request; Backoff is the first
// wait, doubling per attempt and capped at 30 s.
Retries int
Backoff time.Duration
// contains filtered or unexported fields
}
Client talks to one Atlassian Cloud site over REST. The credential is held only as an Authorization header value; it is never copied into an error, a log line, or a URL. Retries and Backoff apply to reads; writes use a narrower policy (see write).
func New ¶
New builds a Client for site using Basic auth (email:token). The HTTP client times out at 60s; Retries is 5; the first Backoff is 1s.
func (*Client) AddComment ¶
AddComment posts an ADF body (not plain text). Mentions must already be mention nodes — a leftover "@Name" string notifies nobody.
func (*Client) Changelog ¶
Changelog pages the full history of one issue, for the issues whose inline expand=changelog came back truncated.
func (*Client) Comments ¶
Comments pages every comment on one issue, for the issues with more than the inline limit.
func (*Client) Count ¶
Count returns Jira's approximate issue count for a JQL. It exists only to give progress output a denominator, so a failure is not the caller's problem: callers treat any error as "unknown" and keep going.
func (*Client) CreateIssue ¶
CreateIssue returns the new issue's key.
func (*Client) CreateMeta ¶
CreateMeta lists what can be created. Restricted to the configured projects: the site-wide answer is large and most of it is unreachable from this UI.
func (*Client) EditIssue ¶ added in v0.14.1
EditIssue PUTs /issue/{key} with fields and/or update. Either map may be empty; empty maps are omitted so a labels-only edit is {"update":…} only.
func (*Client) EditMeta ¶
EditMeta returns the fields this user may edit on this issue, keyed by field id.
func (*Client) MediaRef ¶
func (c *Client) MediaRef(ctx context.Context, attachmentID string) (mediaID, filename string, err error)
MediaRef resolves an attachment id to both the media UUID Jira needs in an ADF node and the filename our own renderer matches on (`alt`), which is what makes an inline image resolve without persisting the UUID anywhere.
There is no documented endpoint for this. Requesting the attachment's content answers 3xx to a pre-signed media URL that carries the UUID, so the redirect is read rather than followed — following it would download the whole file for a string, and the credential must not travel to the media host.
func (*Client) MyFilters ¶ added in v0.13.0
func (c *Client) MyFilters(ctx context.Context) ([]SavedFilter, error)
MyFilters returns filters the user owns, plus visible favourites when includeFavourites is honoured (Cloud's GET /filter/my).
func (*Client) Myself ¶
Myself verifies a credential and identifies its owner. It is the only call `PUT credential/` makes before storing a token.
func (*Client) Priorities ¶
Priorities returns the site's priority names, most urgent first, which is the order priority_rank counts from.
func (*Client) PriorityCatalog ¶
PriorityCatalog is the site's priority list, most urgent first. Names are in the account language; writes should send the id.
func (*Client) Projects ¶
func (c *Client) Projects(ctx context.Context, limit int) (list []Project, truncated bool, err error)
Projects lists the projects this credential can browse, newest API first (`/project/search` is the only paged, permission-filtered listing).
It stops after limit projects and reports truncated=true, because a very large site would otherwise turn a first-run picker into hundreds of requests.
func (*Client) Raw ¶
func (c *Client) Raw(ctx context.Context, method, path string, body []byte, mutating bool) (status int, out []byte, err error)
Raw sends a request and returns the HTTP status and response body without JSON decoding. Path must be site-relative (leading "/"); absolute URLs and scheme-relative paths are rejected so the Authorization header never leaves the configured site. mutating selects the write retry policy (429/503 only).
A completed HTTP response always returns err == nil with the status and body (including non-2xx). err is reserved for transport failures and bad paths.
func (*Client) Search ¶
func (c *Client) Search(ctx context.Context, jql string, fields []string, withChangelog bool, fn func([]Issue) error) error
Search pages a JQL query and calls fn once per page, which is what lets sync commit page by page. Pagination is by nextPageToken: the legacy startAt search is deprecated and drifts under concurrent writes.
func (*Client) SearchUsers ¶
SearchUsers backs the assignee picker. Jira's own endpoint decides what matches; there is no local user table to search.
func (*Client) SetAssignee ¶
SetAssignee assigns or, with an empty id, unassigns. Jira distinguishes "no assignee" (null) from "default assignee" (-1); the UI only ever asks for the former.
func (*Client) Statuses ¶
Statuses maps every status id on the site to its category. This is the input the derived-field rules need, because a changelog entry carries ids only.
func (*Client) TakeUsage ¶
TakeUsage returns the current counters and zeroes the numeric fields so a flusher can accumulate into daily totals without double-counting.
LastThrottledAt is a timestamp, not a counter: it is included in the snapshot but is NOT cleared. The in-process "last 429" stays visible until the process exits or a later 429 overwrites it.
func (*Client) Transition ¶
Transition performs the transition id on key. It uses the write retry policy: only 429 and 503 are retried, because a 500 may mean Jira already acted.
func (*Client) Transitions ¶
Transitions lists the status changes Jira will currently accept on key. Each entry's To carries StatusCategory so callers can key on the stable category; names are localized per account.
func (*Client) UpdateFields ¶
UpdateFields sets raw field values. The caller is responsible for the shape each field id expects, which EditMeta describes.
func (*Client) Upload ¶
func (c *Client) Upload(ctx context.Context, key, filename string, file io.Reader) ([]Attachment, error)
Upload attaches one file. Jira requires the nosniff header on this endpoint and answers with the created attachments.
ponytail: buffers the whole file in memory. Fine for the screenshots this is for; stream with io.Pipe if someone starts attaching video.
type CommentPage ¶
type CreateMetaProject ¶
type CreateMetaProject struct {
Key string `json:"key"`
Name string `json:"name"`
IssueTypes []NamedID `json:"issuetypes"`
}
CreateMetaProject is one project a person may file into, with its issue types.
type FieldInfo ¶
type FieldInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Custom bool `json:"custom"`
Schema struct {
Type string `json:"type"`
Custom string `json:"custom"`
Items string `json:"items"`
} `json:"schema"`
}
FieldInfo is one row from GET /rest/api/3/field — the site-wide field catalog. Distinct from FieldMeta (editmeta for one issue); do not reuse that type here.
type FieldMeta ¶
type FieldMeta struct {
Required bool `json:"required"`
Operations []string `json:"operations"`
Schema struct {
Type string `json:"type"`
Items string `json:"items"`
Custom string `json:"custom"`
} `json:"schema"`
AllowedValues []struct {
ID string `json:"id"`
Value string `json:"value"`
Name string `json:"name"`
} `json:"allowedValues"`
}
FieldMeta is one editable field as Jira describes it: what it accepts and, for a closed set, every value it accepts.
type Fields ¶
type Fields struct {
Summary string `json:"summary"`
Description json.RawMessage `json:"description"`
Environment json.RawMessage `json:"environment"`
IssueType NamedID `json:"issuetype"`
Status Status `json:"status"`
Priority *NamedID `json:"priority"`
Assignee *User `json:"assignee"`
Reporter *User `json:"reporter"`
Creator *User `json:"creator"`
Project struct {
Key string `json:"key"`
} `json:"project"`
Parent *struct {
Key string `json:"key"`
} `json:"parent"`
Labels []string `json:"labels"`
Components []NamedID `json:"components"`
FixVersions []NamedID `json:"fixVersions"`
Versions []NamedID `json:"versions"` // affects versions
Duedate string `json:"duedate"`
Resolution *NamedID `json:"resolution"`
Created string `json:"created"`
Updated string `json:"updated"`
Comment CommentPage `json:"comment"`
Attachment []Attachment `json:"attachment"`
IssueLinks []IssueLink `json:"issuelinks"`
}
type History ¶
type History struct {
ID string `json:"id"`
Created string `json:"created"`
Author User `json:"author"`
Items []HistoryItem `json:"items"`
}
type HistoryItem ¶
type HistoryItem struct {
Field string `json:"field"`
FieldID string `json:"fieldId"`
From string `json:"from"`
FromString string `json:"fromString"`
To string `json:"to"`
ToString string `json:"toString"`
}
HistoryItem's FieldID is the stable identifier ("status", "assignee"); Field is the display name and is localized.
type Issue ¶
type Issue struct {
ID string
Key string
Fields Fields
Extra map[string]json.RawMessage
Raw json.RawMessage
Changelog *Changelog
}
Issue keeps the fields object three ways: typed for the mapping, verbatim per field id so configured custom fields need no code, and whole as Raw.
func (*Issue) UnmarshalJSON ¶
type Media ¶
Media is one inline image in a comment: the Jira media UUID (not the attachment id — see Client.MediaRef) plus the filename, which is carried as `alt` so our own renderer can match the node to the attachment without persisting the UUID anywhere (web/src/lib/adf.ts, findAttachment).
type Project ¶
type Project struct {
Key string `json:"key"`
Name string `json:"name"`
TypeKey string `json:"projectTypeKey"`
}
Project is one row of the site's project list, as the onboarding picker needs it: the key sync will use, a name to recognise it by, and Jira's own type slug.
type SavedFilter ¶ added in v0.13.0
SavedFilter is a Jira filter the account owns or has starred.
type Status ¶
type Status struct {
ID string `json:"id"`
Name string `json:"name"`
StatusCategory struct {
Key string `json:"key"`
} `json:"statusCategory"`
}
Status carries the category because every piece of logic keys on it: names come back in the account's display language (contracts/sync.md, "Localization hazard").
type Transition ¶
Transition is one available status change, with the target's category so the UI can colour it without knowing the site's status names.
type Usage ¶
Usage is a point-in-time snapshot of this client's outbound Jira traffic. Counters are process-local until a caller persists them (see store.api_usage).
Requests counts every HTTP attempt, including retries: that is the unit that draws from Jira's rate budget. This is our own call volume, not Jira's remaining point pool — the site does not expose that.
type User ¶
type User struct {
AccountID string `json:"accountId"`
DisplayName string `json:"displayName"`
Email string `json:"emailAddress"`
// The two below are only ever filled by the user search the assignee picker
// calls; the mirror stores neither.
AvatarURLs map[string]string `json:"avatarUrls"`
Active bool `json:"active"`
}