1// Copyright © 2016 Aaron Longwell
2//
3// Use of this source code is governed by an MIT licese.
4// Details in the LICENSE file.
5
6package trello
7
8import (
9	"fmt"
10	"time"
11)
12
13// Action represents Trello API actions
14// Actions are immutable event traces generated whenever an action occurs in Trello.
15// See https://developers.trello.com/reference/#actions.
16type Action struct {
17	ID              string      `json:"id"`
18	IDMemberCreator string      `json:"idMemberCreator"`
19	Type            string      `json:"type"`
20	Date            time.Time   `json:"date"`
21	Data            *ActionData `json:"data,omitempty"`
22	MemberCreator   *Member     `json:"memberCreator,omitempty"`
23	Member          *Member     `json:"member,omitempty"`
24}
25
26// ActionData represent the nested data of actions
27type ActionData struct {
28	Text           string          `json:"text,omitempty"`
29	List           *List           `json:"list,omitempty"`
30	Card           *ActionDataCard `json:"card,omitempty"`
31	CardSource     *ActionDataCard `json:"cardSource,omitempty"`
32	Board          *Board          `json:"board,omitempty"`
33	Old            *ActionDataCard `json:"old,omitempty"`
34	ListBefore     *List           `json:"listBefore,omitempty"`
35	ListAfter      *List           `json:"listAfter,omitempty"`
36	DateLastEdited time.Time       `json:"dateLastEdited"`
37
38	CheckItem *CheckItem `json:"checkItem"`
39	Checklist *Checklist `json:"checklist"`
40}
41
42// ActionDataCard represent the nested 'card' data attribute of actions
43type ActionDataCard struct {
44	ID        string  `json:"id"`
45	Name      string  `json:"name"`
46	IDShort   int     `json:"idShort"`
47	ShortLink string  `json:"shortLink"`
48	Pos       float64 `json:"pos"`
49	Closed    bool    `json:"closed"`
50}
51
52// GetActions make a GET call for a board's actions
53func (b *Board) GetActions(args Arguments) (actions ActionCollection, err error) {
54	path := fmt.Sprintf("boards/%s/actions", b.ID)
55	err = b.client.Get(path, args, &actions)
56	return
57}
58
59// GetActions makes a GET call for a list's actions
60func (l *List) GetActions(args Arguments) (actions ActionCollection, err error) {
61	path := fmt.Sprintf("lists/%s/actions", l.ID)
62	err = l.client.Get(path, args, &actions)
63	return
64}
65
66// GetActions makes a GET for a card's actions
67func (c *Card) GetActions(args Arguments) (actions ActionCollection, err error) {
68	path := fmt.Sprintf("cards/%s/actions", c.ID)
69	err = c.client.Get(path, args, &actions)
70	return
71}
72
73// GetListChangeActions retrieves a slice of Actions which resulted in changes
74// to the card's active List. This includes the createCard and copyCard action (which
75// place the card in its first list), and the updateCard:closed action (which remove it
76// from its last list).
77//
78// This function is just an alias for:
79//   card.GetActions(Arguments{"filter": "createCard,copyCard,updateCard:idList,updateCard:closed", "limit": "1000"})
80//
81func (c *Card) GetListChangeActions() (actions ActionCollection, err error) {
82	return c.GetActions(Arguments{"filter": "createCard,copyCard,updateCard:idList,updateCard:closed"})
83}
84
85// GetMembershipChangeActions makes a GET call for a card's membership-change actions
86func (c *Card) GetMembershipChangeActions() (actions ActionCollection, err error) {
87	// We include updateCard:closed as if the member is implicitly removed from the card when it's closed.
88	// This allows us to "close out" the duration length.
89	return c.GetActions(Arguments{"filter": "addMemberToCard,removeMemberFromCard,updateCard:closed"})
90}
91
92// DidCreateCard returns true if this action created a card, false otherwise.
93func (a *Action) DidCreateCard() bool {
94	switch a.Type {
95	case "createCard", "emailCard", "copyCard", "convertToCardFromCheckItem":
96		return true
97	case "moveCardToBoard":
98		return true // Unsure about this one
99	default:
100		return false
101	}
102}
103
104// DidArchiveCard returns true if the card was updated
105func (a *Action) DidArchiveCard() bool {
106	return (a.Type == "updateCard") && a.Data != nil && a.Data.Card != nil && a.Data.Card.Closed
107}
108
109// DidUnarchiveCard returns true if the card was unarchived
110func (a *Action) DidUnarchiveCard() bool {
111	return (a.Type == "updateCard") && a.Data != nil && a.Data.Old != nil && a.Data.Old.Closed
112}
113
114// DidChangeListForCard returns true if this action created the card (in which case it
115// caused it to enter its first list), archived the card (in which case it caused it to
116// leave its last List), or was an updateCard action involving a change to the list. This
117// is supporting functionality for ListDuration.
118//
119func (a *Action) DidChangeListForCard() bool {
120	if a.DidCreateCard() {
121		return true
122	}
123	if a.DidArchiveCard() {
124		return true
125	}
126	if a.DidUnarchiveCard() {
127		return true
128	}
129	if a.Type == "updateCard" {
130		if a.Data != nil && a.Data.ListAfter != nil {
131			return true
132		}
133	}
134	return false
135}
136
137// DidChangeCardMembership returns true if card's membership was changed
138func (a *Action) DidChangeCardMembership() bool {
139	switch a.Type {
140	case "addMemberToCard":
141		return true
142	case "removeMemberFromCard":
143		return true
144	default:
145		return false
146	}
147}
148
149// ListAfterAction calculates which List the card ended up in after this action
150// completed. Returns nil when the action resulted in the card being archived (in
151// which case we consider it to not be in a list anymore), or when the action isn't
152// related to a list at all (in which case this is a nonsensical question to ask).
153//
154func ListAfterAction(a *Action) *List {
155	switch a.Type {
156	case "createCard", "copyCard", "emailCard", "convertToCardFromCheckItem":
157		return a.Data.List
158	case "updateCard":
159		if a.DidArchiveCard() {
160			return nil
161		} else if a.DidUnarchiveCard() {
162			return a.Data.List
163		}
164		if a.Data.ListAfter != nil {
165			return a.Data.ListAfter
166		}
167	}
168	return nil
169}
170