trelloapi 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +11 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/.DS_Store +0 -0
- data/lib/trello.rb +163 -0
- data/lib/trello/.DS_Store +0 -0
- data/lib/trello/action.rb +68 -0
- data/lib/trello/association.rb +14 -0
- data/lib/trello/association_proxy.rb +42 -0
- data/lib/trello/attachment.rb +40 -0
- data/lib/trello/authorization.rb +187 -0
- data/lib/trello/basic_data.rb +132 -0
- data/lib/trello/board.rb +201 -0
- data/lib/trello/card.rb +456 -0
- data/lib/trello/checklist.rb +142 -0
- data/lib/trello/client.rb +120 -0
- data/lib/trello/comment.rb +62 -0
- data/lib/trello/configuration.rb +68 -0
- data/lib/trello/core_ext/array.rb +6 -0
- data/lib/trello/core_ext/hash.rb +6 -0
- data/lib/trello/core_ext/string.rb +6 -0
- data/lib/trello/cover_image.rb +8 -0
- data/lib/trello/has_actions.rb +9 -0
- data/lib/trello/item.rb +37 -0
- data/lib/trello/item_state.rb +30 -0
- data/lib/trello/json_utils.rb +64 -0
- data/lib/trello/label.rb +108 -0
- data/lib/trello/label_name.rb +31 -0
- data/lib/trello/list.rb +114 -0
- data/lib/trello/member.rb +112 -0
- data/lib/trello/multi_association.rb +12 -0
- data/lib/trello/net.rb +39 -0
- data/lib/trello/notification.rb +61 -0
- data/lib/trello/organization.rb +68 -0
- data/lib/trello/plugin_datum.rb +34 -0
- data/lib/trello/token.rb +36 -0
- data/lib/trello/webhook.rb +103 -0
- data/trello.gemspec +41 -0
- metadata +161 -0
data/lib/trello/card.rb
ADDED
@@ -0,0 +1,456 @@
|
|
1
|
+
module Trello
|
2
|
+
# A Card is a container that can house checklists and comments; it resides inside a List.
|
3
|
+
#
|
4
|
+
# @!attribute [r] id
|
5
|
+
# @return [String]
|
6
|
+
# @!attribute [r] short_id
|
7
|
+
# @return [Fixnum]
|
8
|
+
# @!attribute [rw] name
|
9
|
+
# @return [String]
|
10
|
+
# @!attribute [rw] desc
|
11
|
+
# @return [String]
|
12
|
+
# @!attribute [rw] due
|
13
|
+
# @return [Datetime]
|
14
|
+
# @!attribute [rw] closed
|
15
|
+
# @return [Boolean]
|
16
|
+
# @!attribute [r] url
|
17
|
+
# @return [String]
|
18
|
+
# @!attribute [r] short_url
|
19
|
+
# @return [String]
|
20
|
+
# @!attribute [rw] board_id
|
21
|
+
# @return [String] A 24-character hex string
|
22
|
+
# @!attribute [rw] member_ids
|
23
|
+
# @return [Array<String>] An Array of 24-character hex strings
|
24
|
+
# @!attribute [rw] list_id
|
25
|
+
# @return [String] A 24-character hex string
|
26
|
+
# @!attribute [rw] pos
|
27
|
+
# @return [Float]
|
28
|
+
# @!attribute [r] last_activity_date
|
29
|
+
# @return [Dateime]
|
30
|
+
# @!attribute [rw] card_labels
|
31
|
+
# @return [Array<Hash>]
|
32
|
+
# @!attribute [rw] labels
|
33
|
+
# @return [Array<Trello::Labels>]
|
34
|
+
# @!attribute [rw] cover_image_id
|
35
|
+
# @return [String] A 24-character hex string
|
36
|
+
# @!attribute [r] badges
|
37
|
+
# @return [Hash]
|
38
|
+
# @!attribute [r] card_members
|
39
|
+
# @return [Object]
|
40
|
+
# @!attribute [rw] source_card_id
|
41
|
+
# @return [String] A 24-character hex string
|
42
|
+
# @!attribute [rw] source_card_properties
|
43
|
+
# @return [Array<String>] Array of strings
|
44
|
+
|
45
|
+
class Card < BasicData
|
46
|
+
register_attributes :id, :short_id, :name, :desc, :due, :due_complete, :closed, :url, :short_url,
|
47
|
+
:board_id, :member_ids, :list_id, :pos, :last_activity_date, :labels, :card_labels,
|
48
|
+
:cover_image_id, :badges, :card_members, :source_card_id, :source_card_properties,
|
49
|
+
readonly: [ :id, :short_id, :url, :short_url, :last_activity_date, :badges, :card_members ]
|
50
|
+
validates_presence_of :id, :name, :list_id
|
51
|
+
validates_length_of :name, in: 1..16384
|
52
|
+
validates_length_of :desc, in: 0..16384
|
53
|
+
|
54
|
+
include HasActions
|
55
|
+
|
56
|
+
SYMBOL_TO_STRING = {
|
57
|
+
id: 'id',
|
58
|
+
short_id: 'idShort',
|
59
|
+
name: 'name',
|
60
|
+
desc: 'desc',
|
61
|
+
due: 'due',
|
62
|
+
due_complete: 'dueComplete',
|
63
|
+
closed: 'closed',
|
64
|
+
url: 'url',
|
65
|
+
short_url: 'shortUrl',
|
66
|
+
board_id: 'idBoard',
|
67
|
+
member_ids: 'idMembers',
|
68
|
+
cover_image_id: 'idAttachmentCover',
|
69
|
+
list_id: 'idList',
|
70
|
+
pos: 'pos',
|
71
|
+
last_activity_date: 'dateLastActivity',
|
72
|
+
card_labels: 'idLabels',
|
73
|
+
labels: 'labels',
|
74
|
+
badges: 'badges',
|
75
|
+
card_members: 'members',
|
76
|
+
source_card_id: "idCardSource",
|
77
|
+
source_card_properties: "keepFromSource"
|
78
|
+
}
|
79
|
+
|
80
|
+
class << self
|
81
|
+
# Find a specific card by its id.
|
82
|
+
#
|
83
|
+
# @raise [Trello::Error] if the card could not be found.
|
84
|
+
#
|
85
|
+
# @return [Trello::Card]
|
86
|
+
def find(id, params = {})
|
87
|
+
client.find(:card, id, params)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Create a new card and save it on Trello.
|
91
|
+
#
|
92
|
+
# If using source_card_id to duplicate a card, make sure to save
|
93
|
+
# the source card to Trello before calling this method to assure
|
94
|
+
# the correct data is used in the duplication.
|
95
|
+
#
|
96
|
+
# @param [Hash] options
|
97
|
+
# @option options [String] :name The name of the new card.
|
98
|
+
# @option options [String] :list_id ID of the list that the card should
|
99
|
+
# be added to.
|
100
|
+
# @option options [String] :desc A string with a
|
101
|
+
# length from 0 to 16384.
|
102
|
+
# @option options [String] :member_ids A comma-separated list of
|
103
|
+
# objectIds (24-character hex strings).
|
104
|
+
# @option options [String] :card_labels A comma-separated list of
|
105
|
+
# objectIds (24-character hex strings).
|
106
|
+
# @option options [Date] :due A date, or `nil`.
|
107
|
+
# @option options [String] :pos A position. `"top"`, `"bottom"`, or a
|
108
|
+
# positive number. Defaults to `"bottom"`.
|
109
|
+
# @option options [String] :source_card_id ID of the card to copy
|
110
|
+
# @option options [String] :source_card_properties A single, or array of,
|
111
|
+
# string properties to copy from source card.
|
112
|
+
# `"all"`, `"checklists"`, `"due"`, `"members"`, or `nil`.
|
113
|
+
# Defaults to `"all"`.
|
114
|
+
#
|
115
|
+
# @raise [Trello::Error] if the card could not be created.
|
116
|
+
#
|
117
|
+
# @return [Trello::Card]
|
118
|
+
def create(options)
|
119
|
+
client.create(:card,
|
120
|
+
'name' => options[:name],
|
121
|
+
'idList' => options[:list_id],
|
122
|
+
'desc' => options[:desc],
|
123
|
+
'idMembers' => options[:member_ids],
|
124
|
+
'idLabels' => options[:card_labels],
|
125
|
+
'due' => options[:due],
|
126
|
+
'due_complete' => options[:due_complete] || false,
|
127
|
+
'pos' => options[:pos],
|
128
|
+
'idCardSource' => options[:source_card_id],
|
129
|
+
'keepFromSource' => options.key?(:source_card_properties) ? options[:source_card_properties] : 'all'
|
130
|
+
)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Update the fields of a card.
|
135
|
+
#
|
136
|
+
# Supply a hash of string keyed data retrieved from the Trello API representing
|
137
|
+
# a card.
|
138
|
+
#
|
139
|
+
# Note that this this method does not save anything new to the Trello API,
|
140
|
+
# it just assigns the input attributes to your local object. If you use
|
141
|
+
# this method to assign attributes, call `save` or `update!` afterwards if
|
142
|
+
# you want to persist your changes to Trello.
|
143
|
+
#
|
144
|
+
# @param [Hash] fields
|
145
|
+
# @option fields [String] :id
|
146
|
+
# @option fields [String] :short_id
|
147
|
+
# @option fields [String] :name The new name of the card.
|
148
|
+
# @option fields [String] :desc A string with a length from 0 to
|
149
|
+
# 16384.
|
150
|
+
# @option fields [Date] :due A date, or `nil`.
|
151
|
+
# @option fields [Boolean] :due_complete
|
152
|
+
# @option fields [Boolean] :closed
|
153
|
+
# @option fields [String] :url
|
154
|
+
# @option fields [String] :short_url
|
155
|
+
# @option fields [String] :board_id
|
156
|
+
# @option fields [String] :member_ids A comma-separated list of objectIds
|
157
|
+
# (24-character hex strings).
|
158
|
+
# @option fields [String] :pos A position. `"top"`, `"bottom"`, or a
|
159
|
+
# positive number. Defaults to `"bottom"`.
|
160
|
+
# @option fields [Array] :labels An Array of Trello::Label objects
|
161
|
+
# derived from the JSON response
|
162
|
+
# @option fields [String] :card_labels A comma-separated list of
|
163
|
+
# objectIds (24-character hex strings).
|
164
|
+
# @option fields [Object] :cover_image_id
|
165
|
+
# @option fields [Object] :badges
|
166
|
+
# @option fields [Object] :card_members
|
167
|
+
# @option fields [String] :source_card_id
|
168
|
+
# @option fields [Array] :source_card_properties
|
169
|
+
#
|
170
|
+
# @return [Trello::Card] self
|
171
|
+
def update_fields(fields)
|
172
|
+
attributes[:id] = fields[SYMBOL_TO_STRING[:id]]
|
173
|
+
attributes[:short_id] = fields[SYMBOL_TO_STRING[:short_id]]
|
174
|
+
attributes[:name] = fields[SYMBOL_TO_STRING[:name]] || fields[:name]
|
175
|
+
attributes[:desc] = fields[SYMBOL_TO_STRING[:desc]] || fields[:desc]
|
176
|
+
attributes[:due] = Time.iso8601(fields[SYMBOL_TO_STRING[:due]]) rescue nil
|
177
|
+
attributes[:due] ||= fields[:due]
|
178
|
+
attributes[:due_complete] = fields[SYMBOL_TO_STRING[:due_complete]] || false
|
179
|
+
attributes[:closed] = fields[SYMBOL_TO_STRING[:closed]]
|
180
|
+
attributes[:url] = fields[SYMBOL_TO_STRING[:url]]
|
181
|
+
attributes[:short_url] = fields[SYMBOL_TO_STRING[:short_url]]
|
182
|
+
attributes[:board_id] = fields[SYMBOL_TO_STRING[:board_id]]
|
183
|
+
attributes[:member_ids] = fields[SYMBOL_TO_STRING[:member_ids]] || fields[:member_ids]
|
184
|
+
attributes[:list_id] = fields[SYMBOL_TO_STRING[:list_id]] || fields[:list_id]
|
185
|
+
attributes[:pos] = fields[SYMBOL_TO_STRING[:pos]] || fields[:pos]
|
186
|
+
attributes[:labels] = (fields[SYMBOL_TO_STRING[:labels]] || []).map { |lbl| Trello::Label.new(lbl) }
|
187
|
+
attributes[:card_labels] = fields[SYMBOL_TO_STRING[:card_labels]] || fields[:card_labels]
|
188
|
+
attributes[:last_activity_date] = Time.iso8601(fields[SYMBOL_TO_STRING[:last_activity_date]]) rescue nil
|
189
|
+
attributes[:cover_image_id] = fields[SYMBOL_TO_STRING[:cover_image_id]]
|
190
|
+
attributes[:badges] = fields[SYMBOL_TO_STRING[:badges]]
|
191
|
+
attributes[:card_members] = fields[SYMBOL_TO_STRING[:card_members]]
|
192
|
+
attributes[:source_card_id] = fields[SYMBOL_TO_STRING[:source_card_id]] || fields[:source_card_id]
|
193
|
+
attributes[:source_card_properties] = fields[SYMBOL_TO_STRING[:source_card_properties]] || fields[:source_card_properties]
|
194
|
+
self
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns a reference to the board this card is part of.
|
198
|
+
one :board, path: :boards, using: :board_id
|
199
|
+
# Returns a reference to the cover image attachment
|
200
|
+
one :cover_image, path: :attachments, using: :cover_image_id
|
201
|
+
|
202
|
+
# Returns a list of checklists associated with the card.
|
203
|
+
#
|
204
|
+
# The options hash may have a filter key which can have its value set as any
|
205
|
+
# of the following values:
|
206
|
+
# :filter => [ :none, :all ] # default :all
|
207
|
+
many :checklists, filter: :all
|
208
|
+
|
209
|
+
# Returns a list of plugins associated with the card
|
210
|
+
many :plugin_data, path: "pluginData"
|
211
|
+
|
212
|
+
def check_item_states
|
213
|
+
states = CheckItemState.from_response client.get("/cards/#{self.id}/checkItemStates")
|
214
|
+
MultiAssociation.new(self, states).proxy
|
215
|
+
end
|
216
|
+
|
217
|
+
# Returns a reference to the list this card is currently in.
|
218
|
+
one :list, path: :lists, using: :list_id
|
219
|
+
|
220
|
+
# Returns a list of members who are assigned to this card.
|
221
|
+
#
|
222
|
+
# @return [Array<Trello::Member>]
|
223
|
+
def members
|
224
|
+
members = member_ids.map do |member_id|
|
225
|
+
Member.from_response client.get("/members/#{member_id}")
|
226
|
+
end
|
227
|
+
MultiAssociation.new(self, members).proxy
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns a list of members who have upvoted this card
|
231
|
+
# NOTE: this fetches a list each time it's called to avoid case where
|
232
|
+
# card is voted (or vote is removed) after card is fetched. Optimizing
|
233
|
+
# accuracy over network performance
|
234
|
+
#
|
235
|
+
# @return [Array<Trello::Member>]
|
236
|
+
def voters
|
237
|
+
Member.from_response client.get("/cards/#{id}/membersVoted")
|
238
|
+
end
|
239
|
+
|
240
|
+
# Saves a record.
|
241
|
+
#
|
242
|
+
# @raise [Trello::Error] if the card could not be saved
|
243
|
+
#
|
244
|
+
# @return [String] The JSON representation of the saved card returned by
|
245
|
+
# the Trello API.
|
246
|
+
def save
|
247
|
+
# If we have an id, just update our fields.
|
248
|
+
return update! if id
|
249
|
+
|
250
|
+
from_response client.post("/cards", {
|
251
|
+
name: name,
|
252
|
+
desc: desc,
|
253
|
+
idList: list_id,
|
254
|
+
idMembers: member_ids,
|
255
|
+
idLabels: card_labels,
|
256
|
+
pos: pos,
|
257
|
+
due: due,
|
258
|
+
dueComplete: due_complete,
|
259
|
+
idCardSource: source_card_id,
|
260
|
+
keepFromSource: source_card_properties
|
261
|
+
})
|
262
|
+
end
|
263
|
+
|
264
|
+
# Update an existing record.
|
265
|
+
#
|
266
|
+
# Warning: this updates all fields using values already in memory. If
|
267
|
+
# an external resource has updated these fields, you should refresh!
|
268
|
+
# this object before making your changes, and before updating the record.
|
269
|
+
#
|
270
|
+
# @raise [Trello::Error] if the card could not be updated.
|
271
|
+
#
|
272
|
+
# @return [String] The JSON representation of the updated card returned by
|
273
|
+
# the Trello API.
|
274
|
+
def update!
|
275
|
+
@previously_changed = changes
|
276
|
+
# extract only new values to build payload
|
277
|
+
payload = Hash[changes.map { |key, values| [SYMBOL_TO_STRING[key.to_sym].to_sym, values[1]] }]
|
278
|
+
@changed_attributes.clear
|
279
|
+
|
280
|
+
client.put("/cards/#{id}", payload)
|
281
|
+
end
|
282
|
+
|
283
|
+
# Delete this card
|
284
|
+
#
|
285
|
+
# @return [String] the JSON response from the Trello API
|
286
|
+
def delete
|
287
|
+
client.delete("/cards/#{id}")
|
288
|
+
end
|
289
|
+
|
290
|
+
# Check if the card is not active anymore.
|
291
|
+
def closed?
|
292
|
+
closed
|
293
|
+
end
|
294
|
+
|
295
|
+
# Close the card.
|
296
|
+
#
|
297
|
+
# This only marks your local copy card as closed. Use `close!` if you
|
298
|
+
# want to close the card and persist the change to the Trello API.
|
299
|
+
#
|
300
|
+
# @return [Boolean] always returns true
|
301
|
+
#
|
302
|
+
# @return [String] The JSON representation of the closed card returned by
|
303
|
+
# the Trello API.
|
304
|
+
def close
|
305
|
+
self.closed = true
|
306
|
+
end
|
307
|
+
|
308
|
+
def close!
|
309
|
+
close
|
310
|
+
save
|
311
|
+
end
|
312
|
+
|
313
|
+
# Is the record valid?
|
314
|
+
def valid?
|
315
|
+
name && list_id
|
316
|
+
end
|
317
|
+
|
318
|
+
# Add a comment with the supplied text.
|
319
|
+
def add_comment(text)
|
320
|
+
client.post("/cards/#{id}/actions/comments", text: text)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Add a checklist to this card
|
324
|
+
def add_checklist(checklist)
|
325
|
+
client.post("/cards/#{id}/checklists", {
|
326
|
+
value: checklist.id
|
327
|
+
})
|
328
|
+
end
|
329
|
+
|
330
|
+
# create a new checklist and add it to this card
|
331
|
+
def create_new_checklist(name)
|
332
|
+
client.post("/cards/#{id}/checklists", { name: name })
|
333
|
+
end
|
334
|
+
|
335
|
+
# Move this card to the given list
|
336
|
+
def move_to_list(list)
|
337
|
+
list_number = list.is_a?(String) ? list : list.id
|
338
|
+
unless list_id == list_number
|
339
|
+
client.put("/cards/#{id}/idList", {
|
340
|
+
value: list_number
|
341
|
+
})
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Move this card to the given board (and optional list on this board)
|
346
|
+
def move_to_board(new_board, new_list = nil)
|
347
|
+
unless board_id == new_board.id
|
348
|
+
payload = { value: new_board.id }
|
349
|
+
payload[:idList] = new_list.id if new_list
|
350
|
+
client.put("/cards/#{id}/idBoard", payload)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Add a member to this card
|
355
|
+
def add_member(member)
|
356
|
+
client.post("/cards/#{id}/members", {
|
357
|
+
value: member.id
|
358
|
+
})
|
359
|
+
end
|
360
|
+
|
361
|
+
# Remove a member from this card
|
362
|
+
def remove_member(member)
|
363
|
+
client.delete("/cards/#{id}/members/#{member.id}")
|
364
|
+
end
|
365
|
+
|
366
|
+
# Current authenticated user upvotes a card
|
367
|
+
def upvote
|
368
|
+
begin
|
369
|
+
client.post("/cards/#{id}/membersVoted", {
|
370
|
+
value: me.id
|
371
|
+
})
|
372
|
+
rescue Trello::Error => e
|
373
|
+
fail e unless e.message =~ /has already voted/i
|
374
|
+
end
|
375
|
+
|
376
|
+
self
|
377
|
+
end
|
378
|
+
|
379
|
+
# Recind upvote. Noop if authenticated user hasn't previously voted
|
380
|
+
def remove_upvote
|
381
|
+
begin
|
382
|
+
client.delete("/cards/#{id}/membersVoted/#{me.id}")
|
383
|
+
rescue Trello::Error => e
|
384
|
+
fail e unless e.message =~ /has not voted/i
|
385
|
+
end
|
386
|
+
|
387
|
+
self
|
388
|
+
end
|
389
|
+
|
390
|
+
# Add a label
|
391
|
+
def add_label(label)
|
392
|
+
unless label.valid?
|
393
|
+
errors.add(:label, "is not valid.")
|
394
|
+
return Trello.logger.warn "Label is not valid." unless label.valid?
|
395
|
+
end
|
396
|
+
client.post("/cards/#{id}/idLabels", {value: label.id})
|
397
|
+
end
|
398
|
+
|
399
|
+
# Remove a label
|
400
|
+
def remove_label(label)
|
401
|
+
unless label.valid?
|
402
|
+
errors.add(:label, "is not valid.")
|
403
|
+
return Trello.logger.warn "Label is not valid." unless label.valid?
|
404
|
+
end
|
405
|
+
client.delete("/cards/#{id}/idLabels/#{label.id}")
|
406
|
+
end
|
407
|
+
|
408
|
+
# Add an attachment to this card
|
409
|
+
def add_attachment(attachment, name = '')
|
410
|
+
# Is it a file object or a string (url)?
|
411
|
+
if attachment.respond_to?(:path) && attachment.respond_to?(:read)
|
412
|
+
client.post("/cards/#{id}/attachments", {
|
413
|
+
file: attachment,
|
414
|
+
name: name
|
415
|
+
})
|
416
|
+
else
|
417
|
+
client.post("/cards/#{id}/attachments", {
|
418
|
+
url: attachment,
|
419
|
+
name: name
|
420
|
+
})
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# Retrieve a list of attachments
|
425
|
+
def attachments
|
426
|
+
attachments = Attachment.from_response client.get("/cards/#{id}/attachments")
|
427
|
+
MultiAssociation.new(self, attachments).proxy
|
428
|
+
end
|
429
|
+
|
430
|
+
# Remove an attachment from this card
|
431
|
+
def remove_attachment(attachment)
|
432
|
+
client.delete("/cards/#{id}/attachments/#{attachment.id}")
|
433
|
+
end
|
434
|
+
|
435
|
+
# :nodoc:
|
436
|
+
def request_prefix
|
437
|
+
"/cards/#{id}"
|
438
|
+
end
|
439
|
+
|
440
|
+
# Retrieve a list of comments
|
441
|
+
def comments
|
442
|
+
comments = Comment.from_response client.get("/cards/#{id}/actions", filter: "commentCard")
|
443
|
+
end
|
444
|
+
|
445
|
+
# Find the creation date
|
446
|
+
def created_at
|
447
|
+
@created_at ||= Time.at(id[0..7].to_i(16)) rescue nil
|
448
|
+
end
|
449
|
+
|
450
|
+
private
|
451
|
+
|
452
|
+
def me
|
453
|
+
@me ||= Member.find(:me)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|