issues

Github, Jira, Gitlab issues from Plan 9's acme
Log | Files | Refs | README | LICENSE

jira.go (4069B)


      1 package jira
      2 
      3 import (
      4 	"encoding/json"
      5 	"fmt"
      6 	"time"
      7 )
      8 
      9 const timestamp = "2006-01-02T15:04:05.999-0700"
     10 
     11 // Issue represents a limited subset of a Jira issue's data.
     12 // For a complete example of all issue data,
     13 // see https://jira.atlassian.com/rest/api/2/issue/JRA-9.
     14 type Issue struct {
     15 	ID       string `json:"id,omitempty"` // TODO(otl): int?
     16 	URL      string `json:"url,omitempty"`
     17 	Key      string `json:"key,omitempty"`
     18 	Reporter *User  `json:"reporter,omitempty"`
     19 	Assignee *User  `json:"assignee,omitempty"`
     20 	Summary  string `json:"summary"`
     21 	Status   *struct {
     22 		Name string `json:"name"`
     23 	} `json:"status,omitempty"`
     24 	Description string    `json:"description"`
     25 	Project     Project   `json:"project"`
     26 	Created     time.Time `json:"created,omitzero"`
     27 	Updated     time.Time `json:"updated,omitzero"`
     28 	Due         time.Time `json:"duedate,omitzero"`
     29 	Type        struct {
     30 		Name string `json:"name"`
     31 	} `json:"issuetype"`
     32 	Comments []Comment `json:"comments,omitempty"`
     33 	Links    []Issue   `json:"links,omitempty"`
     34 	Subtasks []Issue   `json:"subtasks,omitempty"`
     35 }
     36 
     37 type Project struct {
     38 	ID string `json:"id,omitempty"` // TODO(otl): int?
     39 	// Name string `json:"name"`
     40 	Key string `json:"key,omitempty"`
     41 	URL string `json:"self,omitempty"`
     42 }
     43 
     44 type Comment struct {
     45 	ID           string    `json:"id"` // TODO(otl): int?
     46 	URL          string    `json:"self"`
     47 	Body         string    `json:"body"`
     48 	Created      time.Time `json:"created"`
     49 	Updated      time.Time `json:"updated"`
     50 	Author       User      `json:"author"`
     51 	UpdateAuthor User      `json:"updateAuthor"`
     52 }
     53 
     54 func (c *Comment) UnmarshalJSON(b []byte) error {
     55 	type alias Comment
     56 	aux := &struct {
     57 		Created string `json:"created"`
     58 		Updated string `json:"updated"`
     59 		*alias
     60 	}{
     61 		alias: (*alias)(c),
     62 	}
     63 	if err := json.Unmarshal(b, aux); err != nil {
     64 		return err
     65 	}
     66 	var err error
     67 	c.Created, err = time.Parse(timestamp, aux.Created)
     68 	if err != nil {
     69 		return fmt.Errorf("parse created time: %w", err)
     70 	}
     71 	c.Updated, err = time.Parse(timestamp, aux.Updated)
     72 	if err != nil {
     73 		return fmt.Errorf("parse updated time: %w", err)
     74 	}
     75 	return nil
     76 }
     77 
     78 type User struct {
     79 	Name        string `json:"name"`
     80 	Email       string `json:"emailAddress"`
     81 	DisplayName string `json:"displayName"`
     82 }
     83 
     84 func (u User) String() string {
     85 	if u.DisplayName == "" {
     86 		return u.Email
     87 	}
     88 	return fmt.Sprintf("%s <%s>", u.DisplayName, u.Email)
     89 }
     90 
     91 func (issue *Issue) UnmarshalJSON(b []byte) error {
     92 	aux := &struct {
     93 		ID     string
     94 		Self   string
     95 		Key    string
     96 		Fields json.RawMessage
     97 	}{}
     98 	if err := json.Unmarshal(b, aux); err != nil {
     99 		return err
    100 	}
    101 	issue.ID = aux.ID
    102 	issue.URL = aux.Self
    103 	issue.Key = aux.Key
    104 
    105 	type alias Issue
    106 	iaux := &struct {
    107 		Created    string `json:"created"`
    108 		Updated    string `json:"updated"`
    109 		Due        string `json:"duedate"`
    110 		Comment    map[string]json.RawMessage
    111 		IssueLinks []struct {
    112 			InwardIssue  *Issue
    113 			OutwardIssue *Issue
    114 		}
    115 		*alias
    116 	}{
    117 		alias: (*alias)(issue),
    118 	}
    119 	if err := json.Unmarshal(aux.Fields, iaux); err != nil {
    120 		return err
    121 	}
    122 
    123 	var err error
    124 	if iaux.Created != "" {
    125 		issue.Created, err = time.Parse(timestamp, iaux.Created)
    126 		if err != nil {
    127 			return fmt.Errorf("created time: %w", err)
    128 		}
    129 	}
    130 	if iaux.Updated != "" {
    131 		issue.Updated, err = time.Parse(timestamp, iaux.Updated)
    132 		if err != nil {
    133 			return fmt.Errorf("updated time: %w", err)
    134 		}
    135 	}
    136 	if iaux.Due != "" {
    137 		issue.Due, err = time.Parse(timestamp, iaux.Due)
    138 		if err != nil {
    139 			return fmt.Errorf("due date: %w", err)
    140 		}
    141 	}
    142 	if bb, ok := iaux.Comment["comments"]; ok {
    143 		if err := json.Unmarshal(bb, &issue.Comments); err != nil {
    144 			return fmt.Errorf("unmarshal comments: %w", err)
    145 		}
    146 	}
    147 	for _, l := range iaux.IssueLinks {
    148 		if l.InwardIssue != nil {
    149 			issue.Links = append(issue.Links, *l.InwardIssue)
    150 		}
    151 		if l.OutwardIssue != nil {
    152 			issue.Links = append(issue.Links, *l.OutwardIssue)
    153 		}
    154 	}
    155 	return nil
    156 }
    157 
    158 func (issue *Issue) MarshalJSON() ([]byte, error) {
    159 	type alias Issue
    160 	aux := struct {
    161 		Fields *alias `json:"fields"`
    162 	}{(*alias)(issue)}
    163 	return json.Marshal(aux)
    164 }