Merge lp:~adeuring/charmworld/1199790-last-change-error into lp:~juju-jitsu/charmworld/trunk

Proposed by Abel Deuring
Status: Merged
Approved by: Abel Deuring
Approved revision: 329
Merged at revision: 329
Proposed branch: lp:~adeuring/charmworld/1199790-last-change-error
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 228 lines (+82/-22)
7 files modified
charmworld/models.py (+17/-2)
charmworld/testing/factory.py (+17/-13)
charmworld/tests/test_models.py (+18/-4)
charmworld/views/charms.py (+0/-1)
charmworld/views/helpers.py (+7/-2)
charmworld/views/tests/test_api.py (+12/-0)
charmworld/views/tests/test_helpers.py (+11/-0)
To merge this branch: bzr merge lp:~adeuring/charmworld/1199790-last-change-error
Reviewer Review Type Date Requested Status
Abel Deuring (community) Approve
Aaron Bentley (community) Approve
Review via email: mp+177865@code.launchpad.net

Commit message

Return dummy data for Charm.last_change and Charm.first_change, if a branch does not have any commits.

Description of the change

This branch fixes bug 1199780: search requests errors when a charm has no
last_change

While the root cause of the error described in the bug report (a branch is
registered in LP but no real branch exists) may be fixed by recent changes
to the ingest job, a similar situation may still exist: A branch that
exists but that has no commits.

This problem affects not only the API views but also views which
use the function views.helpers.format_change().

I changed class Charm in models.py so that it does not return None in
this case but some dummy data.

The function format_change() still needs a change: Calling
datetime.strftime() obviously does not work if for non-existent
timestamp -- and using a default timestamp does not make sense either.

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

This is fine as a first step, but I hope there will be a follow-on that changes all the views to check for None when accessing Charm.last_change and Charm.first_change, since that will look better.

review: Approve
Revision history for this message
Charmworld Lander (charmworld-lander) wrote :

No commit message specified.

Revision history for this message
Abel Deuring (adeuring) :
review: Approve
Revision history for this message
Charmworld Lander (charmworld-lander) wrote :

Attempt to merge into lp:charmworld failed due to conflicts:

text conflict in charmworld/testing/factory.py

Revision history for this message
Abel Deuring (adeuring) :
review: Approve
Revision history for this message
Charmworld Lander (charmworld-lander) wrote :

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Revision history for this message
Abel Deuring (adeuring) :
review: Approve
Revision history for this message
Abel Deuring (adeuring) wrote :

I think there is no need for further changes. format_change() is used in templates/charm.pt, but not for first_change or last_change:

              <tr tal:repeat="change charm.changes">
                <td tal:content="structure: format_change(change, charm)"></td>
              </tr>

The second callsite of format_change is in views.feeds._recently_changed(), which in turn is used in recently_changed()
 serving the URL http://staging.jujucharms.com/recently-changed and in recently_changed_feed(), serving the URL http://staging.jujucharms.com/feed/charm-changes. IMHO, an empty string dor the last change does not matter on these pages.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmworld/models.py'
2--- charmworld/models.py 2013-07-30 19:14:07 +0000
3+++ charmworld/models.py 2013-07-31 17:04:21 +0000
4@@ -470,15 +470,30 @@
5 """The list of revision info dicts from the charm's branch."""
6 return self._representation['changes']
7
8+ def _get_change(self, name):
9+ # Some view functions assume that last_change and first_change
10+ # always exist, but this is not true for empty branches. Return
11+ # some default data in this case.
12+ change = self._representation[name]
13+ if change is None:
14+ return {
15+ 'authors': [],
16+ 'revno': 0,
17+ 'committer': '',
18+ 'created': None,
19+ 'message': '',
20+ }
21+ return change
22+
23 @property
24 def first_change(self):
25 """The revision dict of the first change to the charm's branch."""
26- return self._representation['first_change']
27+ return self._get_change('first_change')
28
29 @property
30 def last_change(self):
31 """The revision dict of the last change to the charm's branch."""
32- return self._representation['last_change']
33+ return self._get_change('last_change')
34
35 @property
36 def revision(self):
37
38=== modified file 'charmworld/testing/factory.py'
39--- charmworld/testing/factory.py 2013-07-31 14:41:29 +0000
40+++ charmworld/testing/factory.py 2013-07-31 17:04:21 +0000
41@@ -172,7 +172,7 @@
42 date_created=None, downloads=0,
43 downloads_in_past_30_days=0, is_featured=False,
44 promulgated=False, categories=None, branch_deleted=False,
45- proof=None, payload=None):
46+ proof=None, payload=None, empty_branch=False):
47 """Return the json of a charm."""
48 if not description:
49 description = """byobu-class provides remote terminal access through
50@@ -254,12 +254,6 @@
51 "changes": changes,
52 "description": description,
53 "doctype": "charm",
54- "first_change": {
55- "committer": "Jane Doe <jane@sample.com>",
56- "created": 1308093138.892,
57- "message": "initial checkin\n",
58- "revno": 1
59- },
60 "hooks": [
61 "install",
62 "start",
63@@ -267,12 +261,6 @@
64 "website-relation-joined"
65 ],
66 'is_featured': is_featured,
67- "last_change": {
68- "committer": "John Doe <jdoe@example.com>",
69- "created": 1343082725.06,
70- "message": commit_message,
71- "revno": revno,
72- },
73 "maintainer": maintainer,
74 "proof": proof,
75 "provides": provides,
76@@ -287,6 +275,22 @@
77 'downloads_in_past_30_days': downloads_in_past_30_days,
78 'date_created': date_created.isoformat(),
79 })
80+ if empty_branch:
81+ charm["first_change"] = None
82+ charm["last_change"] = None
83+ else:
84+ charm["first_change"] = {
85+ "committer": "Jane Doe <jane@sample.com>",
86+ "created": 1308093138.892,
87+ "message": "initial checkin\n",
88+ "revno": 1
89+ }
90+ charm["last_change"] = {
91+ "committer": "John Doe <jdoe@example.com>",
92+ "created": 1343082725.06,
93+ "message": commit_message,
94+ "revno": revno,
95+ }
96 if tests is not None:
97 charm['tests'] = tests
98 charm['test_results'] = {}
99
100=== modified file 'charmworld/tests/test_models.py'
101--- charmworld/tests/test_models.py 2013-07-30 19:14:07 +0000
102+++ charmworld/tests/test_models.py 2013-07-31 17:04:21 +0000
103@@ -346,9 +346,16 @@
104 charm_data['first_change'] = change
105 charm = Charm(charm_data)
106 self.assertIs(change, charm.first_change)
107- # The default is None.
108+ # The default is some dummy data.
109 charm = Charm({})
110- self.assertIs(None, charm.first_change)
111+ expected = {
112+ 'authors': [],
113+ 'committer': '',
114+ 'created': None,
115+ 'message': '',
116+ 'revno': 0,
117+ }
118+ self.assertEqual(expected, charm.first_change)
119
120 def test_last_change(self):
121 # The last_change property is a revision info dict.
122@@ -357,9 +364,16 @@
123 charm_data['last_change'] = change
124 charm = Charm(charm_data)
125 self.assertIs(change, charm.last_change)
126- # The default is None.
127+ # The default is some dummy data.
128 charm = Charm({})
129- self.assertIs(None, charm.last_change)
130+ expected = {
131+ 'authors': [],
132+ 'committer': '',
133+ 'created': None,
134+ 'message': '',
135+ 'revno': 0,
136+ }
137+ self.assertEqual(expected, charm.last_change)
138
139 def test_revision(self):
140 # The revision property is an int.
141
142=== modified file 'charmworld/views/charms.py'
143--- charmworld/views/charms.py 2013-07-24 15:05:38 +0000
144+++ charmworld/views/charms.py 2013-07-31 17:04:21 +0000
145@@ -124,7 +124,6 @@
146 endpoint='charm'
147 )
148 icon_path = icon_path + '/' + remainder
149-
150 return {
151 "charm": charm,
152 'format_change': format_change,
153
154=== modified file 'charmworld/views/helpers.py'
155--- charmworld/views/helpers.py 2013-07-24 20:01:52 +0000
156+++ charmworld/views/helpers.py 2013-07-31 17:04:21 +0000
157@@ -13,8 +13,13 @@
158 def format_change(change, charm=None):
159 """Format a bzr commit."""
160 parts = []
161- date = datetime.utcfromtimestamp(change["created"])
162- parts.append(unicode(date.strftime("%Y/%m/%d")))
163+ date = change["created"]
164+ # bug 1199790: We may have a branch without any commits.
165+ # In this case, class Charm returns dummy data for its attributes
166+ # first_change and last_change.
167+ if date is not None:
168+ date = datetime.utcfromtimestamp(date)
169+ parts.append(unicode(date.strftime("%Y/%m/%d")))
170 parts.append(name_filter(change['committer']))
171 parts.append(change["message"][:100])
172 if charm is not None:
173
174=== modified file 'charmworld/views/tests/test_api.py'
175--- charmworld/views/tests/test_api.py 2013-07-26 14:33:11 +0000
176+++ charmworld/views/tests/test_api.py 2013-07-31 17:04:21 +0000
177@@ -8,6 +8,7 @@
178 datetime,
179 )
180 import hashlib
181+import json
182
183 from pyramid.httpexceptions import HTTPNotFound
184 from webob.etag import ETagMatcher
185@@ -607,6 +608,17 @@
186 self.assertNotIn('related', data['result']['popular'][0]['metadata'])
187 self.assertNotIn('related', data['result']['featured'][0]['metadata'])
188
189+ def test_charm_with_empty_branch(self):
190+ # Empty branches do not have real data for the attributes
191+ # first_change and last_change. The view is nevertheless properly
192+ # rendered.
193+ self.makeCharm(empty_branch=True)
194+ result = json.loads(self.get_response('charms').body)['result'][0]
195+ # But the returned data does contain only dummy revision related
196+ # data.
197+ self.assertEqual('0', result['charm']['code_source']['revision'])
198+ self.assertEqual('', result['charm']['code_source']['last_log'])
199+
200
201 class TestAPI2Charms(TestAPICharms, API2Mixin):
202 """Test API 2's "charms" endpoint."""
203
204=== modified file 'charmworld/views/tests/test_helpers.py'
205--- charmworld/views/tests/test_helpers.py 2013-06-26 12:20:17 +0000
206+++ charmworld/views/tests/test_helpers.py 2013-07-31 17:04:21 +0000
207@@ -3,6 +3,7 @@
208
209 from charmworld.models import Charm
210 from charmworld.views.helpers import (
211+ format_change,
212 interfaces,
213 result_sorter,
214 )
215@@ -129,3 +130,13 @@
216 charm_b = factory.get_charm_json(series='raring')
217 with self.assertNoException():
218 result_sorter(charm_a, charm_b)
219+
220+ def test_format_change(self):
221+ charm = Charm(factory.get_charm_json())
222+ result = format_change(charm.last_change)
223+ self.assertEqual('2012/07/23 John Doe maintainer', result)
224+
225+ def test_format_change_empty_branch(self):
226+ charm = Charm(factory.get_charm_json(empty_branch=True))
227+ result = format_change(charm.last_change)
228+ self.assertEqual(' ', result)

Subscribers

People subscribed via source and target branches