Merge lp:~stolowski/unity-scope-click/departments into lp:unity-scope-click/devel

Proposed by Paweł Stołowski
Status: Merged
Approved by: Alejandro J. Cura
Approved revision: 267
Merged at revision: 299
Proposed branch: lp:~stolowski/unity-scope-click/departments
Merge into: lp:unity-scope-click/devel
Diff against target: 2209 lines (+1596/-68)
25 files modified
libclickscope/click/CMakeLists.txt (+3/-0)
libclickscope/click/department-lookup.cpp (+84/-0)
libclickscope/click/department-lookup.h (+58/-0)
libclickscope/click/departments.cpp (+173/-0)
libclickscope/click/departments.h (+83/-0)
libclickscope/click/highlights.cpp (+95/-0)
libclickscope/click/highlights.h (+72/-0)
libclickscope/click/index.cpp (+40/-2)
libclickscope/click/index.h (+6/-1)
libclickscope/click/package.h (+1/-0)
libclickscope/click/webclient.cpp (+2/-1)
libclickscope/click/webclient.h (+1/-1)
libclickscope/tests/CMakeLists.txt (+2/-0)
libclickscope/tests/fake_json.h (+327/-0)
libclickscope/tests/mock_network_access_manager.h (+1/-0)
libclickscope/tests/mock_webclient.h (+2/-2)
libclickscope/tests/test_bootstrap.cpp (+169/-0)
libclickscope/tests/test_departments.cpp (+143/-0)
libclickscope/tests/test_index.cpp (+2/-2)
scope/clickstore/store-query.cpp (+227/-32)
scope/clickstore/store-query.h (+16/-3)
scope/clickstore/store-scope.cpp (+19/-4)
scope/clickstore/store-scope.h (+10/-0)
scope/tests/integration/webclient_integration.cpp (+1/-0)
scope/tests/test_query.cpp (+59/-20)
To merge this branch: bzr merge lp:~stolowski/unity-scope-click/departments
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Alejandro J. Cura (community) Approve
dobey Pending
Review via email: mp+224095@code.launchpad.net

This proposal supersedes a proposal from 2014-06-18.

Commit message

Add support for departments and highlights.

Description of the change

Add support for departments and highlights.

To post a comment you must log in.
Revision history for this message
dobey (dobey) wrote : Posted in a previous version of this proposal

426 +std::map<std::string, std::string> Index::build_headers(const std::string& language)
438 + {"Accept-Language", language},
486 + QSharedPointer<click::web::Response> response(client->call(
487 + get_base_url() + click::BOOTSTRAP_PATH, "GET", false, build_headers("en"), "", params)); //TODO: language

You don't need to do all this work to pass the language around or set Accept-Language manually. It is already added automatically in web::Client::cal().

review: Needs Fixing
Revision history for this message
dobey (dobey) wrote : Posted in a previous version of this proposal

Can you also add a commit with --fixes=lp:1209224 to link that bug?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
dobey (dobey) wrote : Posted in a previous version of this proposal
review: Needs Fixing
Revision history for this message
Paweł Stołowski (stolowski) wrote : Posted in a previous version of this proposal

That should have been fixed now.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Alejandro J. Cura (alecu) wrote : Posted in a previous version of this proposal

On one hand, I like this branch.
I've run it on my desktop against testing, and it's showing the highlights as expected, and seems to be working fine otherwise, so kudos for that.

On the other, I'm a bit disappointed with the length, and a bit more that the unit testing for it seems lacking. Here's a list of what I've found is missing:
- tests for methods in departments.cpp (eg: from_json_root_node)
- tests for failures in departments json parsing
- tests for DepartmentLookup::get_department_info
- tests for parsing Highlights, and for failing to parse them
- tests for Index::bootstrap and Index::departments
- test_query has fixes for existing "search" tests, but it has no new tests for Highlights, Departments

Also, I think Index::bootstrap() could be rewritten as just a call to Index::departments(get_base_url() + click::BOOTSTRAP_PATH, .....)

Before approving I'd like to have jenkins build a proper package to test on the device.
After that, and since we are so close to feature freeze, I'll propose to land this, and to open a bug for the missing tests.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
263. By Paweł Stołowski

Test for invalid highlights. Cleanups.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
264. By Paweł Stołowski

Reuse push_package().

265. By Paweł Stołowski

Fixed comment.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
266. By Paweł Stołowski

Test bootstrap call.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
267. By Paweł Stołowski

Merged devel.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Branch looks good; tested the package on the device and since the webservices are not enabled in production the branch reverts to the old webservices, as expected.
Great job!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libclickscope/click/CMakeLists.txt'
2--- libclickscope/click/CMakeLists.txt 2014-06-20 20:00:53 +0000
3+++ libclickscope/click/CMakeLists.txt 2014-06-23 15:00:46 +0000
4@@ -12,6 +12,9 @@
5 add_library(${SCOPE_LIB_NAME} STATIC
6 configuration.cpp
7 download-manager.cpp
8+ department-lookup.cpp
9+ departments.cpp
10+ highlights.cpp
11 index.cpp
12 interface.cpp
13 key_file_locator.cpp
14
15=== added file 'libclickscope/click/department-lookup.cpp'
16--- libclickscope/click/department-lookup.cpp 1970-01-01 00:00:00 +0000
17+++ libclickscope/click/department-lookup.cpp 2014-06-23 15:00:46 +0000
18@@ -0,0 +1,84 @@
19+/*
20+ * Copyright (C) 2014 Canonical Ltd.
21+ *
22+ * This program is free software: you can redistribute it and/or modify it
23+ * under the terms of the GNU General Public License version 3, as published
24+ * by the Free Software Foundation.
25+ *
26+ * This program is distributed in the hope that it will be useful, but
27+ * WITHOUT ANY WARRANTY; without even the implied warranties of
28+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
29+ * PURPOSE. See the GNU General Public License for more details.
30+ *
31+ * You should have received a copy of the GNU General Public License along
32+ * with this program. If not, see <http://www.gnu.org/licenses/>.
33+ *
34+ * In addition, as a special exception, the copyright holders give
35+ * permission to link the code of portions of this program with the
36+ * OpenSSL library under certain conditions as described in each
37+ * individual source file, and distribute linked combinations
38+ * including the two.
39+ * You must obey the GNU General Public License in all respects
40+ * for all of the code used other than OpenSSL. If you modify
41+ * file(s) with this exception, you may extend this exception to your
42+ * version of the file(s), but you are not obligated to do so. If you
43+ * do not wish to do so, delete this exception statement from your
44+ * version. If you delete this exception statement from all source
45+ * files in the program, then also delete it here.
46+ */
47+
48+#include "department-lookup.h"
49+
50+namespace click
51+{
52+
53+DepartmentLookup::DepartmentLookup()
54+{
55+}
56+
57+void DepartmentLookup::rebuild(const Department::SPtr& dept)
58+{
59+ departments[dept->id()] = dept;
60+ for (auto const& subdep: dept->sub_departments())
61+ {
62+ parent_lut[subdep->id()] = dept;
63+ rebuild(subdep);
64+ }
65+}
66+
67+void DepartmentLookup::rebuild(const std::list<Department::SPtr>& root_departments)
68+{
69+ parent_lut.clear();
70+ departments.clear();
71+ for (auto const& dep: root_departments)
72+ {
73+ rebuild(dep);
74+ }
75+}
76+
77+Department::SPtr DepartmentLookup::get_parent(const std::string& department_id) const
78+{
79+ auto it = parent_lut.find(department_id);
80+ if (it != parent_lut.end())
81+ {
82+ return it->second;
83+ }
84+ return Department::SPtr(nullptr);
85+}
86+
87+Department::SPtr DepartmentLookup::get_department_info(const std::string& department_id) const
88+{
89+ auto it = departments.find(department_id);
90+ if (it != departments.end())
91+ {
92+ return it->second;
93+ }
94+ return nullptr;
95+}
96+
97+int DepartmentLookup::size() const
98+{
99+ return parent_lut.size();
100+}
101+
102+}
103
104=== added file 'libclickscope/click/department-lookup.h'
105--- libclickscope/click/department-lookup.h 1970-01-01 00:00:00 +0000
106+++ libclickscope/click/department-lookup.h 2014-06-23 15:00:46 +0000
107@@ -0,0 +1,58 @@
108+/*
109+ * Copyright (C) 2014 Canonical Ltd.
110+ *
111+ * This program is free software: you can redistribute it and/or modify it
112+ * under the terms of the GNU General Public License version 3, as published
113+ * by the Free Software Foundation.
114+ *
115+ * This program is distributed in the hope that it will be useful, but
116+ * WITHOUT ANY WARRANTY; without even the implied warranties of
117+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
118+ * PURPOSE. See the GNU General Public License for more details.
119+ *
120+ * You should have received a copy of the GNU General Public License along
121+ * with this program. If not, see <http://www.gnu.org/licenses/>.
122+ *
123+ * In addition, as a special exception, the copyright holders give
124+ * permission to link the code of portions of this program with the
125+ * OpenSSL library under certain conditions as described in each
126+ * individual source file, and distribute linked combinations
127+ * including the two.
128+ * You must obey the GNU General Public License in all respects
129+ * for all of the code used other than OpenSSL. If you modify
130+ * file(s) with this exception, you may extend this exception to your
131+ * version of the file(s), but you are not obligated to do so. If you
132+ * do not wish to do so, delete this exception statement from your
133+ * version. If you delete this exception statement from all source
134+ * files in the program, then also delete it here.
135+ */
136+
137+#ifndef CLICK_DEPARTMENT_LOOKUP_H
138+#define CLICK_DEPARTMENT_LOOKUP_H
139+
140+#include "departments.h"
141+#include <string>
142+#include <memory>
143+#include <map>
144+
145+namespace click
146+{
147+
148+class DepartmentLookup
149+{
150+ public:
151+ DepartmentLookup();
152+ void rebuild(const std::list<Department::SPtr>& root_departments);
153+ Department::SPtr get_parent(const std::string& department_id) const;
154+ Department::SPtr get_department_info(const std::string& department_id) const;
155+ int size() const;
156+
157+ private:
158+ void rebuild(const Department::SPtr& dept);
159+ std::map<std::string, Department::SPtr> parent_lut;
160+ std::map<std::string, Department::SPtr> departments;
161+};
162+
163+}
164+
165+#endif
166
167=== added file 'libclickscope/click/departments.cpp'
168--- libclickscope/click/departments.cpp 1970-01-01 00:00:00 +0000
169+++ libclickscope/click/departments.cpp 2014-06-23 15:00:46 +0000
170@@ -0,0 +1,173 @@
171+/*
172+ * Copyright (C) 2014 Canonical Ltd.
173+ *
174+ * This program is free software: you can redistribute it and/or modify it
175+ * under the terms of the GNU General Public License version 3, as published
176+ * by the Free Software Foundation.
177+ *
178+ * This program is distributed in the hope that it will be useful, but
179+ * WITHOUT ANY WARRANTY; without even the implied warranties of
180+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
181+ * PURPOSE. See the GNU General Public License for more details.
182+ *
183+ * You should have received a copy of the GNU General Public License along
184+ * with this program. If not, see <http://www.gnu.org/licenses/>.
185+ *
186+ * In addition, as a special exception, the copyright holders give
187+ * permission to link the code of portions of this program with the
188+ * OpenSSL library under certain conditions as described in each
189+ * individual source file, and distribute linked combinations
190+ * including the two.
191+ * You must obey the GNU General Public License in all respects
192+ * for all of the code used other than OpenSSL. If you modify
193+ * file(s) with this exception, you may extend this exception to your
194+ * version of the file(s), but you are not obligated to do so. If you
195+ * do not wish to do so, delete this exception statement from your
196+ * version. If you delete this exception statement from all source
197+ * files in the program, then also delete it here.
198+ */
199+
200+#include "departments.h"
201+#include <iostream>
202+
203+namespace click
204+{
205+
206+Department::Department(const std::string& id, const std::string &name, const std::string& href, bool has_children)
207+ : id_(id),
208+ name_(name),
209+ href_(href),
210+ has_children_flag_(has_children)
211+{
212+}
213+
214+std::string Department::id() const
215+{
216+ return id_;
217+}
218+
219+std::string Department::name() const
220+{
221+ return name_;
222+}
223+
224+std::string Department::href() const
225+{
226+ return href_;
227+}
228+
229+bool Department::has_children_flag() const
230+{
231+ return has_children_flag_;
232+}
233+
234+void Department::set_subdepartments(const std::list<Department::SPtr>& deps)
235+{
236+ sub_departments_ = deps;
237+}
238+
239+std::list<Department::SPtr> Department::sub_departments() const
240+{
241+ return sub_departments_;
242+}
243+
244+Json::Value Department::check_mandatory_attribute(const Json::Value& item, const std::string& name, Json::ValueType valtype)
245+{
246+ if (!item.isMember(name)) {
247+ throw std::runtime_error("Missing '" + name + "' node");
248+ }
249+ auto const val = item[name];
250+ if (val.type() != valtype) {
251+ throw std::runtime_error("Invalid type of '" + name + "' node");
252+ }
253+ return val;
254+}
255+
256+std::list<Department::SPtr> Department::from_json_node(const Json::Value& node)
257+{
258+ std::list<Department::SPtr> deps;
259+
260+ for (uint i = 0; i < node.size(); i++)
261+ {
262+ try
263+ {
264+ auto const item = node[i];
265+
266+ auto name = check_mandatory_attribute(item, Department::JsonKeys::name, Json::ValueType::stringValue).asString();
267+ const bool has_children = item.isMember(Department::JsonKeys::has_children) ? item[Department::JsonKeys::has_children].asBool() : false;
268+
269+ auto const links = check_mandatory_attribute(item, Department::JsonKeys::links, Json::ValueType::objectValue);
270+ auto const self = check_mandatory_attribute(links, Department::JsonKeys::self, Json::ValueType::objectValue);
271+ auto const href = check_mandatory_attribute(self, Department::JsonKeys::href, Json::ValueType::stringValue).asString();
272+
273+ auto dep = std::make_shared<Department>(name, name, href, has_children); //FIXME: id
274+ if (item.isObject() && item.isMember(Department::JsonKeys::embedded))
275+ {
276+ auto const emb = item[Department::JsonKeys::embedded];
277+ if (emb.isObject() && emb.isMember(Department::JsonKeys::department))
278+ {
279+ auto const ditem = emb[Department::JsonKeys::department];
280+ auto const subdeps = from_json_node(ditem);
281+ dep->set_subdepartments(subdeps);
282+ }
283+ }
284+ deps.push_back(dep);
285+ }
286+ catch (const std::runtime_error& e)
287+ {
288+ std::cerr << "Invalid department #" << i << std::endl;
289+ }
290+ }
291+
292+ return deps;
293+}
294+
295+std::list<Department::SPtr> Department::from_json_root_node(const Json::Value& root)
296+{
297+ if (root.isObject() && root.isMember(Department::JsonKeys::embedded))
298+ {
299+ auto const emb = root[Department::JsonKeys::embedded];
300+ if (emb.isObject() && emb.isMember(Department::JsonKeys::department))
301+ {
302+ auto const ditem = emb[Department::JsonKeys::department];
303+ return from_json_node(ditem);
304+ }
305+ }
306+
307+ return std::list<Department::SPtr>();
308+}
309+
310+std::list<Department::SPtr> Department::from_json(const std::string& json)
311+{
312+ Json::Reader reader;
313+ Json::Value root;
314+
315+ try
316+ {
317+ if (!reader.parse(json, root)) {
318+ throw std::runtime_error(reader.getFormattedErrorMessages());
319+ }
320+
321+ if (root.isObject() && root.isMember(Department::JsonKeys::embedded))
322+ {
323+ auto const emb = root[Department::JsonKeys::embedded];
324+ if (emb.isObject() && emb.isMember(Department::JsonKeys::department))
325+ {
326+ auto const ditem = emb[Department::JsonKeys::department];
327+ return from_json_node(ditem);
328+ }
329+ }
330+ }
331+ catch (const std::exception& e)
332+ {
333+ std::cerr << "Error parsing departments: " << e.what() << std::endl;
334+ }
335+ catch (...)
336+ {
337+ std::cerr << "Unknown error when parsing departments" << std::endl;
338+ }
339+
340+ return std::list<Department::SPtr>();
341+}
342+
343+}
344
345=== added file 'libclickscope/click/departments.h'
346--- libclickscope/click/departments.h 1970-01-01 00:00:00 +0000
347+++ libclickscope/click/departments.h 2014-06-23 15:00:46 +0000
348@@ -0,0 +1,83 @@
349+/*
350+ * Copyright (C) 2014 Canonical Ltd.
351+ *
352+ * This program is free software: you can redistribute it and/or modify it
353+ * under the terms of the GNU General Public License version 3, as published
354+ * by the Free Software Foundation.
355+ *
356+ * This program is distributed in the hope that it will be useful, but
357+ * WITHOUT ANY WARRANTY; without even the implied warranties of
358+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
359+ * PURPOSE. See the GNU General Public License for more details.
360+ *
361+ * You should have received a copy of the GNU General Public License along
362+ * with this program. If not, see <http://www.gnu.org/licenses/>.
363+ *
364+ * In addition, as a special exception, the copyright holders give
365+ * permission to link the code of portions of this program with the
366+ * OpenSSL library under certain conditions as described in each
367+ * individual source file, and distribute linked combinations
368+ * including the two.
369+ * You must obey the GNU General Public License in all respects
370+ * for all of the code used other than OpenSSL. If you modify
371+ * file(s) with this exception, you may extend this exception to your
372+ * version of the file(s), but you are not obligated to do so. If you
373+ * do not wish to do so, delete this exception statement from your
374+ * version. If you delete this exception statement from all source
375+ * files in the program, then also delete it here.
376+ */
377+
378+#ifndef CLICK_DEPARTMENTS_H
379+#define CLICK_DEPARTMENTS_H
380+
381+#include <string>
382+#include <list>
383+#include <memory>
384+#include <json/json.h>
385+
386+namespace click
387+{
388+
389+class Department
390+{
391+ public:
392+ typedef std::shared_ptr<Department> SPtr;
393+ typedef std::shared_ptr<Department const> SCPtr;
394+
395+ struct JsonKeys
396+ {
397+ JsonKeys() = delete;
398+ constexpr static const char* name {"name"};
399+ constexpr static const char* embedded {"_embedded"};
400+ constexpr static const char* department {"clickindex:department"};
401+ constexpr static const char* has_children {"has_children"};
402+ constexpr static const char* links {"_links"};
403+ constexpr static const char* self {"self"};
404+ constexpr static const char* href {"href"};
405+ };
406+
407+ Department(const std::string &id, const std::string &name, const std::string& href, bool has_children);
408+ std::string id() const;
409+ std::string name() const;
410+ std::string href() const;
411+ bool has_children_flag() const;
412+ void set_subdepartments(const std::list<Department::SPtr>& deps);
413+ std::list<Department::SPtr> sub_departments() const;
414+ static std::list<Department::SPtr> from_json(const std::string& json);
415+ static std::list<Department::SPtr> from_json_root_node(const Json::Value& val);
416+
417+ private:
418+ static std::list<Department::SPtr> from_json_node(const Json::Value& val);
419+ static Json::Value check_mandatory_attribute(const Json::Value& item, const std::string& name, Json::ValueType valtype);
420+ std::string id_;
421+ std::string name_;
422+ std::string href_;
423+ bool has_children_flag_;
424+ std::list<Department::SPtr> sub_departments_;
425+};
426+
427+typedef std::list<Department::SPtr> DepartmentList;
428+
429+}
430+
431+#endif
432
433=== added file 'libclickscope/click/highlights.cpp'
434--- libclickscope/click/highlights.cpp 1970-01-01 00:00:00 +0000
435+++ libclickscope/click/highlights.cpp 2014-06-23 15:00:46 +0000
436@@ -0,0 +1,95 @@
437+/*
438+ * Copyright (C) 2014 Canonical Ltd.
439+ *
440+ * This program is free software: you can redistribute it and/or modify it
441+ * under the terms of the GNU General Public License version 3, as published
442+ * by the Free Software Foundation.
443+ *
444+ * This program is distributed in the hope that it will be useful, but
445+ * WITHOUT ANY WARRANTY; without even the implied warranties of
446+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
447+ * PURPOSE. See the GNU General Public License for more details.
448+ *
449+ * You should have received a copy of the GNU General Public License along
450+ * with this program. If not, see <http://www.gnu.org/licenses/>.
451+ *
452+ * In addition, as a special exception, the copyright holders give
453+ * permission to link the code of portions of this program with the
454+ * OpenSSL library under certain conditions as described in each
455+ * individual source file, and distribute linked combinations
456+ * including the two.
457+ * You must obey the GNU General Public License in all respects
458+ * for all of the code used other than OpenSSL. If you modify
459+ * file(s) with this exception, you may extend this exception to your
460+ * version of the file(s), but you are not obligated to do so. If you
461+ * do not wish to do so, delete this exception statement from your
462+ * version. If you delete this exception statement from all source
463+ * files in the program, then also delete it here.
464+ */
465+
466+#include "highlights.h"
467+#include <iostream>
468+
469+namespace click
470+{
471+
472+Highlight::Highlight(const std::string& name)
473+ : name_(name)
474+{
475+}
476+
477+Highlight::Highlight(const std::string& name, const Packages& pkgs)
478+ : name_(name),
479+ packages_(pkgs)
480+{
481+}
482+
483+void Highlight::add_package(const Package& pkg)
484+{
485+ packages_.push_back(pkg);
486+}
487+
488+std::string Highlight::name() const
489+{
490+ return name_;
491+}
492+
493+Packages Highlight::packages() const
494+{
495+ return packages_;
496+}
497+
498+std::list<Highlight> Highlight::from_json_node(const Json::Value& node)
499+{
500+ std::list<Highlight> highlights;
501+
502+ for (uint i = 0; i < node.size(); i++)
503+ {
504+ auto const item = node[i];
505+ if (item.isObject() && item.isMember(Highlight::JsonKeys::name))
506+ {
507+ auto name = item[Highlight::JsonKeys::name].asString();
508+ auto pkgs = package_list_from_json_node(item);
509+ highlights.push_back(Highlight(name, pkgs));
510+ }
511+ }
512+
513+ return highlights;
514+}
515+
516+std::list<Highlight> Highlight::from_json_root_node(const Json::Value& root)
517+{
518+ if (root.isObject() && root.isMember(Highlight::JsonKeys::embedded))
519+ {
520+ auto const emb = root[Highlight::JsonKeys::embedded];
521+ if (emb.isObject() && emb.isMember(Highlight::JsonKeys::highlight))
522+ {
523+ auto const hl = emb[Highlight::JsonKeys::highlight];
524+ return from_json_node(hl);
525+ }
526+ }
527+
528+ return std::list<Highlight>();
529+}
530+
531+}
532
533=== added file 'libclickscope/click/highlights.h'
534--- libclickscope/click/highlights.h 1970-01-01 00:00:00 +0000
535+++ libclickscope/click/highlights.h 2014-06-23 15:00:46 +0000
536@@ -0,0 +1,72 @@
537+/*
538+ * Copyright (C) 2014 Canonical Ltd.
539+ *
540+ * This program is free software: you can redistribute it and/or modify it
541+ * under the terms of the GNU General Public License version 3, as published
542+ * by the Free Software Foundation.
543+ *
544+ * This program is distributed in the hope that it will be useful, but
545+ * WITHOUT ANY WARRANTY; without even the implied warranties of
546+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
547+ * PURPOSE. See the GNU General Public License for more details.
548+ *
549+ * You should have received a copy of the GNU General Public License along
550+ * with this program. If not, see <http://www.gnu.org/licenses/>.
551+ *
552+ * In addition, as a special exception, the copyright holders give
553+ * permission to link the code of portions of this program with the
554+ * OpenSSL library under certain conditions as described in each
555+ * individual source file, and distribute linked combinations
556+ * including the two.
557+ * You must obey the GNU General Public License in all respects
558+ * for all of the code used other than OpenSSL. If you modify
559+ * file(s) with this exception, you may extend this exception to your
560+ * version of the file(s), but you are not obligated to do so. If you
561+ * do not wish to do so, delete this exception statement from your
562+ * version. If you delete this exception statement from all source
563+ * files in the program, then also delete it here.
564+ */
565+
566+#ifndef CLICK_HIGHLIGHTS_H
567+#define CLICK_HIGHLIGHTS_H
568+
569+#include <string>
570+#include <list>
571+#include <json/json.h>
572+#include <click/package.h>
573+
574+namespace click
575+{
576+
577+class Highlight
578+{
579+public:
580+ struct JsonKeys
581+ {
582+ JsonKeys() = delete;
583+ constexpr static const char* name {"name"};
584+ constexpr static const char* embedded {"_embedded"};
585+ constexpr static const char* highlight {"clickindex:highlight"};
586+ };
587+
588+ Highlight(const std::string& name);
589+ Highlight(const std::string& name, const Packages& pkgs);
590+ void add_package(const Package& pkg);
591+
592+ std::string name() const;
593+ Packages packages() const;
594+
595+ static std::list<Highlight> from_json_root_node(const Json::Value& val);
596+
597+private:
598+ static std::list<Highlight> from_json_node(const Json::Value& val);
599+
600+ std::string name_;
601+ Packages packages_;
602+};
603+
604+typedef std::list<Highlight> HighlightList;
605+
606+}
607+
608+#endif
609
610=== modified file 'libclickscope/click/index.cpp'
611--- libclickscope/click/index.cpp 2014-06-12 21:05:25 +0000
612+++ libclickscope/click/index.cpp 2014-06-23 15:00:46 +0000
613@@ -88,11 +88,15 @@
614
615 }
616
617-std::string Index::build_index_query(const std::string& query)
618+std::string Index::build_index_query(const std::string& query, const std::string& department)
619 {
620 std::stringstream result;
621
622 result << query;
623+ if (!department.empty()) {
624+ result << ",department:" << department;
625+ }
626+
627 return result.str();
628 }
629
630@@ -113,7 +117,7 @@
631 click::web::Cancellable Index::search (const std::string& query, std::function<void(click::Packages)> callback)
632 {
633 click::web::CallParams params;
634- const std::string built_query(build_index_query(query));
635+ const std::string built_query(build_index_query(query, ""));
636 params.add(click::QUERY_ARGNAME, built_query.c_str());
637 QSharedPointer<click::web::Response> response(client->call(
638 get_base_url() + click::SEARCH_PATH, "GET", false, build_headers(), "", params));
639@@ -139,6 +143,40 @@
640 return click::web::Cancellable(response);
641 }
642
643+click::web::Cancellable Index::bootstrap(std::function<void(const click::DepartmentList&, const click::HighlightList&, Error, int)> callback)
644+{
645+ return departments(get_base_url() + click::BOOTSTRAP_PATH, callback);
646+}
647+
648+click::web::Cancellable Index::departments(const std::string& department_href, std::function<void(const DepartmentList&, const HighlightList&, Error, int)> callback)
649+{
650+ click::web::CallParams params;
651+ QSharedPointer<click::web::Response> response(client->call(
652+ department_href, "GET", false, build_headers(), "", params));
653+
654+ QObject::connect(response.data(), &click::web::Response::finished, [=](QString reply) {
655+ qDebug() << "departments request finished";
656+ Json::Reader reader;
657+ Json::Value root;
658+
659+ click::DepartmentList depts;
660+ click::HighlightList highlights;
661+ if (reader.parse(reply.toUtf8().constData(), root)) {
662+ depts = Department::from_json_root_node(root);
663+ highlights = Highlight::from_json_root_node(root);
664+ }
665+ callback(depts, highlights, click::Index::Error::NoError, 0);
666+ });
667+ QObject::connect(response.data(), &click::web::Response::error, [=](QString /*description*/, int error_code) {
668+ qWarning() << "departments call failed due to network error";
669+ const click::DepartmentList depts;
670+ const click::HighlightList highlights;
671+ qDebug() << "departments: calling callback";
672+ callback(depts, highlights, click::Index::Error::NetworkError, error_code);
673+ });
674+ return click::web::Cancellable(response);
675+}
676+
677 click::web::Cancellable Index::get_details (const std::string& package_name, std::function<void(PackageDetails, click::Index::Error)> callback)
678 {
679 QSharedPointer<click::web::Response> response = client->call
680
681=== modified file 'libclickscope/click/index.h'
682--- libclickscope/click/index.h 2014-06-12 21:05:25 +0000
683+++ libclickscope/click/index.h 2014-06-23 15:00:46 +0000
684@@ -38,6 +38,8 @@
685 #include <click/webclient.h>
686
687 #include "package.h"
688+#include <click/departments.h>
689+#include <click/highlights.h>
690
691
692 namespace click {
693@@ -47,6 +49,7 @@
694 const std::string SEARCH_BASE_URL_ENVVAR = "U1_SEARCH_BASE_URL";
695 const std::string SEARCH_BASE_URL = "https://search.apps.ubuntu.com/";
696 const std::string SEARCH_PATH = "api/v1/search";
697+const std::string BOOTSTRAP_PATH = "api/v1";
698 const std::string SUPPORTED_FRAMEWORKS = "framework:ubuntu-sdk-13.10";
699 const std::string QUERY_ARGNAME = "q";
700 const std::string ARCHITECTURE = "architecture:";
701@@ -64,7 +67,7 @@
702 protected:
703 QSharedPointer<web::Client> client;
704 QSharedPointer<Configuration> configuration;
705- virtual std::string build_index_query(const std::string& query);
706+ virtual std::string build_index_query(const std::string& query, const std::string& department);
707 virtual std::map<std::string, std::string> build_headers();
708
709 public:
710@@ -73,6 +76,8 @@
711 const QSharedPointer<Configuration> configuration=QSharedPointer<Configuration>(new Configuration()));
712 virtual click::web::Cancellable search (const std::string& query, std::function<void(Packages)> callback);
713 virtual click::web::Cancellable get_details(const std::string& package_name, std::function<void(PackageDetails, Error)> callback);
714+ virtual click::web::Cancellable bootstrap(std::function<void(const DepartmentList&, const HighlightList&, Error, int)> callback);
715+ virtual click::web::Cancellable departments(const std::string& department_href, std::function<void(const DepartmentList&, const HighlightList&, Error, int)> callback);
716 virtual ~Index();
717
718 static std::string get_base_url ();
719
720=== modified file 'libclickscope/click/package.h'
721--- libclickscope/click/package.h 2014-06-13 22:42:02 +0000
722+++ libclickscope/click/package.h 2014-06-23 15:00:46 +0000
723@@ -34,6 +34,7 @@
724 #include <string>
725 #include <unordered_set>
726 #include <vector>
727+#include <functional>
728
729 #include <json/json.h>
730
731
732=== modified file 'libclickscope/click/webclient.cpp'
733--- libclickscope/click/webclient.cpp 2014-05-23 18:45:01 +0000
734+++ libclickscope/click/webclient.cpp 2014-06-23 15:00:46 +0000
735@@ -176,7 +176,8 @@
736 {
737 auto message = reply->errorString() + QString(" (%1)").arg(network_error);
738 qWarning() << "Network error:" << message << "\n" << reply->readAll();
739- emit error(message);
740+ int error_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
741+ emit error(message, error_code);
742 }
743
744 void click::web::Response::abort()
745
746=== modified file 'libclickscope/click/webclient.h'
747--- libclickscope/click/webclient.h 2014-05-23 18:45:01 +0000
748+++ libclickscope/click/webclient.h 2014-06-23 15:00:46 +0000
749@@ -84,7 +84,7 @@
750
751 signals:
752 void finished(QByteArray result);
753- void error(QString description);
754+ void error(QString description, int error_code);
755
756 private:
757 QSharedPointer<click::network::Reply> reply;
758
759=== modified file 'libclickscope/tests/CMakeLists.txt'
760--- libclickscope/tests/CMakeLists.txt 2014-05-26 14:27:31 +0000
761+++ libclickscope/tests/CMakeLists.txt 2014-06-23 15:00:46 +0000
762@@ -28,6 +28,8 @@
763 test_reviews.cpp
764 test_smartconnect.cpp
765 test_webclient.cpp
766+ test_bootstrap.cpp
767+ test_departments.cpp
768
769 ${CMAKE_CURRENT_BINARY_DIR}/test_data.cpp
770 )
771
772=== modified file 'libclickscope/tests/fake_json.h'
773--- libclickscope/tests/fake_json.h 2014-06-13 23:18:41 +0000
774+++ libclickscope/tests/fake_json.h 2014-06-23 15:00:46 +0000
775@@ -150,6 +150,333 @@
776 }
777 )foo";
778
779+const std::string FAKE_JSON_BOOTSTRAP = R"(
780+ {
781+ "_embedded": {
782+ "clickindex:department": [
783+ {
784+ "has_children": false,
785+ "_links": {
786+ "self": {
787+ "href": "https://search.apps.staging.ubuntu.com/api/v1/departments/fake-subdepartment"}
788+ },
789+ "name": "Fake Subdepartment", "slug": "fake-subdepartment"}
790+ ],
791+ "clickindex:highlight": [
792+ {
793+ "_embedded": {
794+ "clickindex:package": [
795+ {
796+ "publisher": "Awesome Widget Company",
797+ "name": "org.example.awesomelauncher",
798+ "title": "Awesome Launcher",
799+ "price": 1.99,
800+ "_links": {
801+ "self": {
802+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"}
803+ },
804+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
805+ },
806+ {
807+ "publisher": "Awesome Widget Company",
808+ "name": "org.example.awesomewidget",
809+ "title": "Awesome Widget", "price": 1.99,
810+ "_links": {
811+ "self": {
812+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomewidget"
813+ }
814+ },
815+ "icon": "http://example.org/media/org.example.awesomewidget/icons/icon16.png"}
816+ ]
817+ },
818+ "_links": {
819+ "self": {
820+ "href": "https://search.apps.staging.ubuntu.com/api/v1/highlights/top-apps"
821+ }
822+ },
823+ "name": "Top Apps", "slug": "top-apps"
824+ },
825+ {
826+ "_embedded": {
827+ "clickindex:package": [
828+ {
829+ "publisher": "Awesome Widget Company",
830+ "name": "org.example.awesomelauncher",
831+ "title": "Awesome Launcher",
832+ "price": 1.99,
833+ "_links": {
834+ "self": {
835+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"
836+ }
837+ },
838+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
839+ },
840+ {
841+ "publisher": "Awesome Widget Company",
842+ "name": "org.example.awesomewidget",
843+ "title": "Awesome Widget",
844+ "price": 1.99,
845+ "_links": {
846+ "self": {
847+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomewidget"
848+ }
849+ },
850+ "icon": "http://example.org/media/org.example.awesomewidget/icons/icon16.png"
851+ }
852+ ]
853+ },
854+ "_links": {
855+ "self": {
856+ "href": "https://search.apps.staging.ubuntu.com/api/v1/highlights/most-purchased"
857+ }
858+ },
859+ "name": "Most Purchased",
860+ "slug": "most-purchased"
861+ },
862+ {
863+ "_embedded": {
864+ "clickindex:package": [
865+ {
866+ "publisher": "Awesome Widget Company",
867+ "name": "org.example.awesomelauncher",
868+ "title": "Awesome Launcher",
869+ "price": 1.99,
870+ "_links": {
871+ "self": {
872+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"
873+ }
874+ },
875+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
876+ },
877+ {
878+ "publisher": "Awesome Widget Company",
879+ "name": "org.example.awesomewidget",
880+ "title": "Awesome Widget",
881+ "price": 1.99,
882+ "_links": {
883+ "self": {
884+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomewidget"
885+ }
886+ },
887+ "icon": "http://example.org/media/org.example.awesomewidget/icons/icon16.png"
888+ }
889+ ]
890+ },
891+ "_links": {
892+ "self": {
893+ "href": "https://search.apps.staging.ubuntu.com/api/v1/highlights/new-releases"
894+ }
895+ },
896+ "name": "New Releases",
897+ "slug": "new-releases"
898+ }
899+ ]
900+ }, "has_children": true,
901+ "_links": {
902+ "curies": [
903+ {
904+ "href": "https://search.apps.staging.ubuntu.com/docs/v1/relations.html{#rel}",
905+ "name": "clickindex", "templated": true
906+ }
907+ ],
908+ "self": {
909+ "href": "https://search.apps.staging.ubuntu.com/api/v1/departments/fake-department-with-subdepartments"
910+ },
911+ "collection": {
912+ "href": "https://search.apps.staging.ubuntu.com/api/v1/departments"
913+ }
914+ },
915+ "name": "Fake Department With Subdepartments",
916+ "slug": "fake-department-with-subdepartments"
917+ })";
918+
919+const std::string FAKE_JSON_BROKEN_BOOTSTRAP = R"(
920+ {
921+ "_embedded": {
922+ "clickindex:department": [
923+ {
924+ "name": "Broken department"
925+ }
926+ ],
927+ "clickindex:highlight": [
928+ {
929+ "_embedded": {
930+ "clickindex:package": [
931+ {
932+ "publisher": "Awesome Widget Company",
933+ "name": "org.example.awesomelauncher",
934+ "title": "Awesome Launcher",
935+ "price": 1.99,
936+ "_links": {
937+ "self": {
938+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"}
939+ },
940+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
941+ }
942+ ]
943+ },
944+ "_links": {
945+ "self": {
946+ "href": "https://search.apps.staging.ubuntu.com/api/v1/highlights/top-apps"
947+ }
948+ },
949+ "name": "Top Apps",
950+ "slug": "top-apps"
951+ },
952+ {
953+ "_embedded": {
954+ "clickindex:package": [
955+ {
956+ "publisher": "Awesome Widget Company",
957+ "name": "org.example.awesomelauncher",
958+ "title": "Awesome Launcher",
959+ "price": 1.99,
960+ "_links": {
961+ "self": {
962+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"
963+ }
964+ },
965+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
966+ }
967+ ]
968+ },
969+ "____name": "Broken highlight"
970+ }
971+ ]
972+ }
973+ })";
974+
975+const std::string FAKE_JSON_DEPARTMENTS_ONLY = R"(
976+ {
977+ "_links": {
978+ "self": {
979+ "href": "https://search.apps.ubuntu.com/api/v1/departments"
980+ },
981+ "curies": [
982+ {
983+ "name": "clickindex",
984+ "href": "https://search.apps.ubuntu.com/docs/v1/relations.html{#rel}",
985+ "templated": true
986+ }
987+ ]
988+ },
989+ "_embedded": {
990+ "clickindex:department": [
991+ {
992+ "name": "Games",
993+ "_links": {
994+ "self": {
995+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Games"
996+ }
997+ },
998+ "_embedded": {
999+ "clickindex:department": [
1000+ {
1001+ "name": "Board Games",
1002+ "_links": {
1003+ "self": {
1004+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Games/Board+Games"
1005+ }
1006+ }
1007+ }
1008+ ]
1009+ }
1010+ },
1011+ {
1012+ "name": "Graphics",
1013+ "_links": {
1014+ "self": {
1015+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Graphics"
1016+ }
1017+ },
1018+ "_embedded": {
1019+ "clickindex:department": [
1020+ {
1021+ "name": "Drawing",
1022+ "_links": {
1023+ "self": {
1024+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Graphics/Drawing"
1025+ }
1026+ }
1027+ }
1028+ ]
1029+ }
1030+ },
1031+ {
1032+ "name": "Internet",
1033+ "_links": {
1034+ "self": {
1035+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Internet"
1036+ }
1037+ },
1038+ "_embedded": {
1039+ "clickindex:department": [
1040+ {
1041+ "name": "Chat",
1042+ "_links": {
1043+ "self": {
1044+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Internet/Chat"
1045+ }
1046+ }
1047+ },
1048+ {
1049+ "name": "Mail",
1050+ "_links": {
1051+ "self": {
1052+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Internet/Mail"
1053+ }
1054+ }
1055+ },
1056+ {
1057+ "name": "Web Browsers",
1058+ "_links": {
1059+ "self": {
1060+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Internet/Web+Browsers"
1061+ }
1062+ }
1063+ }
1064+ ]
1065+ }
1066+ }
1067+ ]
1068+ }
1069+})";
1070+
1071+const std::string FAKE_JSON_BROKEN_DEPARTMENTS = R"(
1072+ {
1073+ "_links": {
1074+ "self": {
1075+ "href": "https://search.apps.ubuntu.com/api/v1/departments"
1076+ },
1077+ "curies": [
1078+ {
1079+ "name": "clickindex",
1080+ "href": "https://search.apps.ubuntu.com/docs/v1/relations.html{#rel}",
1081+ "templated": true
1082+ }
1083+ ]
1084+ },
1085+ "_embedded": {
1086+ "clickindex:department": [
1087+ {
1088+ "name": "Games",
1089+ "_links": {
1090+ "self": {
1091+ "href": "https://search.apps.ubuntu.com/api/v1/departments/Games"
1092+ }
1093+ },
1094+ "_embedded": {
1095+ "clickindex:department": [
1096+ {
1097+ "name": "Broken department"
1098+ }
1099+ ]
1100+ }
1101+ }
1102+ ]
1103+ }
1104+ })";
1105+
1106 const std::string FAKE_JSON_MANIFEST_REMOVABLE = R"foo(
1107 {
1108 "_removable": 1,
1109
1110=== modified file 'libclickscope/tests/mock_network_access_manager.h'
1111--- libclickscope/tests/mock_network_access_manager.h 2014-05-20 19:33:41 +0000
1112+++ libclickscope/tests/mock_network_access_manager.h 2014-06-23 15:00:46 +0000
1113@@ -48,6 +48,7 @@
1114 {
1115 // Set a default value for QByteArray-returning mocked methods.
1116 ::testing::DefaultValue<QByteArray>::Set(QByteArray(""));
1117+ ON_CALL(*this, attribute(::testing::_)).WillByDefault(::testing::Return(0));
1118 }
1119
1120 MOCK_METHOD0(abort, void());
1121
1122=== modified file 'libclickscope/tests/mock_webclient.h'
1123--- libclickscope/tests/mock_webclient.h 2014-05-23 18:45:01 +0000
1124+++ libclickscope/tests/mock_webclient.h 2014-06-23 15:00:46 +0000
1125@@ -86,7 +86,7 @@
1126 const click::web::CallParams& params));
1127 QSharedPointer<click::web::Response> call(
1128 const std::string& iri,
1129- const click::web::CallParams& params=click::web::CallParams()) {
1130+ const click::web::CallParams& params=click::web::CallParams()) override {
1131 return callImpl(iri, "GET", false,
1132 std::map<std::string, std::string>(), "", params);
1133 }
1134@@ -96,7 +96,7 @@
1135 bool sign = false,
1136 const std::map<std::string, std::string>& headers = std::map<std::string, std::string>(),
1137 const std::string& data = "",
1138- const click::web::CallParams& params=click::web::CallParams()) {
1139+ const click::web::CallParams& params=click::web::CallParams()) override {
1140 return callImpl(iri, method, sign, headers, data, params);
1141 }
1142 };
1143
1144=== added file 'libclickscope/tests/test_bootstrap.cpp'
1145--- libclickscope/tests/test_bootstrap.cpp 1970-01-01 00:00:00 +0000
1146+++ libclickscope/tests/test_bootstrap.cpp 2014-06-23 15:00:46 +0000
1147@@ -0,0 +1,169 @@
1148+/*
1149+ * Copyright (C) 2014 Canonical Ltd.
1150+ *
1151+ * This program is free software: you can redistribute it and/or modify it
1152+ * under the terms of the GNU General Public License version 3, as published
1153+ * by the Free Software Foundation.
1154+ *
1155+ * This program is distributed in the hope that it will be useful, but
1156+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1157+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1158+ * PURPOSE. See the GNU General Public License for more details.
1159+ *
1160+ * You should have received a copy of the GNU General Public License along
1161+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1162+ *
1163+ * In addition, as a special exception, the copyright holders give
1164+ * permission to link the code of portions of this program with the
1165+ * OpenSSL library under certain conditions as described in each
1166+ * individual source file, and distribute linked combinations
1167+ * including the two.
1168+ * You must obey the GNU General Public License in all respects
1169+ * for all of the code used other than OpenSSL. If you modify
1170+ * file(s) with this exception, you may extend this exception to your
1171+ * version of the file(s), but you are not obligated to do so. If you
1172+ * do not wish to do so, delete this exception statement from your
1173+ * version. If you delete this exception statement from all source
1174+ * files in the program, then also delete it here.
1175+ */
1176+#include <click/configuration.h>
1177+#include <click/reviews.h>
1178+#include <click/webclient.h>
1179+#include <click/index.h>
1180+
1181+#include <tests/mock_network_access_manager.h>
1182+#include <tests/mock_ubuntuone_credentials.h>
1183+#include <tests/mock_webclient.h>
1184+
1185+#include <gtest/gtest.h>
1186+#include "fake_json.h"
1187+#include <json/reader.h>
1188+#include <json/value.h>
1189+#include <click/highlights.h>
1190+#include <click/configuration.h>
1191+#include <click/departments.h>
1192+
1193+#include <click/webclient.h>
1194+
1195+using namespace ::testing;
1196+
1197+namespace
1198+{
1199+
1200+class BootstrapTest: public ::testing::Test {
1201+protected:
1202+ QSharedPointer<MockClient> clientPtr;
1203+ QSharedPointer<MockNetworkAccessManager> namPtr;
1204+ std::shared_ptr<click::Index> indexPtr;
1205+
1206+ virtual void SetUp() {
1207+ namPtr.reset(new MockNetworkAccessManager());
1208+ clientPtr.reset(new NiceMock<MockClient>(namPtr));
1209+ indexPtr.reset(new click::Index(clientPtr));
1210+ }
1211+
1212+public:
1213+ MOCK_METHOD4(bootstrap_callback, void(const click::DepartmentList&, const click::HighlightList&, click::Index::Error, int));
1214+};
1215+}
1216+
1217+TEST_F(BootstrapTest, testBootstrapCallsWebservice)
1218+{
1219+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
1220+ auto response = responseForReply(reply.asSharedPtr());
1221+
1222+ EXPECT_CALL(*clientPtr, callImpl(EndsWith(click::BOOTSTRAP_PATH), "GET", _, _, _, _))
1223+ .Times(1)
1224+ .WillOnce(Return(response));
1225+ indexPtr->bootstrap([](const click::DepartmentList&, const click::HighlightList&, click::Index::Error, int) {});
1226+}
1227+
1228+TEST_F(BootstrapTest, testBootstrapJsonIsParsed)
1229+{
1230+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
1231+ auto response = responseForReply(reply.asSharedPtr());
1232+
1233+ QByteArray fake_json(FAKE_JSON_BOOTSTRAP.c_str());
1234+ EXPECT_CALL(reply.instance, readAll())
1235+ .Times(1)
1236+ .WillOnce(Return(fake_json));
1237+
1238+ EXPECT_CALL(*clientPtr, callImpl(EndsWith(click::BOOTSTRAP_PATH), "GET", _, _, _, _))
1239+ .Times(1)
1240+ .WillOnce(Return(response));
1241+
1242+ EXPECT_CALL(*this, bootstrap_callback(_, _, _, _)).Times(1);
1243+ indexPtr->bootstrap([this](const click::DepartmentList& depts, const click::HighlightList& highlights, click::Index::Error error, int error_code) {
1244+ bootstrap_callback(depts, highlights, error, error_code);
1245+ {
1246+ EXPECT_EQ(3u, highlights.size());
1247+ auto it = highlights.begin();
1248+ EXPECT_EQ("Top Apps", it->name());
1249+ EXPECT_EQ(2u, it->packages().size());
1250+ ++it;
1251+ EXPECT_EQ("Most Purchased", it->name());
1252+ EXPECT_EQ(2u, it->packages().size());
1253+ ++it;
1254+ EXPECT_EQ("New Releases", it->name());
1255+ EXPECT_EQ(2u, it->packages().size());
1256+ }
1257+ {
1258+ EXPECT_EQ(1u, depts.size());
1259+ auto it = depts.begin();
1260+ EXPECT_EQ("Fake Subdepartment", (*it)->name());
1261+ EXPECT_FALSE((*it)->has_children_flag());
1262+ EXPECT_EQ("https://search.apps.staging.ubuntu.com/api/v1/departments/fake-subdepartment", (*it)->href());
1263+ }
1264+ });
1265+ response->replyFinished();
1266+}
1267+
1268+TEST_F(BootstrapTest, testParsing)
1269+{
1270+ Json::Reader reader;
1271+ Json::Value root;
1272+
1273+ EXPECT_TRUE(reader.parse(FAKE_JSON_BOOTSTRAP, root));
1274+
1275+ {
1276+ auto highlights = click::Highlight::from_json_root_node(root);
1277+ EXPECT_EQ(3u, highlights.size());
1278+ auto it = highlights.begin();
1279+ EXPECT_EQ("Top Apps", it->name());
1280+ EXPECT_EQ(2u, it->packages().size());
1281+ ++it;
1282+ EXPECT_EQ("Most Purchased", it->name());
1283+ EXPECT_EQ(2u, it->packages().size());
1284+ ++it;
1285+ EXPECT_EQ("New Releases", it->name());
1286+ EXPECT_EQ(2u, it->packages().size());
1287+ }
1288+ {
1289+ auto depts = click::Department::from_json_root_node(root);
1290+ EXPECT_EQ(1u, depts.size());
1291+ auto it = depts.begin();
1292+ EXPECT_EQ("Fake Subdepartment", (*it)->name());
1293+ EXPECT_FALSE((*it)->has_children_flag());
1294+ EXPECT_EQ("https://search.apps.staging.ubuntu.com/api/v1/departments/fake-subdepartment", (*it)->href());
1295+ }
1296+}
1297+
1298+TEST_F(BootstrapTest, testParsingErrors)
1299+{
1300+ Json::Reader reader;
1301+ Json::Value root;
1302+
1303+ EXPECT_TRUE(reader.parse(FAKE_JSON_BROKEN_BOOTSTRAP, root));
1304+
1305+ {
1306+ auto highlights = click::Highlight::from_json_root_node(root);
1307+ EXPECT_EQ(1u, highlights.size());
1308+ auto it = highlights.begin();
1309+ EXPECT_EQ("Top Apps", it->name());
1310+ EXPECT_EQ(1u, it->packages().size());
1311+ }
1312+ {
1313+ auto depts = click::Department::from_json_root_node(root);
1314+ EXPECT_EQ(0u, depts.size());
1315+ }
1316+}
1317
1318=== added file 'libclickscope/tests/test_departments.cpp'
1319--- libclickscope/tests/test_departments.cpp 1970-01-01 00:00:00 +0000
1320+++ libclickscope/tests/test_departments.cpp 2014-06-23 15:00:46 +0000
1321@@ -0,0 +1,143 @@
1322+/*
1323+ * Copyright (C) 2014 Canonical Ltd.
1324+ *
1325+ * This program is free software: you can redistribute it and/or modify it
1326+ * under the terms of the GNU General Public License version 3, as published
1327+ * by the Free Software Foundation.
1328+ *
1329+ * This program is distributed in the hope that it will be useful, but
1330+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1331+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1332+ * PURPOSE. See the GNU General Public License for more details.
1333+ *
1334+ * You should have received a copy of the GNU General Public License along
1335+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1336+ *
1337+ * In addition, as a special exception, the copyright holders give
1338+ * permission to link the code of portions of this program with the
1339+ * OpenSSL library under certain conditions as described in each
1340+ * individual source file, and distribute linked combinations
1341+ * including the two.
1342+ * You must obey the GNU General Public License in all respects
1343+ * for all of the code used other than OpenSSL. If you modify
1344+ * file(s) with this exception, you may extend this exception to your
1345+ * version of the file(s), but you are not obligated to do so. If you
1346+ * do not wish to do so, delete this exception statement from your
1347+ * version. If you delete this exception statement from all source
1348+ * files in the program, then also delete it here.
1349+ */
1350+
1351+#include <gtest/gtest.h>
1352+#include "fake_json.h"
1353+#include <click/departments.h>
1354+#include <click/department-lookup.h>
1355+
1356+TEST(DepartmentsTest, testParsing)
1357+{
1358+ const std::string jsonstr(FAKE_JSON_DEPARTMENTS_ONLY);
1359+ auto depts = click::Department::from_json(jsonstr);
1360+ EXPECT_EQ(3u, depts.size());
1361+ auto it = depts.cbegin();
1362+ {
1363+ auto dep = *it;
1364+ EXPECT_EQ("Games", dep->id());
1365+ EXPECT_EQ("Games", dep->name());
1366+ EXPECT_EQ("https://search.apps.ubuntu.com/api/v1/departments/Games", dep->href());
1367+ EXPECT_FALSE(dep->has_children_flag());
1368+ auto subdepts = dep->sub_departments();
1369+ EXPECT_EQ(1u, subdepts.size());
1370+ auto sit = subdepts.cbegin();
1371+ EXPECT_EQ("Board Games", (*sit)->name());
1372+ }
1373+ {
1374+ ++it;
1375+ auto dep = *it;
1376+ EXPECT_EQ("Graphics", dep->id());
1377+ EXPECT_EQ("Graphics", dep->name());
1378+ EXPECT_EQ("https://search.apps.ubuntu.com/api/v1/departments/Graphics", dep->href());
1379+ EXPECT_FALSE(dep->has_children_flag());
1380+ auto subdepts = dep->sub_departments();
1381+ EXPECT_EQ(1u, subdepts.size());
1382+ auto sit = subdepts.cbegin();
1383+ EXPECT_EQ("Drawing", (*sit)->name());
1384+ }
1385+ {
1386+ ++it;
1387+ auto dep = *it;
1388+ EXPECT_EQ("Internet", dep->id());
1389+ EXPECT_EQ("Internet", dep->name());
1390+ EXPECT_EQ("https://search.apps.ubuntu.com/api/v1/departments/Internet", dep->href());
1391+ EXPECT_FALSE(dep->has_children_flag());
1392+ auto subdepts = dep->sub_departments();
1393+ EXPECT_EQ(3u, subdepts.size());
1394+ auto sit = subdepts.cbegin();
1395+ auto subdep = *sit;
1396+ EXPECT_EQ("Chat", subdep->name());
1397+ EXPECT_EQ("https://search.apps.ubuntu.com/api/v1/departments/Internet/Chat", subdep->href());
1398+ subdep = *(++sit);
1399+ EXPECT_EQ("Mail", subdep->name());
1400+ EXPECT_EQ("https://search.apps.ubuntu.com/api/v1/departments/Internet/Mail", subdep->href());
1401+ subdep = *(++sit);
1402+ EXPECT_EQ("Web Browsers", subdep->name());
1403+ EXPECT_EQ("https://search.apps.ubuntu.com/api/v1/departments/Internet/Web+Browsers", subdep->href());
1404+ }
1405+}
1406+
1407+TEST(DepartmentsTest, testParsingErrors)
1408+{
1409+ // invalid json
1410+ {
1411+ const std::string jsonstr("{{{");
1412+ auto depts = click::Department::from_json(jsonstr);
1413+ EXPECT_EQ(0, depts.size());
1414+ }
1415+ // one of the departments is invalid
1416+ {
1417+ const std::string jsonstr(FAKE_JSON_BROKEN_DEPARTMENTS);
1418+ auto depts = click::Department::from_json(jsonstr);
1419+ EXPECT_EQ(1, depts.size());
1420+ }
1421+}
1422+
1423+TEST(DepartmentsTest, testLookup)
1424+{
1425+ auto dep_games = std::make_shared<click::Department>("games", "Games", "http://foobar.com/", false);
1426+ auto dep_rpg = std::make_shared<click::Department>("rpg", "RPG", "http://ubuntu.com/", false);
1427+ auto dep_strategy = std::make_shared<click::Department>("strategy", "Strategy", "", false);
1428+ const std::list<click::Department::SPtr> departments {dep_rpg, dep_strategy};
1429+ dep_games->set_subdepartments(departments);
1430+
1431+ const std::list<click::Department::SPtr> root {dep_games};
1432+ click::DepartmentLookup lut;
1433+ lut.rebuild(root);
1434+
1435+ {
1436+ EXPECT_EQ(2u, lut.size());
1437+ EXPECT_EQ("games", lut.get_parent("strategy")->id());
1438+ EXPECT_EQ("games", lut.get_parent("rpg")->id());
1439+ EXPECT_EQ(nullptr, lut.get_parent("games"));
1440+ }
1441+ {
1442+ auto info = lut.get_department_info("games");
1443+ EXPECT_EQ("games", info->id());
1444+ EXPECT_EQ("Games", info->name());
1445+ EXPECT_EQ("http://foobar.com/", info->href());
1446+ EXPECT_EQ(false, info->has_children_flag());
1447+
1448+ auto sub = info->sub_departments();
1449+ EXPECT_EQ(2u, sub.size());
1450+
1451+ auto it = sub.begin();
1452+ EXPECT_EQ("rpg", (*it)->id());
1453+ EXPECT_EQ("RPG", (*it)->name());
1454+ EXPECT_EQ("http://ubuntu.com/", (*it)->href());
1455+ ++it;
1456+ EXPECT_EQ("strategy", (*it)->id());
1457+ EXPECT_EQ("Strategy", (*it)->name());
1458+ }
1459+ {
1460+ lut.rebuild(root);
1461+ EXPECT_EQ(2u, lut.size());
1462+ }
1463+}
1464+
1465
1466=== modified file 'libclickscope/tests/test_index.cpp'
1467--- libclickscope/tests/test_index.cpp 2014-06-12 21:05:25 +0000
1468+++ libclickscope/tests/test_index.cpp 2014-06-23 15:00:46 +0000
1469@@ -52,8 +52,8 @@
1470 click::Index(client, configuration)
1471 {
1472 }
1473- MOCK_METHOD1(build_index_query, std::string(const std::string&));
1474 MOCK_METHOD0(build_headers, std::map<std::string, std::string>());
1475+ MOCK_METHOD2(build_index_query, std::string(const std::string&, const std::string&));
1476 };
1477
1478 class MockConfiguration : public click::Configuration {
1479@@ -116,7 +116,7 @@
1480 .Times(1)
1481 .WillOnce(Return(response));
1482
1483- EXPECT_CALL(*indexPtr, build_index_query(FAKE_QUERY))
1484+ EXPECT_CALL(*indexPtr, build_index_query(FAKE_QUERY, ""))
1485 .Times(1)
1486 .WillOnce(Return(FAKE_BUILT_QUERY));
1487
1488
1489=== modified file 'scope/clickstore/store-query.cpp'
1490--- scope/clickstore/store-query.cpp 2014-06-18 16:23:50 +0000
1491+++ scope/clickstore/store-query.cpp 2014-06-23 15:00:46 +0000
1492@@ -30,6 +30,7 @@
1493 #include <click/application.h>
1494 #include <click/interface.h>
1495 #include "store-query.h"
1496+#include "store-scope.h"
1497 #include <click/qtbridge.h>
1498
1499 #include <click/key_file_locator.h>
1500@@ -37,6 +38,7 @@
1501 #include <unity/scopes/Annotation.h>
1502 #include <unity/scopes/CategoryRenderer.h>
1503 #include <unity/scopes/CategorisedResult.h>
1504+#include <unity/scopes/Department.h>
1505 #include <unity/scopes/CannedQuery.h>
1506 #include <unity/scopes/SearchReply.h>
1507 #include <unity/scopes/SearchMetadata.h>
1508@@ -93,19 +95,26 @@
1509
1510 struct click::Query::Private
1511 {
1512- Private(click::Index& index, const scopes::SearchMetadata& metadata)
1513+ Private(click::Index& index, click::DepartmentLookup& depts,
1514+ click::HighlightList& highlights, const scopes::SearchMetadata& metadata)
1515 : index(index),
1516+ department_lookup(depts),
1517+ highlights(highlights),
1518 meta(metadata)
1519 {
1520 }
1521 click::Index& index;
1522+ click::DepartmentLookup& department_lookup;
1523+ click::HighlightList& highlights;
1524 scopes::SearchMetadata meta;
1525 click::web::Cancellable search_operation;
1526 };
1527
1528-click::Query::Query(unity::scopes::CannedQuery const& query, click::Index& index, scopes::SearchMetadata const& metadata)
1529+click::Query::Query(unity::scopes::CannedQuery const& query, click::Index& index, click::DepartmentLookup& depts,
1530+ click::HighlightList& highlights,
1531+ scopes::SearchMetadata const& metadata)
1532 : unity::scopes::SearchQueryBase(query, metadata),
1533- impl(new Private(index, metadata))
1534+ impl(new Private(index, depts, highlights, metadata))
1535 {
1536 }
1537
1538@@ -154,6 +163,166 @@
1539 });
1540 }
1541
1542+//
1543+// creates department menu with narrowed-down list of subdepartments of current department, as
1544+// returned by server call
1545+void click::Query::populate_departments(const click::DepartmentList& subdepts, const std::string& current_dep_id, unity::scopes::Department::SPtr &root)
1546+{
1547+ unity::scopes::DepartmentList departments;
1548+
1549+ // create a list of subdepartments of current department
1550+ foreach (auto d, subdepts)
1551+ {
1552+ unity::scopes::Department::SPtr department = unity::scopes::Department::create(d->id(), query(), d->name());
1553+ if (d->has_children_flag())
1554+ {
1555+ department->set_has_subdepartments();
1556+ }
1557+ departments.push_back(department);
1558+ }
1559+
1560+ if (current_dep_id != "")
1561+ {
1562+ auto curr_dpt = impl->department_lookup.get_department_info(current_dep_id);
1563+ if (curr_dpt != nullptr)
1564+ {
1565+ unity::scopes::Department::SPtr current = unity::scopes::Department::create(current_dep_id, query(), curr_dpt->name());
1566+ if (departments.size() > 0) // this may be a leaf department
1567+ {
1568+ current->set_subdepartments(departments);
1569+ }
1570+
1571+ auto parent_info = impl->department_lookup.get_parent(current_dep_id);
1572+ if (parent_info != nullptr)
1573+ {
1574+ root = unity::scopes::Department::create(parent_info->id(), query(), parent_info->name());
1575+ root->set_subdepartments({current});
1576+ return;
1577+ }
1578+ else
1579+ {
1580+ root = unity::scopes::Department::create("", query(), _("All departments"));
1581+ root->set_subdepartments({current});
1582+ return;
1583+ }
1584+ }
1585+ else
1586+ {
1587+ qWarning() << "Unknown department:" << QString::fromStdString(current_dep_id);
1588+ }
1589+ }
1590+
1591+ root = unity::scopes::Department::create("", query(), _("All departments"));
1592+ root->set_subdepartments(departments);
1593+}
1594+
1595+void click::Query::push_package(const scopes::SearchReplyProxy& searchReply, scopes::Category::SCPtr category, const PackageSet &installedPackages, const Package& pkg)
1596+{
1597+ qDebug() << "pushing result" << QString::fromStdString(pkg.name);
1598+ try {
1599+ scopes::CategorisedResult res(category);
1600+ res.set_title(pkg.title);
1601+ res.set_art(pkg.icon_url);
1602+ res.set_uri(pkg.url);
1603+ res[click::Query::ResultKeys::NAME] = pkg.name;
1604+ auto installed = installedPackages.find(pkg);
1605+ if (installed != installedPackages.end()) {
1606+ res[click::Query::ResultKeys::INSTALLED] = true;
1607+ res["subtitle"] = _("✔ INSTALLED");
1608+ res[click::Query::ResultKeys::VERSION] = installed->version;
1609+ } else {
1610+ res[click::Query::ResultKeys::INSTALLED] = false;
1611+ // TODO: get the real price from the webservice (upcoming branch)
1612+ res["subtitle"] = _("FREE");
1613+ }
1614+
1615+ this->push_result(searchReply, res);
1616+ } catch(const std::exception& e){
1617+ qDebug() << "PackageDetails::loadJson: Exception thrown while decoding JSON: " << e.what() ;
1618+ } catch(...){
1619+ qDebug() << "no reason to catch";
1620+ }
1621+}
1622+
1623+void click::Query::push_highlights(const scopes::SearchReplyProxy& searchReply, const HighlightList& highlights, const PackageSet &locallyInstalledApps)
1624+{
1625+ std::string categoryTemplate = CATEGORY_APPS_DISPLAY; //FIXME
1626+ scopes::CategoryRenderer renderer(categoryTemplate);
1627+
1628+ for (auto const& hl: highlights)
1629+ {
1630+ auto category = register_category(searchReply, hl.name(), hl.name(), "", renderer); //FIXME: highlight slug
1631+ for (auto const& pkg: hl.packages())
1632+ {
1633+ push_package(searchReply, category, locallyInstalledApps, pkg);
1634+ }
1635+ }
1636+ qDebug() << "Highlights pushed";
1637+}
1638+
1639+void click::Query::push_departments(const scopes::SearchReplyProxy& searchReply, const scopes::Department::SCPtr& root)
1640+{
1641+ if (root != nullptr)
1642+ {
1643+ try
1644+ {
1645+ qDebug() << "pushing departments";
1646+ searchReply->register_departments(root);
1647+ }
1648+ catch (const std::exception& e)
1649+ {
1650+ qWarning() << "Failed to register departments for query " << QString::fromStdString(query().query_string()) <<
1651+ ", current department " << QString::fromStdString(query().department_id()) << ": " << e.what();
1652+ }
1653+ }
1654+ else
1655+ {
1656+ qWarning() << "No departments data for query " << QString::fromStdString(query().query_string()) <<
1657+ "', current department " << QString::fromStdString(query().department_id());
1658+ }
1659+}
1660+
1661+//
1662+// push highlights and departments
1663+// use cached highlights for root department, otherwise run an async job for highlights of current department.
1664+void click::Query::add_highlights(scopes::SearchReplyProxy const& searchReply, const PackageSet& locallyInstalledApps)
1665+{
1666+ auto curdep = impl->department_lookup.get_department_info(query().department_id());
1667+ assert(curdep);
1668+ auto subdepts = curdep->sub_departments();
1669+ if (query().department_id() == "") // top-level departments
1670+ {
1671+ unity::scopes::Department::SPtr root;
1672+ populate_departments(subdepts, query().department_id(), root);
1673+ push_departments(searchReply, root);
1674+
1675+ qDebug() << "pushing cached highlights";
1676+ push_highlights(searchReply, impl->highlights, locallyInstalledApps);
1677+ this->finished(searchReply); //FIXME: this shouldn't be needed
1678+ }
1679+ else
1680+ {
1681+ qDebug() << "starting departments call for department" << QString::fromStdString(curdep->id()) << ", href" << QString::fromStdString(curdep->href());
1682+ impl->search_operation = impl->index.departments(curdep->href(), [this, locallyInstalledApps, searchReply](const DepartmentList& depts,
1683+ const HighlightList& highlights, Index::Error error, int)
1684+ {
1685+ if (error == click::Index::Error::NoError)
1686+ {
1687+ qDebug() << "departments call completed";
1688+ unity::scopes::Department::SPtr root;
1689+ populate_departments(depts, query().department_id(), root);
1690+ push_departments(searchReply, root);
1691+ push_highlights(searchReply, highlights, locallyInstalledApps);
1692+ }
1693+ else
1694+ {
1695+ qWarning() << "departments call failed";
1696+ }
1697+ this->finished(searchReply); //FIXME: this shouldn't be needed
1698+ });
1699+ }
1700+}
1701+
1702 void click::Query::add_available_apps(scopes::SearchReplyProxy const& searchReply,
1703 const PackageSet& installedPackages,
1704 const std::string& categoryTemplate)
1705@@ -161,6 +330,8 @@
1706 scopes::CategoryRenderer categoryRenderer(categoryTemplate);
1707 auto category = register_category(searchReply, "appstore", _("Available"), "", categoryRenderer);
1708
1709+ assert(searchReply);
1710+
1711 run_under_qt([=]()
1712 {
1713 auto search_cb = [this, searchReply, category, installedPackages](Packages packages) {
1714@@ -168,37 +339,61 @@
1715
1716 // handle packages data
1717 foreach (auto p, packages) {
1718- qDebug() << "pushing result" << QString::fromStdString(p.name);
1719- try {
1720- scopes::CategorisedResult res(category);
1721- res.set_title(p.title);
1722- res.set_art(p.icon_url);
1723- res.set_uri(p.url);
1724- res[click::Query::ResultKeys::NAME] = p.name;
1725- auto installed = installedPackages.find(p);
1726- if (installed != installedPackages.end()) {
1727- res[click::Query::ResultKeys::INSTALLED] = true;
1728- res["subtitle"] = _("✔ INSTALLED");
1729- res[click::Query::ResultKeys::VERSION] = installed->version;
1730- } else {
1731- res[click::Query::ResultKeys::INSTALLED] = false;
1732- // TODO: get the real price from the webservice (upcoming branch)
1733- res["subtitle"] = _("FREE");
1734- }
1735-
1736- this->push_result(searchReply, res);
1737- } catch(const std::exception& e){
1738- qDebug() << "PackageDetails::loadJson: Exception thrown while decoding JSON: " << e.what() ;
1739- } catch(...){
1740- qDebug() << "no reason to catch";
1741- }
1742+ push_package(searchReply, category, installedPackages, p);
1743 }
1744 qDebug() << "search completed";
1745- this->finished(searchReply);
1746- };
1747-
1748- qDebug() << "starting search of" << QString::fromStdString(query().query_string());
1749- impl->search_operation = impl->index.search(query().query_string(), search_cb);
1750+ this->finished(searchReply); //FIXME: this shouldn't be needed
1751+ };
1752+
1753+ // this is the case when we do bootstrap for the first time, or it failed last time
1754+ if (impl->department_lookup.size() == 0 && !click::Scope::use_old_api())
1755+ {
1756+ qDebug() << "performing bootstrap request";
1757+ impl->search_operation = impl->index.bootstrap([this, search_cb, searchReply, installedPackages](const DepartmentList& deps, const
1758+ HighlightList& highlights, click::Index::Error error, int error_code) {
1759+ if (error == click::Index::Error::NoError)
1760+ {
1761+ qDebug() << "bootstrap request completed";
1762+ auto root = std::make_shared<click::Department>("", "All Departments", "", true);
1763+ root->set_subdepartments(deps);
1764+ DepartmentList rdeps { root };
1765+ impl->department_lookup.rebuild(rdeps);
1766+ impl->highlights = highlights;
1767+ qDebug() << "Total number of departments:" << impl->department_lookup.size() << ", highlights:" << highlights.size();
1768+ }
1769+ else
1770+ {
1771+ qWarning() << "bootstrap request failed";
1772+ if (error_code == 405) // method not allowed
1773+ {
1774+ qDebug() << "bootstrap not available, using old API";
1775+ click::Scope::set_use_old_api();
1776+ }
1777+ }
1778+
1779+ if (query().query_string().empty() && !click::Scope::use_old_api())
1780+ {
1781+ add_highlights(searchReply, installedPackages);
1782+ }
1783+ else
1784+ {
1785+ qDebug() << "starting search of" << QString::fromStdString(query().query_string());
1786+ impl->search_operation = impl->index.search(query().query_string(), search_cb);
1787+ }
1788+ });
1789+ }
1790+ else
1791+ {
1792+ if (query().query_string().empty() && !click::Scope::use_old_api())
1793+ {
1794+ add_highlights(searchReply, installedPackages);
1795+ }
1796+ else // normal search
1797+ {
1798+ qDebug() << "starting search of" << QString::fromStdString(query().query_string());
1799+ impl->search_operation = impl->index.search(query().query_string(), search_cb);
1800+ }
1801+ }
1802 });
1803 }
1804
1805
1806=== modified file 'scope/clickstore/store-query.h'
1807--- scope/clickstore/store-query.h 2014-06-16 14:09:21 +0000
1808+++ scope/clickstore/store-query.h 2014-06-23 15:00:46 +0000
1809@@ -32,10 +32,16 @@
1810
1811
1812 #include <unity/scopes/SearchQueryBase.h>
1813+#include <unity/scopes/Department.h>
1814
1815 namespace scopes = unity::scopes;
1816
1817 #include <QSharedPointer>
1818+#include <set>
1819+
1820+#include <click/department-lookup.h>
1821+#include <click/package.h>
1822+#include <click/highlights.h>
1823 #include <click/interface.h>
1824
1825 namespace click
1826@@ -43,6 +49,7 @@
1827
1828 class Application;
1829 class Index;
1830+class DepartmentLookup;
1831
1832 class Query : public scopes::SearchQueryBase
1833 {
1834@@ -69,7 +76,8 @@
1835 constexpr static const char* VERSION{"version"};
1836 };
1837
1838- Query(unity::scopes::CannedQuery const& query, click::Index& index, scopes::SearchMetadata const& metadata);
1839+ Query(unity::scopes::CannedQuery const& query, click::Index& index, click::DepartmentLookup& dept_lookup, click::HighlightList& highlights,
1840+ scopes::SearchMetadata const& metadata);
1841 virtual ~Query();
1842
1843 virtual void cancelled() override;
1844@@ -77,8 +85,10 @@
1845 virtual void run(scopes::SearchReplyProxy const& reply) override;
1846
1847 protected:
1848- virtual void add_available_apps(const scopes::SearchReplyProxy &searchReply,
1849- const PackageSet &installedPackages, const std::string &category);
1850+ virtual void populate_departments(const click::DepartmentList& depts, const std::string& current_department_id, unity::scopes::Department::SPtr &root);
1851+ virtual void push_departments(const scopes::SearchReplyProxy& searchReply, const scopes::Department::SCPtr& root);
1852+ virtual void add_highlights(scopes::SearchReplyProxy const& searchReply, const PackageSet& installedPackages);
1853+ virtual void add_available_apps(const scopes::SearchReplyProxy &searchReply, const PackageSet &installedPackages, const std::string &category);
1854 virtual click::Interface& clickInterfaceInstance();
1855 virtual PackageSet get_installed_packages();
1856 virtual bool push_result(const scopes::SearchReplyProxy &searchReply, scopes::CategorisedResult const& res);
1857@@ -88,6 +98,9 @@
1858 std::string const& title,
1859 std::string const& icon,
1860 scopes::CategoryRenderer const& renderer_template);
1861+ virtual void push_package(const scopes::SearchReplyProxy& searchReply, scopes::Category::SCPtr category, const PackageSet &locallyInstalledApps,
1862+ const click::Package& pkg);
1863+ virtual void push_highlights(const scopes::SearchReplyProxy& searchReply, const HighlightList& highlights, const PackageSet &locallyInstalledApps);
1864 virtual void run_under_qt(const std::function<void()> &task);
1865
1866 private:
1867
1868=== modified file 'scope/clickstore/store-scope.cpp'
1869--- scope/clickstore/store-scope.cpp 2014-06-19 17:05:34 +0000
1870+++ scope/clickstore/store-scope.cpp 2014-06-23 15:00:46 +0000
1871@@ -28,6 +28,7 @@
1872 */
1873
1874 #include <click/qtbridge.h>
1875+#include <click/department-lookup.h>
1876 #include "store-scope.h"
1877 #include "store-query.h"
1878 #include <click/preview.h>
1879@@ -42,18 +43,31 @@
1880
1881 #include <logging.h>
1882
1883+bool click::Scope::old_api = false;
1884
1885 click::Scope::Scope()
1886 {
1887 nam.reset(new click::network::AccessManager());
1888 client.reset(new click::web::Client(nam));
1889 index.reset(new click::Index(client));
1890+ depts.reset(new click::DepartmentLookup());
1891+ highlights.reset(new click::HighlightList());
1892 }
1893
1894 click::Scope::~Scope()
1895 {
1896 }
1897
1898+void click::Scope::set_use_old_api()
1899+{
1900+ old_api = true;
1901+}
1902+
1903+bool click::Scope::use_old_api()
1904+{
1905+ return old_api;
1906+}
1907+
1908 void click::Scope::start(std::string const&, scopes::RegistryProxy const&)
1909 {
1910 setlocale(LC_ALL, "");
1911@@ -68,7 +82,6 @@
1912 static const int zero = 0;
1913 auto emptyCb = [this]()
1914 {
1915-
1916 };
1917
1918 qt::core::world::build_and_run(zero, nullptr, emptyCb);
1919@@ -81,7 +94,7 @@
1920
1921 scopes::SearchQueryBase::UPtr click::Scope::search(unity::scopes::CannedQuery const& q, scopes::SearchMetadata const& metadata)
1922 {
1923- return scopes::SearchQueryBase::UPtr(new click::Query(q, *index, metadata));
1924+ return scopes::SearchQueryBase::UPtr(new click::Query(q, *index, *depts, *highlights, metadata));
1925 }
1926
1927
1928@@ -91,10 +104,12 @@
1929 return scopes::PreviewQueryBase::UPtr{new click::Preview(result, metadata, client, nam)};
1930 }
1931
1932-unity::scopes::ActivationQueryBase::UPtr click::Scope::perform_action(unity::scopes::Result const& result, unity::scopes::ActionMetadata const& metadata, std::string const& /* widget_id */, std::string const& action_id)
1933+unity::scopes::ActivationQueryBase::UPtr click::Scope::perform_action(unity::scopes::Result const& result, unity::scopes::ActionMetadata const& metadata,
1934+ std::string const& widget_id, std::string const& _action_id)
1935 {
1936+ std::string action_id = _action_id;
1937+ qDebug() << "perform_action called with widget_id" << QString::fromStdString(widget_id) << "and action_id:" << QString::fromStdString(action_id);
1938 auto activation = new ScopeActivation(result, metadata);
1939- qDebug() << "perform_action called with action_id" << QString().fromStdString(action_id);
1940
1941 // if the purchase is completed, do the install
1942 if (action_id == "purchaseCompleted") {
1943
1944=== modified file 'scope/clickstore/store-scope.h'
1945--- scope/clickstore/store-scope.h 2014-06-18 14:42:47 +0000
1946+++ scope/clickstore/store-scope.h 2014-06-23 15:00:46 +0000
1947@@ -30,6 +30,7 @@
1948 #ifndef CLICK_SCOPE_H
1949 #define CLICK_SCOPE_H
1950
1951+#include <memory>
1952 #include <click/network_access_manager.h>
1953 #include <click/webclient.h>
1954
1955@@ -43,6 +44,9 @@
1956
1957 namespace click
1958 {
1959+
1960+class DepartmentLookup;
1961+
1962 class Scope : public scopes::ScopeBase
1963 {
1964 public:
1965@@ -60,12 +64,18 @@
1966
1967 virtual unity::scopes::ActivationQueryBase::UPtr perform_action(unity::scopes::Result const& result, unity::scopes::ActionMetadata const& metadata, std::string const& widget_id, std::string const& action_id) override;
1968
1969+ static void set_use_old_api();
1970+ static bool use_old_api();
1971+
1972 private:
1973 QSharedPointer<click::network::AccessManager> nam;
1974 QSharedPointer<click::web::Client> client;
1975 QSharedPointer<click::Index> index;
1976+ std::shared_ptr<click::DepartmentLookup> depts;
1977+ std::shared_ptr<click::HighlightList> highlights;
1978
1979 std::string installApplication(unity::scopes::Result const& result);
1980+ static bool old_api;
1981 };
1982 }
1983 #endif // CLICK_SCOPE_H
1984
1985=== modified file 'scope/tests/integration/webclient_integration.cpp'
1986--- scope/tests/integration/webclient_integration.cpp 2014-06-12 21:05:25 +0000
1987+++ scope/tests/integration/webclient_integration.cpp 2014-06-23 15:00:46 +0000
1988@@ -30,6 +30,7 @@
1989 #include <click/network_access_manager.h>
1990 #include <click/webclient.h>
1991 #include <click/index.h>
1992+#include <click/departments.h>
1993
1994 #include <QCoreApplication>
1995 #include <QDebug>
1996
1997=== modified file 'scope/tests/test_query.cpp'
1998--- scope/tests/test_query.cpp 2014-06-12 22:17:42 +0000
1999+++ scope/tests/test_query.cpp 2014-06-23 15:00:46 +0000
2000@@ -45,6 +45,7 @@
2001 #include <unity/scopes/CannedQuery.h>
2002 #include <unity/scopes/ScopeBase.h>
2003 #include <unity/scopes/SearchReply.h>
2004+#include <unity/scopes/testing/MockSearchReply.h>
2005
2006 using namespace ::testing;
2007 using namespace click;
2008@@ -57,10 +58,17 @@
2009
2010 class MockIndex : public click::Index {
2011 click::Packages packages;
2012+ click::DepartmentList departments;
2013+ click::DepartmentList bootstrap_departments;
2014+ click::HighlightList bootstrap_highlights;
2015 public:
2016- MockIndex(click::Packages packages = click::Packages())
2017+ MockIndex(click::Packages packages = click::Packages(),
2018+ click::DepartmentList departments = click::DepartmentList(),
2019+ click::DepartmentList boot_departments = click::DepartmentList())
2020 : Index(QSharedPointer<click::web::Client>()),
2021- packages(packages)
2022+ packages(packages),
2023+ departments(departments),
2024+ bootstrap_departments(boot_departments)
2025 {
2026
2027 }
2028@@ -72,15 +80,21 @@
2029 return click::web::Cancellable();
2030 }
2031
2032- MOCK_METHOD2(do_search,
2033- void(const std::string&,
2034- std::function<void(click::Packages)>));
2035+ click::web::Cancellable bootstrap(std::function<void(const click::DepartmentList&, const click::HighlightList&, Error, int)> callback) override
2036+ {
2037+ callback(bootstrap_departments, bootstrap_highlights, click::Index::Error::NoError, 0);
2038+ return click::web::Cancellable();
2039+ }
2040+
2041+ MOCK_METHOD2(do_search, void(const std::string&, std::function<void(click::Packages)>));
2042 };
2043
2044 class MockQueryBase : public click::Query {
2045 public:
2046 MockQueryBase(const unity::scopes::CannedQuery& query, click::Index& index,
2047- scopes::SearchMetadata const& metadata) : click::Query(query, index, metadata)
2048+ click::DepartmentLookup& depts,
2049+ click::HighlightList& highlights,
2050+ scopes::SearchMetadata const& metadata) : click::Query(query, index, depts, highlights, metadata)
2051 {
2052
2053 }
2054@@ -94,7 +108,9 @@
2055 class MockQuery : public MockQueryBase {
2056 public:
2057 MockQuery(const unity::scopes::CannedQuery& query, click::Index& index,
2058- scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, metadata)
2059+ click::DepartmentLookup& depts,
2060+ click::HighlightList& highlights,
2061+ scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, depts, highlights, metadata)
2062 {
2063
2064 }
2065@@ -118,7 +134,9 @@
2066 class MockQueryRun : public MockQueryBase {
2067 public:
2068 MockQueryRun(const unity::scopes::CannedQuery& query, click::Index& index,
2069- scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, metadata)
2070+ click::DepartmentLookup& depts,
2071+ click::HighlightList& highlights,
2072+ scopes::SearchMetadata const& metadata) : MockQueryBase(query, index, depts, highlights, metadata)
2073 {
2074
2075 }
2076@@ -147,12 +165,16 @@
2077 TEST(QueryTest, testAddAvailableAppsCallsClickIndex)
2078 {
2079 MockIndex mock_index;
2080+ click::DepartmentLookup dept_lookup;
2081+ click::HighlightList highlights;
2082 scopes::SearchMetadata metadata("en_EN", "phone");
2083 PackageSet no_installed_packages;
2084 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
2085- MockQuery q(query, mock_index, metadata);
2086+ MockQuery q(query, mock_index, dept_lookup, highlights, metadata);
2087 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _)).Times(1);
2088- scopes::SearchReplyProxy reply;
2089+
2090+ scopes::testing::MockSearchReply mock_reply;
2091+ scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
2092
2093 scopes::CategoryRenderer renderer("{}");
2094 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
2095@@ -166,17 +188,21 @@
2096 {"name", "title", 0.0, "icon", "uri"}
2097 };
2098 MockIndex mock_index(packages);
2099+ click::DepartmentLookup dept_lookup;
2100+ click::HighlightList highlights;
2101 scopes::SearchMetadata metadata("en_EN", "phone");
2102 PackageSet no_installed_packages;
2103 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
2104- MockQuery q(query, mock_index, metadata);
2105+ MockQuery q(query, mock_index, dept_lookup, highlights, metadata);
2106 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
2107
2108 scopes::CategoryRenderer renderer("{}");
2109 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
2110 EXPECT_CALL(q, register_category(_, _, _, _, _)).WillOnce(Return(ptrCat));
2111
2112- scopes::SearchReplyProxy reply;
2113+ scopes::testing::MockSearchReply mock_reply;
2114+ scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
2115+
2116 auto expected_title = packages.front().title;
2117 EXPECT_CALL(q, push_result(_, Property(&scopes::CategorisedResult::title, expected_title)));
2118 q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
2119@@ -188,17 +214,20 @@
2120 {"name", "title", 0.0, "icon", "uri"}
2121 };
2122 MockIndex mock_index(packages);
2123+ click::DepartmentLookup dept_lookup;
2124+ click::HighlightList highlights;
2125 scopes::SearchMetadata metadata("en_EN", "phone");
2126 PackageSet no_installed_packages;
2127 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
2128- MockQuery q(query, mock_index, metadata);
2129+ MockQuery q(query, mock_index, dept_lookup, highlights, metadata);
2130 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
2131
2132 scopes::CategoryRenderer renderer("{}");
2133 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
2134 EXPECT_CALL(q, register_category(_, _, _, _, _)).WillOnce(Return(ptrCat));
2135
2136- scopes::SearchReplyProxy reply;
2137+ scopes::testing::MockSearchReply mock_reply;
2138+ scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
2139 EXPECT_CALL(q, finished(_));
2140 q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
2141 }
2142@@ -209,10 +238,12 @@
2143 {"name", "title", 0.0, "icon", "uri"}
2144 };
2145 MockIndex mock_index(packages);
2146+ click::DepartmentLookup dept_lookup;
2147+ click::HighlightList highlights;
2148 scopes::SearchMetadata metadata("en_EN", "phone");
2149 PackageSet no_installed_packages;
2150 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
2151- MockQueryRun q(query, mock_index, metadata);
2152+ MockQueryRun q(query, mock_index, dept_lookup, highlights, metadata);
2153 auto reply = scopes::SearchReplyProxy();
2154 EXPECT_CALL(q, get_installed_packages()).WillOnce(Return(no_installed_packages));
2155 EXPECT_CALL(q, add_available_apps(reply, no_installed_packages, _));
2156@@ -234,15 +265,18 @@
2157 PackageSet one_installed_package {
2158 {"org.example.app2", "0.2"}
2159 };
2160+ click::DepartmentLookup dept_lookup;
2161+ click::HighlightList highlights;
2162 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
2163- MockQuery q(query, mock_index, metadata);
2164+ MockQuery q(query, mock_index, dept_lookup, highlights, metadata);
2165 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
2166
2167 scopes::CategoryRenderer renderer("{}");
2168 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
2169 EXPECT_CALL(q, register_category(_, _, _, _, _)).WillOnce(Return(ptrCat));
2170
2171- scopes::SearchReplyProxy reply;
2172+ scopes::testing::MockSearchReply mock_reply;
2173+ scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
2174 auto expected_name1 = packages.front().name;
2175 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name1)));
2176 auto expected_name2 = packages.back().name;
2177@@ -261,15 +295,18 @@
2178 PackageSet one_installed_package {
2179 {"org.example.app2", "0.2"}
2180 };
2181+ click::DepartmentLookup dept_lookup;
2182+ click::HighlightList highlights;
2183 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
2184- MockQuery q(query, mock_index, metadata);
2185+ MockQuery q(query, mock_index, dept_lookup, highlights, metadata);
2186 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
2187
2188 scopes::CategoryRenderer renderer("{}");
2189 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
2190 EXPECT_CALL(q, register_category(_, _, _, _, _)).WillOnce(Return(ptrCat));
2191
2192- scopes::SearchReplyProxy reply;
2193+ scopes::testing::MockSearchReply mock_reply;
2194+ scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
2195 EXPECT_CALL(q, push_result(_, IsInstalled(true)));
2196 EXPECT_CALL(q, push_result(_, IsInstalled(false)));
2197 q.wrap_add_available_apps(reply, one_installed_package, FAKE_CATEGORY_TEMPLATE);
2198@@ -288,8 +325,10 @@
2199 };
2200 MockIndex mock_index(uninstalled_packages);
2201 scopes::SearchMetadata metadata("en_EN", "phone");
2202+ click::DepartmentLookup dept_lookup;
2203+ click::HighlightList highlights;
2204 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
2205- MockQuery q(query, mock_index, metadata);
2206+ MockQuery q(query, mock_index, dept_lookup, highlights, metadata);
2207 PackageSet installed_packages{{"package_1", "0.1"}};
2208
2209 FakeInterface fake_interface;

Subscribers

People subscribed via source and target branches

to all changes: