monolith-trello 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +115 -0
- data/lib/trello-lite/activity.rb +41 -0
- data/lib/trello-lite/board.rb +127 -0
- data/lib/trello-lite/card.rb +93 -0
- data/lib/trello-lite/client.rb +16 -0
- data/lib/trello-lite/configuration.rb +21 -0
- data/lib/trello-lite/custom_field.rb +27 -0
- data/lib/trello-lite/list.rb +51 -0
- data/lib/trello-lite/member.rb +74 -0
- data/lib/trello-lite/organization.rb +28 -0
- data/lib/trello.rb +49 -0
- data/spec/spec_helper.rb +105 -0
- data/spec/user_spec.rb +196 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b0204cf895047636e5b51fb1fd4b43964cd2538616e5182515d5a21f5dbb0656
|
4
|
+
data.tar.gz: 664230200e2731e9316cde2653547e4994844e0e21b7b54a0f735137ad363686
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d03c59e497c074c4e6bd36f31c6534e0f7c381f5ebae343dbc8d87baa3e6b62adc8530a3326ac04f5e4a344df061280bb0871ed47fbd02546476d1bf0a92e91b
|
7
|
+
data.tar.gz: cd40098a7f168d66844c25ebc9deaf47550fce995796bdc65f57715a7cd665ce776cb84cc4078c99715f406a4fa65f79f9568baa68303d065590f1f4cee67e9c
|
data/README.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
# Ruby Trello Lite
|
2
|
+
|
3
|
+
This is a learning project using this gem: [ruby-trello](https://github.com/jeremytregunna/ruby-trello).
|
4
|
+
|
5
|
+
The idea behind this project is to replicate some features using the examples in the gem's documentation.
|
6
|
+
|
7
|
+
## Other ways to enhance this project
|
8
|
+
* Practice delegation (Forwardable)
|
9
|
+
* Practice ActiveModel
|
10
|
+
|
11
|
+
## How to use this project
|
12
|
+
|
13
|
+
1. Get your Trello API keys: [trello.com/api-key](https://trello.com/app-key/)
|
14
|
+
2. Clone the project to your local repository
|
15
|
+
3. In your repository, add `require './lib/trello'` in your "play" script. `run.rb` is provided as an example.
|
16
|
+
4. Run your script
|
17
|
+
|
18
|
+
## Features implemented so far:
|
19
|
+
|
20
|
+
1. Configuration
|
21
|
+
|
22
|
+
```
|
23
|
+
Trello.configure do |config|
|
24
|
+
config.consumer_key = TRELLO_CONSUMER_KEY
|
25
|
+
config.oauth_token = TRELLO_OAUTH_TOKEN
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
2. Member information
|
30
|
+
|
31
|
+
```
|
32
|
+
bob = Trello::Member.find("bobtester")
|
33
|
+
|
34
|
+
# Print out his name
|
35
|
+
puts bob.full_name # "Bob Tester"
|
36
|
+
|
37
|
+
# Print his bio
|
38
|
+
puts bob.bio # A wonderfully delightful test user
|
39
|
+
```
|
40
|
+
|
41
|
+
3. List member boards (returns an array instead of ActiveModel:Associations)
|
42
|
+
|
43
|
+
```
|
44
|
+
# Print boards
|
45
|
+
|
46
|
+
puts bob.boards
|
47
|
+
|
48
|
+
# Optional: limit the number of boards to view
|
49
|
+
|
50
|
+
puts bob.boards(10)
|
51
|
+
|
52
|
+
```
|
53
|
+
|
54
|
+
4. Find board (Custom to this gem)
|
55
|
+
|
56
|
+
```
|
57
|
+
|
58
|
+
okrs_board = bob.find_board("okrs")
|
59
|
+
|
60
|
+
puts okrs_board
|
61
|
+
|
62
|
+
```
|
63
|
+
|
64
|
+
5. Get all the lists of that board and their names
|
65
|
+
|
66
|
+
```
|
67
|
+
|
68
|
+
puts okrs_board.lists
|
69
|
+
|
70
|
+
# print all the list names of that board
|
71
|
+
okrs_board.lists.each do |list|
|
72
|
+
puts list.name
|
73
|
+
end
|
74
|
+
|
75
|
+
```
|
76
|
+
|
77
|
+
6. Get all the cards of a list and get all their names
|
78
|
+
|
79
|
+
```
|
80
|
+
|
81
|
+
okrs_board.lists.first.cards.each do |card|
|
82
|
+
puts card.name
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
## TODO:
|
87
|
+
|
88
|
+
* Use active model so `bob.boards` returns a ActiveRecord-style associations object instead of just an array
|
89
|
+
|
90
|
+
|
91
|
+
## Things I've Learned:
|
92
|
+
|
93
|
+
* How configuration through a block works (search for Trello.configure)
|
94
|
+
* How you can get standard libraries to work in local
|
95
|
+
(especially when autoload is used) ($LOAD_PATH.unshift 'lib'). This is true
|
96
|
+
in the case where you use the original library
|
97
|
+
* Metaprogramming hacks
|
98
|
+
* What I've noticed is that it's mainly a tool to add instance variables,
|
99
|
+
methods, and classes after it has been defined. It reminds me of
|
100
|
+
the way you can set methods in javascript after an object has been defined.
|
101
|
+
* Memoization: this is a way for you to easily define and reference instance variables without doing too many expensive method calls.
|
102
|
+
|
103
|
+
## New techniques used:
|
104
|
+
|
105
|
+
* When consuming the JSON response of the API, assign it to an `@attributes` instance variable. In the future, you can do metaprogramming to automatically produce instance methods for this.
|
106
|
+
```
|
107
|
+
|
108
|
+
def find(username)
|
109
|
+
url = "https://api.trello.com/1/members/#{username}?fields=all&#{credentials}"
|
110
|
+
@attributes = Trello.parse(url)
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
```
|
115
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Trello
|
2
|
+
class Activity
|
3
|
+
attr_accessor :attributes
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
@attributes = attrs
|
7
|
+
end
|
8
|
+
|
9
|
+
def type
|
10
|
+
attributes[:type]
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
attributes[:data][:card][:name]
|
15
|
+
end
|
16
|
+
|
17
|
+
def short_link
|
18
|
+
"https://trello.com/c/" + attributes[:data][:card][:shortLink]
|
19
|
+
end
|
20
|
+
|
21
|
+
def old_list
|
22
|
+
unless attributes[:data][:listBefore].nil?
|
23
|
+
attributes[:data][:listBefore][:name]
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def new_list
|
30
|
+
unless attributes[:data][:listAfter].nil?
|
31
|
+
attributes[:data][:listAfter][:name]
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def updated_at
|
38
|
+
Date.parse(attributes[:date]).strftime('%d/%m/%Y')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Trello
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Board
|
5
|
+
attr_accessor :id, :lists, :attributes
|
6
|
+
|
7
|
+
def initialize(id, attrs = {})
|
8
|
+
@id = id
|
9
|
+
@lists = []
|
10
|
+
@attributes = attrs
|
11
|
+
@board_url = "https://api.trello.com/1/boards/#{id}?fields=all&members=all&customFields=true"
|
12
|
+
@board_list_url = "https://api.trello.com/1/boards/#{id}/lists?cards=open&card_fields=name&filter=open&fields=all"
|
13
|
+
@members = []
|
14
|
+
@custom_fields = []
|
15
|
+
find(id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def credentials
|
19
|
+
Trello.credentials
|
20
|
+
end
|
21
|
+
|
22
|
+
def find(id)
|
23
|
+
# puts "creating board #{id}"
|
24
|
+
@attributes = Trello.parse(@board_url + "&#{credentials}")
|
25
|
+
attributes[:members].each do |member|
|
26
|
+
member_obj = Member.new(member)
|
27
|
+
@members << member_obj
|
28
|
+
end
|
29
|
+
attributes[:customFields].each do |custom_field|
|
30
|
+
@custom_fields << CustomField.new(custom_field)
|
31
|
+
end
|
32
|
+
Trello.parse(@board_list_url + "&#{credentials}").each do |list_json|
|
33
|
+
list = List.new(list_json)
|
34
|
+
@lists << list
|
35
|
+
end
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_list(name)
|
40
|
+
list_obj = nil
|
41
|
+
lists.each do |list|
|
42
|
+
list_obj = list if list.name == name
|
43
|
+
end
|
44
|
+
if list_obj.nil?
|
45
|
+
puts "List doesn't exist. Here are some list names."
|
46
|
+
lists.each do |list|
|
47
|
+
puts list.name
|
48
|
+
end
|
49
|
+
else
|
50
|
+
list_obj
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_member(name)
|
55
|
+
@members.each do |member|
|
56
|
+
if name == member.full_name || name == member.username
|
57
|
+
return member
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def check_created_cards_since(days_ago)
|
63
|
+
url = "https://api.trello.com/1/boards/#{id}/actions?#{credentials}"
|
64
|
+
activities = Trello.parse(url)
|
65
|
+
created_cards = []
|
66
|
+
activities.each do |activity|
|
67
|
+
if activity[:type] == "createCard" && Time.parse(activity[:date]) > days_ago
|
68
|
+
created_cards << Activity.new(activity)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
created_cards
|
72
|
+
end
|
73
|
+
|
74
|
+
def lists
|
75
|
+
@lists
|
76
|
+
end
|
77
|
+
|
78
|
+
def name
|
79
|
+
attributes[:name]
|
80
|
+
end
|
81
|
+
|
82
|
+
def desc
|
83
|
+
attributes[:desc]
|
84
|
+
end
|
85
|
+
|
86
|
+
def url
|
87
|
+
attributes[:url]
|
88
|
+
end
|
89
|
+
|
90
|
+
def has_custom_fields?
|
91
|
+
url = "https://api.trello.com/1/boards/#{id}/plugins?filter=enabled&" + Trello.credentials
|
92
|
+
plugin_list = Trello.parse(url)
|
93
|
+
!plugin_list.select { |plugin| plugin[:name] == "Custom Fields"}.empty?
|
94
|
+
end
|
95
|
+
|
96
|
+
def custom_fields
|
97
|
+
@custom_fields
|
98
|
+
end
|
99
|
+
|
100
|
+
def enable_custom_fields
|
101
|
+
cf_id = "56d5e249a98895a9797bebb9"
|
102
|
+
url = "https://api.trello.com/1/boards/#{id}/boardPlugins?idPlugin=#{cf_id}&" + Trello.credentials
|
103
|
+
|
104
|
+
response = HTTParty.post(url, format: :plain)
|
105
|
+
JSON.parse(response, symbolize_names: true)
|
106
|
+
end
|
107
|
+
|
108
|
+
def create_work_units_field
|
109
|
+
url = "https://api.trello.com/1/customFields?" + Trello.credentials
|
110
|
+
|
111
|
+
wu_body = {
|
112
|
+
idModel: "#{id}",
|
113
|
+
modelType: "board",
|
114
|
+
name: "Work Units",
|
115
|
+
pos: "top",
|
116
|
+
type: "number",
|
117
|
+
display_cardFront: true
|
118
|
+
}
|
119
|
+
|
120
|
+
wu_headers = {
|
121
|
+
'Content-Type': 'application/json'
|
122
|
+
}
|
123
|
+
response = HTTParty.post(url, body: wu_body, format: :plain)
|
124
|
+
JSON.parse(response, symbolize_names: true)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Trello
|
2
|
+
class Card
|
3
|
+
attr_accessor :attributes, :url, :activities, :activities_url, :members
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
@attributes = attrs
|
7
|
+
@url = "https://api.trello.com/1/cards/#{attributes[:id]}?fields=all&members=true&member_fields=fullName%2Cusername&#{Trello.credentials}"
|
8
|
+
@activities_url = "https://api.trello.com/1/cards/#{attributes[:id]}/actions?limit=5&#{Trello.credentials}"
|
9
|
+
@card_json = nil
|
10
|
+
@activities = []
|
11
|
+
@members = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def id
|
15
|
+
attributes[:id]
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
attributes[:name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def due
|
23
|
+
Time.parse(card_json[:due]).strftime("%d/%m/%Y")
|
24
|
+
end
|
25
|
+
|
26
|
+
def card_json
|
27
|
+
@card_json ||= Trello.parse(url)
|
28
|
+
end
|
29
|
+
|
30
|
+
def last_activity
|
31
|
+
Time.parse(card_json[:dateLastActivity]).strftime("%d/%m/%Y")
|
32
|
+
end
|
33
|
+
|
34
|
+
def due_complete
|
35
|
+
card_json[:dueComplete]
|
36
|
+
end
|
37
|
+
|
38
|
+
def short_link
|
39
|
+
card_json[:shortUrl]
|
40
|
+
end
|
41
|
+
|
42
|
+
def members
|
43
|
+
if @members.empty?
|
44
|
+
create_members
|
45
|
+
end
|
46
|
+
@members
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_members
|
50
|
+
card_json[:members].each do |member|
|
51
|
+
@members << Member.new(member)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def status
|
56
|
+
unless due_complete
|
57
|
+
days = Date.parse(Time.now.strftime('%d/%m/%Y')) - Date.parse(due)
|
58
|
+
"Delayed by #{days.to_i} days"
|
59
|
+
else
|
60
|
+
"Done"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def work_units
|
65
|
+
url = "https://api.trello.com/1/cards/#{id}/customFieldItems?" + Trello.credentials
|
66
|
+
data = Trello.parse(url)
|
67
|
+
updated_data = data.select {|plugin| plugin[:value].keys.include?(:number)}
|
68
|
+
if updated_data.empty?
|
69
|
+
puts "kindly add work units"
|
70
|
+
else
|
71
|
+
updated_data[0][:value][:number].to_i
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def activities_url
|
76
|
+
@activities_url
|
77
|
+
end
|
78
|
+
|
79
|
+
def activities(limit = 5)
|
80
|
+
unless @activities.empty? || limit != 5
|
81
|
+
@activities
|
82
|
+
else
|
83
|
+
Trello.parse(activities_url).each_with_index do |activity, idx|
|
84
|
+
_activity = Activity.new(activity)
|
85
|
+
if _activity.type == "updateCard" && !_activity.old_list.nil?
|
86
|
+
@activities << _activity
|
87
|
+
end
|
88
|
+
break if @activities.size == limit
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Trello
|
2
|
+
class Client
|
3
|
+
def configure(&block)
|
4
|
+
return puts "No configuration details passed" unless block_given?
|
5
|
+
yield configuration
|
6
|
+
end
|
7
|
+
|
8
|
+
def configuration
|
9
|
+
@configuration ||= Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def credentials
|
13
|
+
"key=#{configuration.consumer_key}&token=#{configuration.oauth_token}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Trello
|
2
|
+
class Configuration
|
3
|
+
CONFIG_ATTRIBUTES = [
|
4
|
+
:consumer_key,
|
5
|
+
:consumer_secret,
|
6
|
+
:oauth_token,
|
7
|
+
:oauth_token_secret
|
8
|
+
]
|
9
|
+
|
10
|
+
attr_accessor *CONFIG_ATTRIBUTES
|
11
|
+
|
12
|
+
def initialize(attrs = {})
|
13
|
+
@attributes = attrs
|
14
|
+
end
|
15
|
+
|
16
|
+
# this only works if you pass values through a block
|
17
|
+
def attributes=(attrs = {})
|
18
|
+
attrs.each { |key, value| instance_variable_set("@#{key}", value) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Trello
|
2
|
+
class CustomField
|
3
|
+
attr_accessor :attributes
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
@attributes = attrs
|
7
|
+
end
|
8
|
+
|
9
|
+
def id
|
10
|
+
attributes[:id]
|
11
|
+
end
|
12
|
+
|
13
|
+
def type
|
14
|
+
attributes[:type]
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
attributes[:name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete
|
22
|
+
url = "https://api.trello.com/1/customfields/#{id}?" + Trello.credentials
|
23
|
+
response = HTTParty.delete(url, format: :plain)
|
24
|
+
JSON.parse(response, symbolize_names: true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Trello
|
2
|
+
class List
|
3
|
+
attr_accessor :attributes
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
@attributes = attrs
|
7
|
+
@cards = []
|
8
|
+
@attributes[:cards].each do |card|
|
9
|
+
card_obj = Card.new(card)
|
10
|
+
@cards << card_obj
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def id
|
15
|
+
attributes[:id]
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
attributes[:name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def cards
|
23
|
+
@cards
|
24
|
+
end
|
25
|
+
|
26
|
+
def cards_by_member(username)
|
27
|
+
members_cards = []
|
28
|
+
@cards.each do |card|
|
29
|
+
member_in_card = card.members.select { |member| member.username == username }
|
30
|
+
next if member_in_card.empty?
|
31
|
+
members_cards << card
|
32
|
+
end
|
33
|
+
members_cards
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_card(name = "")
|
37
|
+
card_obj = nil
|
38
|
+
cards.each do |card|
|
39
|
+
card_obj = card if card.name == name
|
40
|
+
end
|
41
|
+
if card_obj.nil?
|
42
|
+
puts "Card doesn't exist. Here are some card names."
|
43
|
+
cards.each do |card|
|
44
|
+
puts card.name
|
45
|
+
end
|
46
|
+
else
|
47
|
+
card_obj
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Trello
|
2
|
+
class Member
|
3
|
+
attr_accessor :attributes
|
4
|
+
|
5
|
+
# to keep things pragmatic, @attributes holds the json
|
6
|
+
# a cool feature in the future is to use metaprogramming to automatically
|
7
|
+
# create instance methods, including an #instance_methods method
|
8
|
+
# to help the user better understand the API.
|
9
|
+
def initialize(attrs = {})
|
10
|
+
@attributes = attrs
|
11
|
+
@boards = []
|
12
|
+
@organizations = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def credentials
|
16
|
+
Trello.credentials
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find(username)
|
20
|
+
@username = username
|
21
|
+
Trello.find_member(username)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find(username)
|
25
|
+
url = "https://api.trello.com/1/members/#{username}?fields=all&organizations=members&organization_fields=all&#{credentials}"
|
26
|
+
@attributes = Trello.parse(url)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def full_name
|
31
|
+
attributes[:fullName]
|
32
|
+
end
|
33
|
+
|
34
|
+
def bio
|
35
|
+
attributes[:bio]
|
36
|
+
end
|
37
|
+
|
38
|
+
def username
|
39
|
+
@username ||= attributes[:username]
|
40
|
+
end
|
41
|
+
|
42
|
+
# just returns an array for now - would be cool to use activemodel
|
43
|
+
def boards(number = "none")
|
44
|
+
attributes[:idBoards].each_with_index do |id_board, idx|
|
45
|
+
number == "none" ? number = attributes[:idBoards].size : number
|
46
|
+
board_number = idx + 1
|
47
|
+
@boards << Board.new(id_board)
|
48
|
+
break if board_number == number
|
49
|
+
end
|
50
|
+
@boards
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_board(name)
|
54
|
+
board = nil
|
55
|
+
attributes[:idBoards].each_with_index do |id_board, idx|
|
56
|
+
board_number = idx + 1
|
57
|
+
Board.new(id_board).name.downcase.include?(name.downcase) ? board = Board.new(id_board) : next
|
58
|
+
break
|
59
|
+
end
|
60
|
+
board
|
61
|
+
end
|
62
|
+
|
63
|
+
def organizations
|
64
|
+
attributes[:organizations].each do |org|
|
65
|
+
@organizations << Organization.new(org)
|
66
|
+
end
|
67
|
+
@organizations
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_orgs_by_name(name)
|
71
|
+
organizations.select { |org| org.display_name.include?(name) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Trello
|
2
|
+
class Organization
|
3
|
+
attr_accessor :attributes
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
@attributes = attrs
|
7
|
+
@boards = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def display_name
|
11
|
+
attributes[:displayName]
|
12
|
+
end
|
13
|
+
|
14
|
+
def name
|
15
|
+
attributes[:displayName]
|
16
|
+
end
|
17
|
+
|
18
|
+
def boards(limit = "all")
|
19
|
+
attributes[:idBoards].each_with_index do |board_id, idx|
|
20
|
+
@boards << Board.new(board_id)
|
21
|
+
unless limit == "all"
|
22
|
+
break if (idx + 1) == limit
|
23
|
+
end
|
24
|
+
end
|
25
|
+
@boards
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/trello.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
$LOAD_PATH.unshift 'lib'
|
2
|
+
require 'httparty'
|
3
|
+
require 'pry-byebug'
|
4
|
+
require 'active_support/core_ext/integer/time'
|
5
|
+
|
6
|
+
module Trello
|
7
|
+
autoload :Card, 'trello-lite/card'
|
8
|
+
autoload :List, 'trello-lite/list'
|
9
|
+
autoload :Board, 'trello-lite/board'
|
10
|
+
autoload :Client, 'trello-lite/client'
|
11
|
+
autoload :Member, 'trello-lite/member'
|
12
|
+
autoload :Activity, 'trello-lite/activity'
|
13
|
+
autoload :Configuration, 'trello-lite/configuration'
|
14
|
+
autoload :Organization, 'trello-lite/organization'
|
15
|
+
autoload :CustomField, 'trello-lite/custom_field'
|
16
|
+
|
17
|
+
# parse url using httparty and return json
|
18
|
+
def self.parse(url)
|
19
|
+
response = HTTParty.get(url, format: :plain)
|
20
|
+
JSON.parse(response, symbolize_names: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
# initialize a client in the environment
|
24
|
+
def self.client
|
25
|
+
@client ||= Client.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# block for taking in some credentials
|
29
|
+
def self.configure(&block)
|
30
|
+
client.configure(&block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# class methods in the Client object access the environment
|
34
|
+
# instance methods do the actual work
|
35
|
+
def self.credentials
|
36
|
+
client.credentials
|
37
|
+
end
|
38
|
+
|
39
|
+
# initialize a member in the environment
|
40
|
+
def self.member
|
41
|
+
@member ||= Member.new
|
42
|
+
end
|
43
|
+
|
44
|
+
# class methods in the Member object access the environment
|
45
|
+
# instance methods do the actual work
|
46
|
+
def self.find_member(username)
|
47
|
+
member.find(username)
|
48
|
+
end
|
49
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require "rspec"
|
2
|
+
|
3
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
4
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
5
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
6
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
7
|
+
# files.
|
8
|
+
#
|
9
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
10
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
11
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
12
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
13
|
+
# a separate helper file that requires the additional dependencies and performs
|
14
|
+
# the additional setup, and require it from the spec files that actually need
|
15
|
+
# it.
|
16
|
+
#
|
17
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
18
|
+
RSpec.configure do |config|
|
19
|
+
# rspec-expectations config goes here. You can use an alternate
|
20
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
21
|
+
# assertions if you prefer.
|
22
|
+
config.expect_with :rspec do |expectations|
|
23
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
24
|
+
# and `failure_message` of custom matchers include text for helper methods
|
25
|
+
# defined using `chain`, e.g.:
|
26
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
27
|
+
# # => "be bigger than 2 and smaller than 4"
|
28
|
+
# ...rather than:
|
29
|
+
# # => "be bigger than 2"
|
30
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
31
|
+
end
|
32
|
+
|
33
|
+
config.filter_run focus: true
|
34
|
+
config.run_all_when_everything_filtered = true
|
35
|
+
|
36
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
37
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
38
|
+
config.mock_with :rspec do |mocks|
|
39
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
40
|
+
# a real object. This is generally recommended, and will default to
|
41
|
+
# `true` in RSpec 4.
|
42
|
+
mocks.verify_partial_doubles = true
|
43
|
+
end
|
44
|
+
|
45
|
+
# This option will default to `:apply_to_host_groups` in RSpec 4 (and will
|
46
|
+
# have no way to turn it off -- the option exists only for backwards
|
47
|
+
# compatibility in RSpec 3). It causes shared context metadata to be
|
48
|
+
# inherited by the metadata hash of host groups and examples, rather than
|
49
|
+
# triggering implicit auto-inclusion in groups with matching metadata.
|
50
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
51
|
+
|
52
|
+
# The settings below are suggested to provide a good initial experience
|
53
|
+
# with RSpec, but feel free to customize to your heart's content.
|
54
|
+
=begin
|
55
|
+
# This allows you to limit a spec run to individual examples or groups
|
56
|
+
# you care about by tagging them with `:focus` metadata. When nothing
|
57
|
+
# is tagged with `:focus`, all examples get run. RSpec also provides
|
58
|
+
# aliases for `it`, `describe`, and `context` that include `:focus`
|
59
|
+
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
60
|
+
config.filter_run_when_matching :focus
|
61
|
+
|
62
|
+
# Allows RSpec to persist some state between runs in order to support
|
63
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
64
|
+
# you configure your source control system to ignore this file.
|
65
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
66
|
+
|
67
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
68
|
+
# recommended. For more details, see:
|
69
|
+
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
70
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
71
|
+
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
72
|
+
config.disable_monkey_patching!
|
73
|
+
|
74
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
75
|
+
# be too noisy due to issues in dependencies.
|
76
|
+
config.warnings = true
|
77
|
+
|
78
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
79
|
+
# file, and it's useful to allow more verbose output when running an
|
80
|
+
# individual spec file.
|
81
|
+
if config.files_to_run.one?
|
82
|
+
# Use the documentation formatter for detailed output,
|
83
|
+
# unless a formatter has already been configured
|
84
|
+
# (e.g. via a command-line flag).
|
85
|
+
config.default_formatter = "doc"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Print the 10 slowest examples and example groups at the
|
89
|
+
# end of the spec run, to help surface which specs are running
|
90
|
+
# particularly slow.
|
91
|
+
config.profile_examples = 10
|
92
|
+
|
93
|
+
# Run specs in random order to surface order dependencies. If you find an
|
94
|
+
# order dependency and want to debug it, you can fix the order by providing
|
95
|
+
# the seed, which is printed after each run.
|
96
|
+
# --seed 1234
|
97
|
+
config.order = :random
|
98
|
+
|
99
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
100
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
101
|
+
# test failures related to randomization by passing the same `--seed` value
|
102
|
+
# as the one that triggered the failure.
|
103
|
+
Kernel.srand config.seed
|
104
|
+
=end
|
105
|
+
end
|
data/spec/user_spec.rb
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'dotenv/load'
|
3
|
+
require './lib/trello'
|
4
|
+
|
5
|
+
Trello.configure do |config|
|
6
|
+
config.consumer_key = ENV['CONSUMER_KEY']
|
7
|
+
config.oauth_token = ENV['OAUTH_TOKEN']
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "Member" do
|
11
|
+
let(:kenn) {
|
12
|
+
Trello::Member.find("kennyfrc")
|
13
|
+
}
|
14
|
+
|
15
|
+
let(:board) {
|
16
|
+
kenn.find_board("okrs")
|
17
|
+
}
|
18
|
+
|
19
|
+
describe "has properties such as" do
|
20
|
+
it "can return the full name" do
|
21
|
+
expect(kenn.full_name).to eq "Kenn Costales"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "can return the bio" do
|
25
|
+
expect(kenn.bio).to eq "Managing Director of Monolith Growth Consulting / [monolithgrowth.com](https://monolithgrowth.com)"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can return an array of boards" do
|
29
|
+
expect(kenn.boards(1).class).to eq Array
|
30
|
+
expect(kenn.boards(1)[0].class).to eq Trello::Board
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "Board" do
|
35
|
+
it "can get the board name" do
|
36
|
+
expect(board.name).to eq "OKRs"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "can get the lists of a board" do
|
40
|
+
expect(board.lists.class).to eq Array
|
41
|
+
expect(board.lists[0].class).to eq Trello::List
|
42
|
+
expect(board.lists[0].name.class).to eq String
|
43
|
+
end
|
44
|
+
|
45
|
+
# this will stop working if there's no card in the live boards that > 5 days old
|
46
|
+
# it "can get created cards from X" do
|
47
|
+
# expect(board.check_created_cards_since(5.days.ago).class).to eq Array
|
48
|
+
# expect(board.check_created_cards_since(5.days.ago)[0].class).to eq Trello::Activity
|
49
|
+
# expect(board.check_created_cards_since(5.days.ago)[0].name).to eq "something"
|
50
|
+
# expect(board.check_created_cards_since(5.days.ago)[0].short_link).to eq "https://trello.com/c/UPd7RKD4"
|
51
|
+
# expect(board.check_created_cards_since(5.days.ago)[0].updated_at).to eq "19/01/2020"
|
52
|
+
# end
|
53
|
+
|
54
|
+
## Get members' cards for a board | https://developers.trello.com/reference#membersidboards
|
55
|
+
|
56
|
+
it "can get members' cards in a board" do
|
57
|
+
expect(board.find_list("Moving Out").cards_by_member("kennyfrc").class).to eq Array
|
58
|
+
expect(board.find_list("Moving Out").cards_by_member("kennyfrc")[0].class).to eq Trello::Card
|
59
|
+
expect(board.find_list("Moving Out").cards_by_member("kennyfrc")[0].name).to eq "MIDDLE cabinet - buy cr2032 batter for garmin awatch"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "can check if it has the plugin" do
|
63
|
+
expect(board.has_custom_fields?).to eq true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "has custom fields" do
|
67
|
+
expect(board.custom_fields[0].class).to eq Trello::CustomField
|
68
|
+
expect(board.custom_fields[0].name).to eq "Work Units"
|
69
|
+
end
|
70
|
+
|
71
|
+
it "its custom fields can be deleted" do
|
72
|
+
custom_fields = board.custom_fields
|
73
|
+
custom_fields.each do |custom_field|
|
74
|
+
custom_field.delete
|
75
|
+
end
|
76
|
+
updated_board = kenn.find_board("okrs")
|
77
|
+
expect(updated_board.custom_fields).to eq []
|
78
|
+
end
|
79
|
+
|
80
|
+
it "can create new custom fields" do
|
81
|
+
json = board.create_work_units_field
|
82
|
+
updated_board = kenn.find_board("okrs")
|
83
|
+
expect(updated_board.custom_fields[0].class).to eq Trello::CustomField
|
84
|
+
expect(updated_board.custom_fields[0].name).to eq "Work Units"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "List" do
|
90
|
+
let(:list) {
|
91
|
+
board.find_list("Moving Out")
|
92
|
+
}
|
93
|
+
|
94
|
+
it "get the list based on a name" do
|
95
|
+
expect(list.name).to eq "Moving Out"
|
96
|
+
expect(list.class).to eq Trello::List
|
97
|
+
end
|
98
|
+
|
99
|
+
it "get card name of a named list" do
|
100
|
+
expect(list.cards[0].name).to eq "MIDDLE cabinet - buy cr2032 batter for garmin awatch"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "Card" do
|
105
|
+
let(:list) {
|
106
|
+
board.find_list("Moving Out")
|
107
|
+
}
|
108
|
+
|
109
|
+
let(:card) {
|
110
|
+
list.find_card("MIDDLE cabinet - buy cr2032 batter for garmin awatch")
|
111
|
+
}
|
112
|
+
|
113
|
+
it "can find a card based on the name" do
|
114
|
+
expect(card.name).to eq "MIDDLE cabinet - buy cr2032 batter for garmin awatch"
|
115
|
+
end
|
116
|
+
|
117
|
+
it "has a due date" do
|
118
|
+
expect(card.due).to eq "17/08/2015"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "has a last activity" do
|
122
|
+
expect(card.last_activity).to eq Date.parse(Time.now.strftime('%d/%m/%Y')).strftime('%d/%m/%Y')
|
123
|
+
end
|
124
|
+
|
125
|
+
it "has a due complete" do
|
126
|
+
expect(card.due_complete).to eq false
|
127
|
+
end
|
128
|
+
|
129
|
+
it "contains a short url" do
|
130
|
+
expect(card.short_link).to eq "https://trello.com/c/NLHqoq08"
|
131
|
+
end
|
132
|
+
|
133
|
+
it "days ahead or before due" do
|
134
|
+
expect(card.status).to eq "Delayed by #{(Date.parse(Time.now.strftime('%d/%m/%Y')) - Date.parse("17/08/2015")).to_i} days"
|
135
|
+
end
|
136
|
+
|
137
|
+
it "has activities" do
|
138
|
+
expect(card.activities.class).to eq Array
|
139
|
+
expect(card.activities[0].class).to eq Trello::Activity
|
140
|
+
expect(card.activities[0].type).to eq "updateCard"
|
141
|
+
expect(card.activities[0].old_list).to eq "miCab MKT & partnerships"
|
142
|
+
expect(card.activities[0].new_list).to eq "Moving Out"
|
143
|
+
expect(card.activities[0].updated_at).to eq "18/01/2020"
|
144
|
+
end
|
145
|
+
|
146
|
+
it "has members" do
|
147
|
+
expect(card.members.class).to eq Array
|
148
|
+
expect(card.members[0].class).to eq Trello::Member
|
149
|
+
end
|
150
|
+
|
151
|
+
it "has work units" do
|
152
|
+
expect(card.work_units).to eq nil
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "Organization" do
|
157
|
+
it "can get a list of organizations" do
|
158
|
+
expect(kenn.organizations[0].class).to eq Trello::Organization
|
159
|
+
expect(kenn.organizations.class).to eq Array
|
160
|
+
end
|
161
|
+
|
162
|
+
it "can filter organizations based on name" do
|
163
|
+
expect(kenn.get_orgs_by_name("MGV Operations")[0].class).to eq Trello::Organization
|
164
|
+
expect(kenn.get_orgs_by_name("MGV Operations").class).to eq Array
|
165
|
+
end
|
166
|
+
|
167
|
+
it "has board ids" do
|
168
|
+
expect(kenn.get_orgs_by_name("MGV Operations").map {|org| org.boards(1)}[0].class).to eq Array
|
169
|
+
expect(kenn.get_orgs_by_name("MGV Operations").map {|org| org.boards(1)}[0][0].class).to eq Trello::Board
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
# megatracker features
|
176
|
+
## DONE get cards from the "Backlog", "Doing", "Done", "Sprint" cards
|
177
|
+
## DONE what card names are in the list
|
178
|
+
## DONE check if the task has been made
|
179
|
+
## DONE are we on or off the due date
|
180
|
+
## DONE check the date of last activity of that card
|
181
|
+
## DONE when is the due date of the card
|
182
|
+
## DONE - card changes only - check activities | https://developers.trello.com/reference#cardsidactions
|
183
|
+
## DONE check if the task was moved to another list
|
184
|
+
## DONE get members in a card
|
185
|
+
## DONE LIST | check if we have any tasks created this week | https://developers.trello.com/reference#listsidactions
|
186
|
+
## DONE LIST? | check the create date of the card | https://developers.trello.com/reference#listsidactions
|
187
|
+
## DONE Get members' cards for a board | https://developers.trello.com/reference#membersidboards
|
188
|
+
## DONE Get members' cards for a board and a specific list | https://developers.trello.com/reference#membersidcards
|
189
|
+
## DONE Organizations
|
190
|
+
## Post work units for a card
|
191
|
+
## Get work units for a card
|
192
|
+
|
193
|
+
# check rate limit
|
194
|
+
## 300 requests per 10 seconds
|
195
|
+
|
196
|
+
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: monolith-trello
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kenn Costales
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httparty
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dotenv
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A wrapper around the trello.com API, built around Monolith's scrum use
|
56
|
+
cases.
|
57
|
+
email: marketing@askmonolith.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files:
|
61
|
+
- README.md
|
62
|
+
files:
|
63
|
+
- README.md
|
64
|
+
- lib/trello-lite/activity.rb
|
65
|
+
- lib/trello-lite/board.rb
|
66
|
+
- lib/trello-lite/card.rb
|
67
|
+
- lib/trello-lite/client.rb
|
68
|
+
- lib/trello-lite/configuration.rb
|
69
|
+
- lib/trello-lite/custom_field.rb
|
70
|
+
- lib/trello-lite/list.rb
|
71
|
+
- lib/trello-lite/member.rb
|
72
|
+
- lib/trello-lite/organization.rb
|
73
|
+
- lib/trello.rb
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
- spec/user_spec.rb
|
76
|
+
homepage: https://github.com/kennyfrc/ruby-trello-lite
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options:
|
82
|
+
- "--charset=UTF-8"
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.1.0
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubygems_version: 3.0.3
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: A wrapper around the trello.com API, built around Monolith's scrum use cases.
|
100
|
+
test_files:
|
101
|
+
- spec/spec_helper.rb
|
102
|
+
- spec/user_spec.rb
|