mingle_events 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -123,12 +123,11 @@ One of the great usages of event playback is historical analysis. E.g., if you w
123
123
  It is possible to repeatedly run analysis against previously fetched events using the ProjectEventFetcher class that is only used in the internals of the standard polling mechanism. ProjectEventFetcher does most of the real work of reading new events from the server and making them available for local processing, so it's not a bad class to get to know.
124
124
 
125
125
  <pre>
126
- event_fetcher = MingleEvents::ProjectEventFetcher.new('mingle', mingle_access)
127
- processor = MingleEvents::Processors::PutsPublisher.new
126
+ event_fetcher = MingleEvents::ProjectEventFetcher.new('my_project', mingle_access)
128
127
  event_fetcher.all_fetched_entries.each do |e|
129
- processor.process_events([e])
128
+ # do something interesting with each event
130
129
  end
131
130
  </pre>
132
131
 
133
- That's not an ideal interface. It's a bit awkward to push a single event into an array for processing. We'll likely look to make this much better as we bring back batch processing of events.
132
+ Until there's time for further documentation take a look at card_count_by_day.rb and story_count_by_day.rb in the examples folder.
134
133
 
@@ -1,5 +1,5 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), 'feed', 'element_support'))
2
1
  require File.expand_path(File.join(File.dirname(__FILE__), 'feed', 'author'))
3
2
  require File.expand_path(File.join(File.dirname(__FILE__), 'feed', 'category'))
4
3
  require File.expand_path(File.join(File.dirname(__FILE__), 'feed', 'entry'))
5
4
  require File.expand_path(File.join(File.dirname(__FILE__), 'feed', 'page'))
5
+ require File.expand_path(File.join(File.dirname(__FILE__), 'feed', 'changes'))
@@ -3,9 +3,7 @@ module MingleEvents
3
3
 
4
4
  # The user who's Mingle activity triggered this event
5
5
  class Author
6
-
7
- include ElementSupport
8
-
6
+
9
7
  # The name of the author
10
8
  attr_reader :name
11
9
  # The email address of the author
@@ -16,13 +14,27 @@ module MingleEvents
16
14
  attr_reader :icon_uri
17
15
 
18
16
  def initialize(author_element)
19
- @name = element_text(author_element, 'name')
20
- @email = element_text(author_element, 'email', true)
21
- @uri = element_text(author_element, 'uri', true)
22
- @icon_uri = element_text(author_element, 'mingle:icon', true)
17
+ @name = author_element.at("name").inner_text
18
+ @email = optional_element_text(author_element, 'email')
19
+ @uri = optional_element_text(author_element, 'uri')
20
+ @icon_uri = optional_element_text(author_element, 'icon')
21
+ end
22
+
23
+ def self.from_snippet(entry_xml)
24
+ self.new(Nokogiri::XML(entry_xml))
25
+ end
26
+
27
+ private
28
+
29
+ def optional_element_text(parent_element, element_name)
30
+ element = parent_element.at(element_name)
31
+ element.nil? ? nil : element.inner_text
23
32
  end
24
33
 
25
34
  end
26
-
35
+
27
36
  end
28
- end
37
+ end
38
+
39
+
40
+
@@ -6,6 +6,8 @@ module MingleEvents
6
6
  # All current Mingle categories are also defined here, as constants.
7
7
  class Category
8
8
 
9
+ @@categories_by_mingle_term = {}
10
+
9
11
  # The category's term
10
12
  attr_reader :term
11
13
  # The category's scheme
@@ -14,6 +16,16 @@ module MingleEvents
14
16
  def initialize(term, scheme)
15
17
  @term = term
16
18
  @scheme = scheme
19
+ @@categories_by_mingle_term[term] = self
20
+ end
21
+
22
+ # lookup a category by the text value of the term (mingle scheme is assumed)
23
+ def self.for_mingle_term(mingle_term)
24
+ @@categories_by_mingle_term[mingle_term]
25
+ end
26
+
27
+ def to_s
28
+ term
17
29
  end
18
30
 
19
31
  def ==(other)
@@ -30,7 +42,7 @@ module MingleEvents
30
42
 
31
43
  # The Atom category scheme for all Mingle categories
32
44
  MINGLE_SCHEME = 'http://www.thoughtworks-studios.com/ns/mingle#categories'
33
-
45
+
34
46
  # Category for any event sourced by a card
35
47
  CARD = Category.new('card', MINGLE_SCHEME)
36
48
  # Category for any event that is the creation of a new card
@@ -0,0 +1,76 @@
1
+ module MingleEvents
2
+ module Feed
3
+
4
+ # Enumerable detail for each change specified in the entry's content section
5
+ class Changes
6
+
7
+ include Enumerable
8
+
9
+ def initialize(changes_element)
10
+ @changes_element = changes_element
11
+ end
12
+
13
+ def each
14
+ (@changes ||= parse_changes).each{|c| yield c}
15
+ end
16
+
17
+ private
18
+
19
+ def parse_changes
20
+ changes = []
21
+ @changes_element.search("change").map do |change_element|
22
+ category = Category.for_mingle_term(change_element["type"])
23
+ changes << Change.new(category).build(change_element)
24
+ end
25
+ changes
26
+ end
27
+
28
+ class Change
29
+
30
+ def initialize(category)
31
+ @category = category
32
+ end
33
+
34
+ def build(element)
35
+ element_to_hash(element)
36
+
37
+ raw_hash_from_xml = element_to_hash(element)
38
+
39
+ raw_hash_from_xml[:change].merge({
40
+ :category => @category,
41
+ :type => @category
42
+ })
43
+ end
44
+
45
+ private
46
+
47
+ def element_to_hash(element, hash = {})
48
+ hash_for_element = (hash[element.name.to_sym] ||= {})
49
+
50
+ element.attribute_nodes.each do |a|
51
+ hash_for_element[a.name.to_sym] = a.value
52
+ end
53
+
54
+ element.children.each do |child|
55
+ next unless child.is_a?(Nokogiri::XML::Element)
56
+
57
+ if child.children.count{|c| c.is_a?(Nokogiri::XML::Element)} > 0
58
+ element_to_hash(child, hash_for_element)
59
+ else
60
+ hash_for_element[child.name.to_sym] = if child["nil"] && child["nil"] == "true"
61
+ nil
62
+ else
63
+ child.inner_text
64
+ end
65
+ end
66
+ end
67
+
68
+ hash
69
+ end
70
+
71
+ end
72
+
73
+
74
+ end
75
+ end
76
+ end
@@ -9,6 +9,10 @@ module MingleEvents
9
9
  def initialize(entry_element)
10
10
  @entry_element = entry_element
11
11
  end
12
+
13
+ def self.from_snippet(entry_xml)
14
+ self.new(Nokogiri::XML(entry_xml).remove_namespaces!)
15
+ end
12
16
 
13
17
  # The raw entry XML from the Atom feed
14
18
  def raw_xml
@@ -18,7 +22,7 @@ module MingleEvents
18
22
  # The Atom entry's id value. This is the one true identifier for the entry,
19
23
  # and therefore the event.
20
24
  def entry_id
21
- @entry_id ||= @entry_element.at('id').inner_text
25
+ @entry_id ||= @entry_element.at("id").inner_text
22
26
  end
23
27
  alias :event_id :entry_id
24
28
 
@@ -29,20 +33,30 @@ module MingleEvents
29
33
 
30
34
  # The time at which entry was created, i.e., the event was triggered
31
35
  def updated
32
- @updated ||= Time.parse(@entry_element.at('updated').inner_text)
36
+ @updated ||= Time.parse(@entry_element.at("updated").inner_text)
33
37
  end
34
38
 
35
39
  # The user who created the entry (triggered the event), i.e., changed project data in Mingle
36
40
  def author
37
- @author ||= Author.new(@entry_element.at('author'))
41
+ @author ||= Author.new(@entry_element.at("author"))
38
42
  end
39
43
 
40
44
  # The set of Atom categoies describing the entry
41
45
  def categories
42
- @categories ||= @entry_element.search('category').map do |category_element|
43
- Category.new(category_element.attribute('term').text, category_element.attribute('scheme').text)
46
+ @categories ||= @entry_element.search("category").map do |category_element|
47
+ Category.new(category_element["term"], category_element["scheme"])
44
48
  end
45
49
  end
50
+
51
+ # The array of changes for this entry. Each change is a hash with redundant :type and
52
+ # :category entries specifying the category to which the change maps.
53
+ # Change detail is contained in nested hashes with keys mapping exactly to the XML
54
+ # as described in http://www.thoughtworks-studios.com/mingle/3.3/help/mingle_api_events.html.
55
+ # The data in the change hashes reflect only what is in the XML as encriching them would
56
+ # require potentially many calls to the Mingle server resulting in very slow processing.
57
+ def changes
58
+ @changes ||= Changes.new(@entry_element.at("content/changes"))
59
+ end
46
60
 
47
61
  # Whether the entry/event was sourced by a Mingle card
48
62
  def card?
@@ -60,7 +74,7 @@ module MingleEvents
60
74
  # The version number of the card or page that was created by this event. (For now, only
61
75
  # working with cards.)
62
76
  def version
63
- @version ||= CGI.parse(URI.parse(card_version_resource_uri).query)['version'].first.to_i
77
+ @version ||= CGI.parse(URI.parse(card_version_resource_uri).query)["version"].first.to_i
64
78
  end
65
79
 
66
80
  # The resource URI for the card version that was created by this event. Throws error if not card event.
@@ -94,14 +108,14 @@ module MingleEvents
94
108
  "link[@rel='http://www.thoughtworks-studios.com/ns/mingle#event-source'][@type='application/vnd.mingle+xml']"
95
109
  )
96
110
  # TODO: improve this bit of parsing :)
97
- card_number_element.attribute('href').text.split('/').last.split('.')[0..-2].join.to_i
111
+ card_number_element["href"].split('/').last.split('.')[0..-2].join.to_i
98
112
  end
99
113
 
100
114
  def parse_card_version_resource_uri
101
115
  card_number_element = @entry_element.at(
102
116
  "link[@rel='http://www.thoughtworks-studios.com/ns/mingle#version'][@type='application/vnd.mingle+xml']"
103
117
  )
104
- card_number_element.attribute('href').text
118
+ card_number_element["href"]
105
119
  end
106
120
 
107
121
  end
@@ -14,45 +14,24 @@ module MingleEvents
14
14
  end
15
15
 
16
16
  def entries
17
- @entries ||= page_as_document.search('feed/entry').map do |entry_element|
17
+ @entries ||= page_as_document.search('entry').map do |entry_element|
18
18
  Entry.new(entry_element)
19
19
  end
20
20
  end
21
21
 
22
22
  def next
23
- next_url_element = page_as_document.at("feed/link[@rel='next']")
23
+ next_url_element = page_as_document.at("link[@rel='next']")
24
24
  if next_url_element.nil?
25
25
  nil
26
26
  else
27
- Page.new(next_url_element.attribute('href').text, @mingle_access)
28
- end
29
- end
30
-
31
- def previous
32
- previous_url_element = page_as_document.at("feed/link[@rel='previous']")
33
- if previous_url_element.nil?
34
- nil
35
- else
36
- Page.new(previous_url_element.attribute('href').text, @mingle_access)
37
- end
38
- end
39
-
40
- def archived?
41
- URI.parse(url).query && url == page_as_document.at("feed/link[@rel='self']").attribute('href').text
42
- end
43
-
44
- def closest_archived_page
45
- if archived?
46
- self
47
- else
48
- self.next
27
+ Page.new(next_url_element["href"], @mingle_access)
49
28
  end
50
29
  end
51
30
 
52
31
  private
53
32
 
54
33
  def page_as_document
55
- @page_as_document ||= Nokogiri::XML(@mingle_access.fetch_page(@url))
34
+ @page_as_document ||= Nokogiri::XML(@mingle_access.fetch_page(@url)).remove_namespaces!
56
35
  end
57
36
 
58
37
  end
@@ -81,7 +81,7 @@ module MingleEvents
81
81
 
82
82
  def current_state_entry(info_file_key)
83
83
  if info_file = load_current_state[info_file_key]
84
- entry_for_xml(YAML.load(File.new(info_file))[:entry_xml])
84
+ Feed::Entry.from_snippet(YAML.load(File.new(info_file))[:entry_xml])
85
85
  else
86
86
  nil
87
87
  end
@@ -106,7 +106,7 @@ module MingleEvents
106
106
  current_state = load_current_state
107
107
  last_fetched_entry = if current_state[:last_fetched_entry_info_file]
108
108
  last_fetched_entry_info = YAML.load(File.new(current_state[:last_fetched_entry_info_file]))
109
- Feed::Entry.new(Nokogiri::XML(last_fetched_entry_info[:entry_xml]).at('/entry'))
109
+ Feed::Entry.from_snippet(last_fetched_entry_info[:entry_xml])
110
110
  else
111
111
  nil
112
112
  end
@@ -120,10 +120,6 @@ module MingleEvents
120
120
  end
121
121
  end
122
122
 
123
- def entry_for_xml(entry_xml)
124
- Feed::Entry.new(Nokogiri::XML(entry_xml).at('/entry'))
125
- end
126
-
127
123
  class Entries
128
124
 
129
125
  include Enumerable
@@ -132,16 +128,12 @@ module MingleEvents
132
128
  @first_info_file = first_info_file
133
129
  @last_info_file = last_info_file
134
130
  end
135
-
136
- def entry_for_xml(entry_xml)
137
- Feed::Entry.new(Nokogiri::XML(entry_xml).at('/entry'))
138
- end
139
-
131
+
140
132
  def each(&block)
141
133
  current_file = @first_info_file
142
134
  while current_file
143
135
  current_entry_info = YAML.load(File.new(current_file))
144
- yield(entry_for_xml(current_entry_info[:entry_xml]))
136
+ yield(Feed::Entry.from_snippet(current_entry_info[:entry_xml]))
145
137
  break if File.expand_path(current_file) == File.expand_path(@last_info_file)
146
138
  current_file = current_entry_info[:next_entry_file_path]
147
139
  end
@@ -6,15 +6,15 @@ module MingleEvents
6
6
  class AuthorTest < Test::Unit::TestCase
7
7
 
8
8
  def test_parse_attributes
9
- element = Nokogiri.XML(%{
10
- <author xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
9
+ author_xml = %{
10
+ <author >
11
11
  <name>Sammy Soso</name>
12
12
  <email>sammy@example.com</email>
13
13
  <uri>https://mingle.example.com/api/v2/users/233.xml</uri>
14
14
  <mingle:icon>https://mingle.example.com/user/icon/233/profile.jpg</mingle:icon>
15
- </author>})
16
-
17
- author = Author.new(element)
15
+ </author>
16
+ }
17
+ author = Author.from_snippet(author_xml)
18
18
  assert_equal("Sammy Soso", author.name)
19
19
  assert_equal("sammy@example.com", author.email)
20
20
  assert_equal("https://mingle.example.com/api/v2/users/233.xml", author.uri)
@@ -22,11 +22,12 @@ module MingleEvents
22
22
  end
23
23
 
24
24
  def test_parse_attributes_when_no_optional_fields
25
- element = Nokogiri.XML(%{
26
- <author xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
25
+ author_xml = %{
26
+ <author>
27
27
  <name>Sammy Soso</name>
28
- </author>})
29
- author = Author.new(element)
28
+ </author>
29
+ }
30
+ author = Author.from_snippet(author_xml)
30
31
  assert_equal("Sammy Soso", author.name)
31
32
  assert_nil(author.email)
32
33
  assert_nil(author.uri)
@@ -13,6 +13,10 @@ module MingleEvents
13
13
  assert_not_equal(Category::CARD, Object.new)
14
14
  assert_equal(:foo, {Category::CARD => :foo}[Category.new('card', 'http://www.thoughtworks-studios.com/ns/mingle#categories')])
15
15
  end
16
+
17
+ def test_can_lookup_by_mingle_term
18
+ assert_equal(Category::CARD_CREATION, Category.for_mingle_term('card-creation'))
19
+ end
16
20
 
17
21
  end
18
22
 
@@ -0,0 +1,344 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ module Feed
5
+
6
+ class ChangesTest < Test::Unit::TestCase
7
+
8
+ def test_parse_multiple_changes
9
+ element_xml_text = %{
10
+ <entry>
11
+ <content type="application/vnd.mingle+xml">
12
+ <changes>
13
+ <change type="card-creation"/>
14
+ <change type="card-type-change">
15
+ <old_value nil="true"></old_value>
16
+ <new_value>
17
+ <card_type url="https://mingle.example.com/api/v2/projects/atlas/card_types/27.xml">
18
+ <name>Defect</name>
19
+ </card_type>
20
+ </new_value>
21
+ </change>
22
+ <change type="name-change">
23
+ <old_value nil="true"></old_value>
24
+ <new_value>A New Card</new_value>
25
+ </change>
26
+ </changes>
27
+ </content>
28
+ </entry>}
29
+ entry = Entry.from_snippet(element_xml_text)
30
+
31
+ # check that all the changes are built
32
+ assert_equal 3, entry.changes.count
33
+ [Category::CARD_CREATION, Category::CARD_TYPE_CHANGE, Category::NAME_CHANGE].each do |change_type|
34
+ assert entry.changes.find{|c| c[:category] == change_type}
35
+ end
36
+
37
+ # check that a change's detail is built
38
+ card_type_change = entry.changes.find{|c| c[:category] == Category::CARD_TYPE_CHANGE}
39
+ assert_equal('Defect', card_type_change[:new_value][:card_type][:name])
40
+ end
41
+
42
+ def test_parse_name_change_from_nil
43
+ element_xml_text = %{
44
+ <entry>
45
+ <content type="application/vnd.mingle+xml">
46
+ <changes>
47
+ <change type="name-change">
48
+ <old_value nil="true" />
49
+ <new_value>Basic email integration</new_value>
50
+ </change
51
+ </changes>
52
+ </content>
53
+ </entry>}
54
+ entry = Entry.from_snippet(element_xml_text)
55
+
56
+ change = entry.changes.first
57
+ assert_equal(Category::NAME_CHANGE, change[:type])
58
+ assert_equal(Category::NAME_CHANGE, change[:category])
59
+ assert_nil change[:old_value]
60
+ assert_equal("Basic email integration", change[:new_value])
61
+ end
62
+
63
+ def test_parse_name_change
64
+ element_xml_text = %{
65
+ <entry>
66
+ <content type="application/vnd.mingle+xml">
67
+ <changes>
68
+ <change type="name-change">
69
+ <old_value>Work with email</old_value>
70
+ <new_value>Basic email integration</new_value>
71
+ </change>
72
+ </changes>
73
+ </content>
74
+ </entry>}
75
+ entry = Entry.from_snippet(element_xml_text)
76
+
77
+ change = entry.changes.first
78
+ assert_equal(Category::NAME_CHANGE, change[:type])
79
+ assert_equal(Category::NAME_CHANGE, change[:category])
80
+ assert_equal("Work with email", change[:old_value])
81
+ assert_equal("Basic email integration", change[:new_value])
82
+ end
83
+
84
+ def test_parse_type_info_when_no_custom_builder_specified
85
+ element_xml_text = %{
86
+ <entry>
87
+ <content type="application/vnd.mingle+xml">
88
+ <changes>
89
+ <change type="card-creation"/>
90
+ </changes>
91
+ </content>
92
+ </entry>}
93
+ entry = Entry.from_snippet(element_xml_text)
94
+
95
+ assert_equal(Category::CARD_CREATION, entry.changes.first[:type])
96
+ assert_equal(Category::CARD_CREATION, entry.changes.first[:category])
97
+ end
98
+
99
+ def test_parse_card_type_change_from_nil
100
+ element_xml_text = %{
101
+ <entry>
102
+ <content type="application/vnd.mingle+xml">
103
+ <changes>
104
+ <change type="card-type-change">
105
+ <old_value nil="true"></old_value>
106
+ <new_value>
107
+ <card_type url="https://mingle.example.com/api/v2/projects/atlas/card_types/27.xml">
108
+ <name>Defect</name>
109
+ </card_type>
110
+ </new_value>
111
+ </change>
112
+ </changes>
113
+ </content>
114
+ </entry>}
115
+ entry = Entry.from_snippet(element_xml_text)
116
+
117
+ change = entry.changes.first
118
+
119
+ assert_equal(Category::CARD_TYPE_CHANGE, change[:type])
120
+ assert_equal(Category::CARD_TYPE_CHANGE, change[:category])
121
+ assert_nil change[:old_value]
122
+ assert_equal("Defect", change[:new_value][:card_type][:name])
123
+ assert_equal("https://mingle.example.com/api/v2/projects/atlas/card_types/27.xml", change[:new_value][:card_type][:url])
124
+ end
125
+
126
+ def test_parse_card_type_change
127
+ element_xml_text = %{
128
+ <entry>
129
+ <content type="application/vnd.mingle+xml">
130
+ <changes>
131
+ <change type="card-type-change">
132
+ <old_value>
133
+ <card_type url="https://mingle.example.com/api/v2/projects/atlas/card_types/30.xml">
134
+ <name>Story</name
135
+ </card_type>
136
+ </old_value>
137
+ <new_value>
138
+ <card_type url="https://mingle.example.com/api/v2/projects/atlas/card_types/27.xml">
139
+ <name>Defect</name>
140
+ </card_type>
141
+ </new_value>
142
+ </change>
143
+ </changes>
144
+ </content>
145
+ </entry>}
146
+ entry = Entry.from_snippet(element_xml_text)
147
+
148
+ change = entry.changes.first
149
+ assert_equal(Category::CARD_TYPE_CHANGE, change[:type])
150
+ assert_equal(Category::CARD_TYPE_CHANGE, change[:category])
151
+ assert_equal("Story", change[:old_value][:card_type][:name])
152
+ assert_equal("https://mingle.example.com/api/v2/projects/atlas/card_types/30.xml", change[:old_value][:card_type][:url])
153
+ assert_equal("Defect", change[:new_value][:card_type][:name])
154
+ assert_equal("https://mingle.example.com/api/v2/projects/atlas/card_types/27.xml", change[:new_value][:card_type][:url])
155
+ end
156
+
157
+ def test_parse_card_type_change_to_deleted_type
158
+ element_xml_text = %{
159
+ <entry>
160
+ <content type="application/vnd.mingle+xml">
161
+ <changes>
162
+ <change type="card-type-change">
163
+ <old_value>
164
+ <card_type url="https://mingle.example.com/api/v2/projects/atlas/card_types/30.xml">
165
+ <name>Story</name>
166
+ </card_type>
167
+ </old_value>
168
+ <new_value>
169
+ <deleted_card_type>
170
+ <name>Card</name>
171
+ </deleted_card_type>
172
+ </new_value>
173
+ </change>
174
+ </changes>
175
+ </content>
176
+ </entry>}
177
+ entry = Entry.from_snippet(element_xml_text)
178
+
179
+ change = entry.changes.first
180
+ assert_equal(Category::CARD_TYPE_CHANGE, change[:type])
181
+ assert_equal(Category::CARD_TYPE_CHANGE, change[:category])
182
+ assert_equal("Story", change[:old_value][:card_type][:name])
183
+ assert_equal("https://mingle.example.com/api/v2/projects/atlas/card_types/30.xml", change[:old_value][:card_type][:url])
184
+ assert_equal("Card", change[:new_value][:deleted_card_type][:name])
185
+ assert_equal(nil, change[:new_value][:deleted_card_type][:url])
186
+ end
187
+
188
+ def test_parse_card_property_change
189
+ element_xml_text = %{
190
+ <entry>
191
+ <content type="application/vnd.mingle+xml">
192
+ <changes>
193
+ <change type="property-change">
194
+ <property_definition
195
+ url="http://mingle.example.com/api/v2/projects/atlas/property_definitions/10418.xml">
196
+ <name>Priority</name>
197
+ <position nil="true"></position>
198
+ <data_type>string</data_type>
199
+ <is_numeric type="boolean">false</is_numeric>
200
+ </property_definition>
201
+ <old_value>nice</old_value>
202
+ <new_value>must</new_value>
203
+ </change>
204
+ </changes>
205
+ </content>
206
+ </entry>}
207
+ entry = Entry.from_snippet(element_xml_text)
208
+
209
+ change = entry.changes.first
210
+ assert_equal(Category::PROPERTY_CHANGE, change[:type])
211
+ assert_equal(Category::PROPERTY_CHANGE, change[:category])
212
+ assert_equal(
213
+ "http://mingle.example.com/api/v2/projects/atlas/property_definitions/10418.xml",
214
+ change[:property_definition][:url]
215
+ )
216
+ assert_equal("Priority", change[:property_definition][:name])
217
+ assert_equal("nice", change[:old_value])
218
+ assert_equal("must", change[:new_value])
219
+ end
220
+
221
+ def test_parse_card_property_change_from_nil
222
+ element_xml_text = %{
223
+ <entry>
224
+ <content type="application/vnd.mingle+xml">
225
+ <changes>
226
+ <change type="property-change">
227
+ <property_definition
228
+ url="http://mingle.example.com/api/v2/projects/atlas/property_definitions/10418.xml">
229
+ <name>Priority</name>
230
+ <position nil="true"></position>
231
+ <data_type>string</data_type>
232
+ <is_numeric type="boolean">false</is_numeric>
233
+ </property_definition>
234
+ <old_value nil="true"></old_value>
235
+ <new_value>must</new_value>
236
+ </change>
237
+ </changes>
238
+ </content>
239
+ </entry>}
240
+ entry = Entry.from_snippet(element_xml_text)
241
+
242
+ change = entry.changes.first
243
+ assert_nil(change[:old_value])
244
+ assert_equal("must", change[:new_value])
245
+ end
246
+
247
+ def test_parse_card_property_change_to_nil
248
+ element_xml_text = %{
249
+ <entry>
250
+ <content type="application/vnd.mingle+xml">
251
+ <changes>
252
+ <change type="property-change">
253
+ <property_definition
254
+ url="http://mingle.example.com/api/v2/projects/atlas/property_definitions/10418.xml">
255
+ <name>Priority</name>
256
+ <position nil="true"></position>
257
+ <data_type>string</data_type>
258
+ <is_numeric type="boolean">false</is_numeric>
259
+ </property_definition>
260
+ <old_value>nice</old_value>
261
+ <new_value nil="true"></new_value>
262
+ </change>
263
+ </changes>
264
+ </content>
265
+ </entry>}
266
+ entry = Entry.from_snippet(element_xml_text)
267
+
268
+ change = entry.changes.first
269
+ assert_equal("nice", change[:old_value])
270
+ assert_nil(change[:new_value])
271
+ end
272
+
273
+ def test_foo
274
+ foo = %{
275
+ <entry>
276
+ <id>https://mingle09.thoughtworks.com/projects/mingle/events/index/1344945</id>
277
+ <title>story #67 CRUD Project created</title>
278
+ <updated>2006-11-13T05:45:06Z</updated>
279
+ <author>
280
+ <name>Jon Tirsen</name>
281
+ <email>jtirsen@thoughtworks.com</email>
282
+ <uri>https://mingle09.thoughtworks.com/api/v2/users/10040.xml</uri>
283
+ </author>
284
+ <link href="https://mingle09.thoughtworks.com/api/v2/projects/mingle/cards/67.xml" rel="http://www.thoughtworks-studios.com/ns/mingle#event-source" type="application/vnd.mingle+xml" title="story #67"/>
285
+ <link href="https://mingle09.thoughtworks.com/projects/mingle/cards/67" rel="http://www.thoughtworks-studios.com/ns/mingle#event-source" type="text/html" title="story #67"/>
286
+ <link href="https://mingle09.thoughtworks.com/api/v2/projects/mingle/cards/67.xml?version=1" rel="http://www.thoughtworks-studios.com/ns/mingle#version" type="application/vnd.mingle+xml" title="story #67 (v1)"/>
287
+ <link href="https://mingle09.thoughtworks.com/projects/mingle/cards/67?version=1" rel="http://www.thoughtworks-studios.com/ns/mingle#version" type="text/html" title="story #67 (v1)"/>
288
+ <category term="card" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
289
+ <category term="card-creation" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
290
+ <category term="card-type-change" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
291
+ <category term="description-change" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
292
+ <category term="name-change" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
293
+ <category term="property-change" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
294
+ <content type="application/vnd.mingle+xml">
295
+ <changes xmlns="http://www.thoughtworks-studios.com/ns/mingle">
296
+ <change type="card-creation"/>
297
+ <change type="card-type-change">
298
+ <old_value nil="true"/>
299
+ <new_value>
300
+ <card_type url="https://mingle09.thoughtworks.com/api/v2/projects/mingle/card_types/10134.xml">
301
+ <name>story</name>
302
+ </card_type>
303
+ </new_value>
304
+ </change>
305
+ <change type="description-change">
306
+ </change>
307
+ <change type="name-change">
308
+ <old_value nil="true"/>
309
+ <new_value>CRUD Project</new_value>
310
+ </change>
311
+ <change type="property-change">
312
+ <property_definition url="https://mingle09.thoughtworks.com/api/v2/projects/mingle/property_definitions/10380.xml">
313
+ <name>release</name>
314
+ <position nil="true"/>
315
+ <data_type>string</data_type>
316
+ <is_numeric type="boolean">false</is_numeric>
317
+ </property_definition>
318
+ <old_value nil="true"/>
319
+ <new_value>0.92</new_value>
320
+ </change>
321
+ <change type="property-change">
322
+ <property_definition url="https://mingle09.thoughtworks.com/api/v2/projects/mingle/property_definitions/10381.xml">
323
+ <name>Priority</name>
324
+ <position nil="true"/>
325
+ <data_type>string</data_type>
326
+ <is_numeric type="boolean">false</is_numeric>
327
+ </property_definition>
328
+ <old_value nil="true"/>
329
+ <new_value>Should</new_value>
330
+ </change>
331
+ </changes>
332
+ </content>
333
+ </entry>
334
+ }
335
+
336
+ entry = Entry.from_snippet(foo)
337
+ entry.changes.each do |c|
338
+ puts c
339
+ end
340
+ end
341
+
342
+ end
343
+ end
344
+ end
@@ -7,7 +7,7 @@ module MingleEvents
7
7
 
8
8
  def test_parse_basic_attributes
9
9
  element_xml_text = %{
10
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
10
+ <entry>
11
11
  <id>https://mingle.example.com/projects/mingle/events/index/234443</id>
12
12
  <title>Page Special:HeaderActions changed</title>
13
13
  <updated>2011-02-03T08:12:42Z</updated>
@@ -16,10 +16,9 @@ module MingleEvents
16
16
  <email>sammy@example.com</email>
17
17
  <uri>https://mingle.example.com/api/v2/users/233.xml</uri>
18
18
  </author>
19
- </entry>}
20
- element = Nokogiri::XML(element_xml_text)
21
-
22
- entry = Entry.new(element)
19
+ </entry>
20
+ }
21
+ entry = Entry.from_snippet(element_xml_text)
23
22
  # assert_equal(element_xml_text.inspect, entry.raw_xml.inspect)
24
23
  assert_equal("https://mingle.example.com/projects/mingle/events/index/234443", entry.entry_id)
25
24
  assert_equal("Page Special:HeaderActions changed", entry.title)
@@ -29,13 +28,11 @@ module MingleEvents
29
28
 
30
29
  def test_parse_categories
31
30
  element_xml_text = %{
32
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
31
+ <entry>
33
32
  <category term="foo" scheme='http://tws.com/ns#mingle' />
34
33
  <category term="bar" scheme="http://tws.com/ns#go" />
35
34
  </entry>}
36
- element = Nokogiri::XML(element_xml_text)
37
-
38
- entry = Entry.new(element)
35
+ entry = Entry.from_snippet(element_xml_text)
39
36
  assert_equal(
40
37
  [Category.new('foo', 'http://tws.com/ns#mingle'), Category.new('bar', 'http://tws.com/ns#go')],
41
38
  entry.categories
@@ -48,29 +45,25 @@ module MingleEvents
48
45
  # that the card number is derived from a single, precise position
49
46
 
50
47
  element_xml_text = %{
51
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
48
+ <entry>
52
49
  <category term="card" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
53
50
  <link href="https://mingle.example.com/projects/atlas/cards/102" rel="http://www.thoughtworks-studios.com/ns/mingle#event-source" type="text/html" title="bug #103"/>
54
51
  <link href="https://mingle.example.com/api/v2/projects/atlas/cards/104.xml?version=7" rel="http://www.thoughtworks-studios.com/ns/mingle#version" type="application/vnd.mingle+xml" title="bug #105 (v7)"/>
55
52
  <link href="https://mingle.example.com/api/v2/projects/atlas/cards/106.xml" rel="http://www.thoughtworks-studios.com/ns/mingle#event-source" type="application/vnd.mingle+xml" title="bug #107"/>
56
53
  <link href="https://mingle.example.com/projects/atlas/cards/108?version=17" rel="http://www.thoughtworks-studios.com/ns/mingle#version" type="text/html" title="bug #109 (v7)"/>
57
54
  </entry>}
58
- element = Nokogiri::XML(element_xml_text)
59
-
60
- entry = Entry.new(element)
55
+ entry = Entry.from_snippet(element_xml_text)
61
56
  assert_equal(106, entry.card_number)
62
57
  assert_equal(7, entry.version)
63
58
  end
64
59
 
65
60
  def test_card_number_and_version_throws_error_when_event_not_related_to_a_card
66
61
  element_xml_text = %{
67
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
62
+ <entry>
68
63
  <category term="page" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
69
64
  </entry>}
70
- element = Nokogiri::XML(element_xml_text)
71
-
72
- entry = Entry.new(element)
73
-
65
+ entry = Entry.from_snippet(element_xml_text)
66
+
74
67
  begin
75
68
  entry.card_number
76
69
  fail("Should not have been able to retrieve a card number for non card-related event!")
@@ -93,27 +86,23 @@ module MingleEvents
93
86
  # that the card number is derived from a single, precise position
94
87
 
95
88
  element_xml_text = %{
96
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
89
+ <entry>
97
90
  <category term="card" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
98
91
  <link href="https://mingle.example.com/projects/atlas/cards/102" rel="http://www.thoughtworks-studios.com/ns/mingle#event-source" type="text/html" title="bug #103"/>
99
92
  <link href="https://mingle.example.com/api/v2/projects/atlas/cards/104.xml?version=7" rel="http://www.thoughtworks-studios.com/ns/mingle#version" type="application/vnd.mingle+xml" title="bug #105 (v7)"/>
100
93
  <link href="https://mingle.example.com/api/v2/projects/atlas/cards/106.xml" rel="http://www.thoughtworks-studios.com/ns/mingle#event-source" type="application/vnd.mingle+xml" title="bug #107"/>
101
94
  <link href="https://mingle.example.com/projects/atlas/cards/108?version=7" rel="http://www.thoughtworks-studios.com/ns/mingle#version" type="text/html" title="bug #109 (v7)"/>
102
95
  </entry>}
103
- element = Nokogiri::XML(element_xml_text)
104
-
105
- entry = Entry.new(element)
96
+ entry = Entry.from_snippet(element_xml_text)
106
97
  assert_equal('https://mingle.example.com/api/v2/projects/atlas/cards/104.xml?version=7', entry.card_version_resource_uri)
107
98
  end
108
99
 
109
100
  def test_card_version_resource_uri_throws_error_when_not_card_event
110
101
  element_xml_text = %{
111
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
102
+ <entry>
112
103
  <category term="page" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
113
104
  </entry>}
114
- element = Nokogiri::XML(element_xml_text)
115
-
116
- entry = Entry.new(element)
105
+ entry = Entry.from_snippet(element_xml_text)
117
106
  begin
118
107
  entry.card_version_resource_uri
119
108
  fail("Should not have been able to retrieve a card version resource URI for non card-related event!")
@@ -124,37 +113,35 @@ module MingleEvents
124
113
 
125
114
  def test_entry_id_aliased_as_event_id
126
115
  element_xml_text = %{
127
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
116
+ <entry>
128
117
  <id>https://mingle.example.com/projects/mingle/events/index/234443</id>
129
118
  </entry>}
130
- element = Nokogiri::XML(element_xml_text)
131
-
132
- entry = Entry.new(element)
119
+ entry = Entry.from_snippet(element_xml_text)
133
120
  assert_equal('https://mingle.example.com/projects/mingle/events/index/234443', entry.event_id)
134
121
  assert_equal(entry.entry_id, entry.event_id)
135
122
  end
136
123
 
137
124
  def test_entry_id_determines_equality
138
125
  element_xml_text_1 = %{
139
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
126
+ <entry>
140
127
  <id>https://mingle.example.com/projects/mingle/events/index/234443</id>
141
128
  <category term="page" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
142
129
  </entry>}
143
- entry_1 = Entry.new(Nokogiri::XML(element_xml_text_1))
130
+ entry_1 = Entry.from_snippet(element_xml_text_1)
144
131
 
145
132
  element_xml_text_2 = %{
146
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
133
+ <entry>
147
134
  <id>https://mingle.example.com/projects/mingle/events/index/234443</id>
148
135
  <category term="card" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
149
136
  </entry>}
150
- entry_2 = Entry.new(Nokogiri::XML(element_xml_text_2))
137
+ entry_2 = Entry.from_snippet(element_xml_text_2)
151
138
 
152
139
  element_xml_text_3 = %{
153
- <entry xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
140
+ <entry>
154
141
  <id>https://mingle.example.com/projects/mingle/events/index/234</id>
155
142
  <category term="card" scheme="http://www.thoughtworks-studios.com/ns/mingle#categories"/>
156
143
  </entry>}
157
- entry_3 = Entry.new(Nokogiri::XML(element_xml_text_3))
144
+ entry_3 = Entry.from_snippet(element_xml_text_3)
158
145
 
159
146
  assert entry_1.eql?(entry_2)
160
147
  assert entry_1 == entry_2
@@ -7,7 +7,7 @@ module MingleEvents
7
7
 
8
8
  def test_entries_are_enumerable
9
9
  latest_entries_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access)
10
-
10
+
11
11
  assert_equal([
12
12
  'https://mingle.example.com/projects/atlas/events/index/103',
13
13
  'https://mingle.example.com/projects/atlas/events/index/101',
@@ -26,56 +26,7 @@ module MingleEvents
26
26
  last_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', stub_mingle_access)
27
27
  assert_nil(last_page.next)
28
28
  end
29
-
30
- def test_previous_page_returns_the_page_of_entries_as_specified_by_previous_link
31
- current_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', stub_mingle_access)
32
- previous_page = current_page.previous
33
- assert_equal('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2', previous_page.url)
34
- assert_equal('https://mingle.example.com/projects/atlas/events/index/99', previous_page.entries.first.entry_id)
35
- end
36
-
37
- def test_previous_page_when_on_latest_entries
38
- latest_entries_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access)
39
- assert_nil(latest_entries_page.previous)
40
- end
41
-
42
- def test_can_determine_whether_archived
43
- assert !Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access).archived?
44
- assert !Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=3', stub_mingle_access).archived?
45
- assert Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', stub_mingle_access).archived?
46
- end
47
-
48
- def test_can_determine_closest_archived_page
49
- assert_equal 'https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2',
50
- Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access).closest_archived_page.url
51
- assert_equal 'https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2',
52
- Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2', stub_mingle_access).closest_archived_page.url
53
- end
54
-
55
- def test_can_determine_when_there_is_no_closest_archived_page
56
- mingle_access = StubMingleAccess.new
57
- mingle_access.register_page_content('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1',
58
- %{
59
- <feed xmlns="http://www.w3.org/2005/Atom" xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
60
- <link href="https://mingle.example.com/api/v2/projects/event_tester/feeds/events.xml" rel="current"/>
61
- <link href="https://mingle.example.com/api/v2/projects/event_tester/feeds/events.xml" rel="self"/>
62
- <entry>
63
- <id>https://mingle.example.com/projects/event_tester/events/index/390</id>
64
- <title>Card #2 Card Two created</title>
65
- <updated>2011-08-02T22:27:38Z</updated>
66
- <author><name>David</name></author>
67
- </entry>
68
- <entry>
69
- <id>https://mingle.example.com/projects/event_tester/events/index/389</id>
70
- <title>Card #1 Card One created</title>
71
- <updated>2011-08-02T22:27:36Z</updated>
72
- <author><name>David</name></author>
73
- </entry>
74
- </feed>
75
- })
76
- assert_nil Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', mingle_access).closest_archived_page
77
- end
78
-
29
+
79
30
  end
80
31
 
81
32
  end
@@ -76,7 +76,7 @@ module MingleEvents
76
76
  <author><name>Bob</name></author>
77
77
  </entry>
78
78
  }
79
- Feed::Entry.new(Nokogiri::XML(entry_xml).at('/entry'))
79
+ Feed::Entry.from_snippet(entry_xml)
80
80
  end
81
81
  end
82
82
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mingle_events
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 6
10
- version: 0.0.6
9
+ - 7
10
+ version: 0.0.7
11
11
  platform: ruby
12
12
  authors:
13
13
  - David Rice
@@ -59,7 +59,7 @@ files:
59
59
  - Gemfile
60
60
  - lib/mingle_events/feed/author.rb
61
61
  - lib/mingle_events/feed/category.rb
62
- - lib/mingle_events/feed/element_support.rb
62
+ - lib/mingle_events/feed/changes.rb
63
63
  - lib/mingle_events/feed/entry.rb
64
64
  - lib/mingle_events/feed/page.rb
65
65
  - lib/mingle_events/feed.rb
@@ -83,6 +83,7 @@ files:
83
83
  - README.textile
84
84
  - test/mingle_events/feed/author_test.rb
85
85
  - test/mingle_events/feed/category_test.rb
86
+ - test/mingle_events/feed/changes_test.rb
86
87
  - test/mingle_events/feed/entry_test.rb
87
88
  - test/mingle_events/feed/page_test.rb
88
89
  - test/mingle_events/poller_test.rb
@@ -1,19 +0,0 @@
1
- module MingleEvents
2
- module Feed
3
-
4
- # Provides some helpers for pulling values from Nokogiri Elems
5
- module ElementSupport
6
-
7
- def element_text(parent_element, element_name, optional = false)
8
- element = parent_element.at(".//#{element_name}")
9
- if optional && element.nil?
10
- nil
11
- else
12
- element.inner_text
13
- end
14
- end
15
-
16
- end
17
-
18
- end
19
- end