Merge lp:~sinzui/charmworld/api3-interesting into lp:~juju-jitsu/charmworld/trunk

Proposed by Curtis Hovey
Status: Merged
Approved by: Curtis Hovey
Approved revision: 370
Merged at revision: 366
Proposed branch: lp:~sinzui/charmworld/api3-interesting
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 111 lines (+50/-6)
2 files modified
charmworld/views/api.py (+7/-6)
charmworld/views/tests/test_api.py (+43/-0)
To merge this branch: bzr merge lp:~sinzui/charmworld/api3-interesting
Reviewer Review Type Date Requested Status
Brad Crittenden (community) Approve
Review via email: mp+181910@code.launchpad.net

Commit message

Update interesting to support bundles.

Description of the change

Update interesting to support bundles.

RULES

    Pre-implementation: no one
    * Change the calls in _interesting_charms() to pass the doctype
    * Update API2 to always pass CHARMS.
    * Update API3 to pass None (to get both CHARMS and BUNDLES)

    FUTURE
    * Add date_created to bundles?
    * Add downloads to bundles?
    * Update get_featured to work with the charms and bundles collections
      possibly by extending http://pastebin.ubuntu.com/6018943/

QA

    * Visit http://staging.jujucharms.com/heartbeat
    * Verify all four checks pass.
    * Visit http://staging.jujucharms.com/api/2/charms/interesting
    * Verify list contains charms.
    * Visit http://staging.jujucharms.com/api/3/charms/interesting
    * Verify the data contains a lists of new, popular and featured
      charms. The wiki bundle wont appear because it has no downloads
      nor is it new.

IMPLEMENTATION

I cut the scope of this branch when I realised other parts of the code
need to change to truly show bundles with charms in production. This
branch does allow API to include bundles in new, popular, and featured,
but there are underlying data issues that prevent bundles from appearing.

The tests are simplified versions of other "interesting" tests that
checks the specific rules of the content of new, popular, and featured.
These tests only verify that API3 includes bundles and charms while
preserving the charm-only rule of API2.

While the tests show that bundles are supported, they wont be in
production! Bundles don't have a date_created attr so they are ranked
last. They appear in test because there are only two things in the db.
Similarly, bundles don't have a downloads attr, they are ranked last and
appear because there are only two things in the test. Following the
example of charms, we can use the branch's date_created as the bundle's
date_created. We need to count bundle json requests and store them in a
downloads attr to collect popular data. Bundles can be featured (as
demonstrated by the test setup) but we cannot see them in results.

I tried to solve including featured bundles. I got stuck when I realised
that get_featured() takes the charms collection as an argument. I suspect
we want to pass both the charms and bundle collections to get the right
data, but I don't know ho we want to merge the two lists? bundles first,
charms first, or intermingle them based on some attr? This is the diff
if what I attempted to allow me to locate featured charms and bundles.
    http://pastebin.ubuntu.com/6018943/

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

I have some additional thoughts about getting featured charms and bundles. The current implementation is a mongo collection find, but all other API search methods use elasticsearch. The current implementation uses the featured data to create a find spec. We could instead use the featured data to create a list of ids to pass the index_clients muiltget() function. We would get charms and bundles from ES as we do for new and popular.

Revision history for this message
Brad Crittenden (bac) wrote :

The code and tests look nice. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmworld/views/api.py'
2--- charmworld/views/api.py 2013-08-22 20:33:45 +0000
3+++ charmworld/views/api.py 2013-08-23 20:00:56 +0000
4@@ -463,7 +463,7 @@
5 if path is None or path == ['']:
6 handler = self._search
7 elif path[0] == 'interesting':
8- handler = self._interesting_charms
9+ handler = self._interesting_charms_and_bundles
10 else:
11 raise HTTPNotFound(self.request.path)
12 return self._handle(handler, args, kwargs)
13@@ -520,15 +520,16 @@
14 results.append(result)
15 return results
16
17- def _interesting_charms(self):
18+ def _interesting_charms_and_bundles(self, doctype=None):
19 """Generate a JSON structure of interesting charms.
20
21 Includes featured, new and popular charms.
22 """
23 db = self.request.db
24 popular = self.request.index_client.api_search(
25- sort='downloaded', limit=10)
26- new = self.request.index_client.api_search(sort='new', limit=10)
27+ sort='downloaded', doctype=doctype, limit=10)
28+ new = self.request.index_client.api_search(
29+ sort='new', doctype=doctype, limit=10)
30 featured = FeaturedSource.from_db(db).get_featured(db.charms, 'charm')
31 return {
32 'result': {
33@@ -550,11 +551,11 @@
34 """
35
36 def charms(self, path=None, *args, **kwargs):
37+ kwargs['doctype'] = CHARM
38 if path is None or path == ['']:
39 handler = self._search
40- kwargs['doctype'] = CHARM
41 elif path[0] == 'interesting':
42- handler = self._interesting_charms
43+ handler = self._interesting_charms_and_bundles
44 else:
45 raise HTTPNotFound(self.request.path)
46 return self._handle(handler, args, kwargs)
47
48=== modified file 'charmworld/views/tests/test_api.py'
49--- charmworld/views/tests/test_api.py 2013-08-22 13:50:17 +0000
50+++ charmworld/views/tests/test_api.py 2013-08-23 20:00:56 +0000
51@@ -1436,6 +1436,16 @@
52 self.assertEqual({}, response['result']['requires'])
53
54
55+def list_names(items):
56+ names = []
57+ for item in items:
58+ if item['metadata']['doctype'] == CHARM:
59+ names.append(item['charm']['name'])
60+ else:
61+ names.append(item['bundle']['name'])
62+ return names
63+
64+
65 class TestAPI3(TestAPI, API3Mixin):
66 """Test API 3."""
67
68@@ -1459,6 +1469,24 @@
69 self.assertEqual(CHARM, found_charm['metadata']['doctype'])
70 self.assertEqual(charm['store_url'], found_charm['charm']['url'])
71
72+ def test_search_interesting_contains_charms_and_bundles(self):
73+ self.use_index_client()
74+ bundle = self.make_indexed_bundle(name='bat')
75+ FeaturedSource.from_db(self.db).set_featured(
76+ bundle._representation, BUNDLE)
77+ charm = self.make_indexed_charm()
78+ FeaturedSource.from_db(self.db).set_featured(charm, CHARM)
79+ result = self.get_response(
80+ self.search_endpoint, 'interesting').json_body['result']
81+ self.assertEqual(3, len(result))
82+ expected = [charm['name'], bundle.name]
83+ self.assertEqual(expected, list_names(result['popular']))
84+ self.assertEqual(expected, list_names(result['new']))
85+ # XXX sinzui 2013-08-23: We want this to include bundles, but that
86+ # is somewhat hard to do with get_featured() limited to just the
87+ # charms collection.
88+ self.assertEqual([charm['name']], list_names(result['featured']))
89+
90
91 class TestAPI2(TestAPI, API2Mixin):
92 """Test API 2."""
93@@ -1485,3 +1513,18 @@
94 result = self.get_response(self.search_endpoint).json_body['result']
95 self.assertEqual(1, len(result))
96 self.assertEqual(charm['store_url'], result[0]['charm']['url'])
97+
98+ def test_charms_interesting_only_contain_charms(self):
99+ self.use_index_client()
100+ bundle = self.make_indexed_bundle()
101+ FeaturedSource.from_db(self.db).set_featured(
102+ bundle._representation, BUNDLE)
103+ charm = self.make_indexed_charm()
104+ FeaturedSource.from_db(self.db).set_featured(charm, CHARM)
105+ result = self.get_response(
106+ self.search_endpoint, 'interesting').json_body['result']
107+ self.assertEqual(3, len(result))
108+ expected = [charm['name']]
109+ self.assertEqual(expected, list_names(result['popular']))
110+ self.assertEqual(expected, list_names(result['new']))
111+ self.assertEqual(expected, list_names(result['featured']))

Subscribers

People subscribed via source and target branches