Merge lp:~knitzsche/scope-aggregator/featured into lp:scope-aggregator

Proposed by Kyle Nitzsche
Status: Merged
Approved by: Kyle Nitzsche
Approved revision: 206
Merged at revision: 175
Proposed branch: lp:~knitzsche/scope-aggregator/featured
Merge into: lp:scope-aggregator
Prerequisite: lp:~knitzsche/scope-aggregator/refactor-handle-methods_add-login_remove-shared-category
Diff against target: 1448 lines (+1015/-49)
15 files modified
README.md (+77/-10)
gtests/CMakeLists.txt (+1/-0)
gtests/MockRegistry.h (+13/-1)
gtests/scope_directory/featured_1.json (+398/-0)
gtests/scope_test.cpp (+130/-2)
include/aggchildscope.h (+13/-0)
include/client.h (+3/-0)
include/query.h (+19/-3)
include/scope.h (+1/-2)
src/aggchildscope.cpp (+37/-0)
src/client.cpp (+88/-0)
src/handle_results.cpp (+113/-1)
src/query.cpp (+9/-0)
src/scope.cpp (+6/-25)
src/utils.cpp (+107/-5)
To merge this branch: bzr merge lp:~knitzsche/scope-aggregator/featured
Reviewer Review Type Date Requested Status
Jin (community) Approve
Zhang Enwei (community) Approve
Gary.Wang Pending
Review via email: mp+309784@code.launchpad.net

Description of the change

This implements the new "Featured Result" feature.

The basic idea is declaring a category that shows a single result. Dev declares a set of scopes to pull from. On each refresh, the result is pulled from the next "pull from" scope, cycling through them and wrapping to the start.

Specification document:
https://docs.google.com/document/d/17_Pw4B86hnfIu1slIc2_qFVeKiOdU2KVXHbyMDxosrs/edit#heading=h.gftd6k6aluuu

I tested the built .so in Today, News, Nearby and Photos. All looks good (their behavior is unchanged).

You can test the new feature by installing the scopes as explained in "Working Sample" section of the specification doc linked above.

Note: README.md file in source should completed explain the new "featured" support.

To post a comment you must log in.
Revision history for this message
Zhang Enwei (zhangew401) wrote :

LGTM except not suppress the log.
I verified on china-dashboard and news-china.

review: Approve
Revision history for this message
Jin (jindallo) wrote :

Code looks good and verified pass on my local,
I approve.

review: Approve
205. By Kyle Nitzsche

fix typo in readme

206. By Kyle Nitzsche

supress log output

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.md'
2--- README.md 2016-11-02 13:36:29 +0000
3+++ README.md 2016-11-10 18:47:16 +0000
4@@ -2,7 +2,7 @@
5
6 This is a *declarative* aggregator scope template. You declare aggregated scopes and keywords for inclusion in json files.
7
8-You may combine the various text files and icons with the built scope-aggregator .so file into a click package, thus creating rich aggregator scopes with no c++ coding and compilation.
9+You may combine the various text files and icons with the built scope-aggregator .so file into a package, thus creating rich aggregator scopes with no c++ coding and compilation.
10
11 Here are couple examples:
12
13@@ -16,6 +16,7 @@
14 * `scope`: includes a single scope
15 * `category`: includes a set of scopes and keywords
16 * `keyword`: includes child scopes that answer to the keywords
17+ * `featured`: displays a single result on each refresh by cycling through a declared set of scopes
18
19 These are declared directly inside the `order` json list element. The order of declaration determines the order of presentation in the aggregator scope, with the exception of `keyword` (explained below)
20
21@@ -24,6 +25,7 @@
22 * `scope`: A single Category is created for each `scope`. It contains all results from the scope. The Category displays in the declared order.
23 * `category`: A single Category is created for each `category`. It contains all results from the internally declared set of scopes and from all present scopes that answer to the internally declared set of keywords. The Category displays in the declared order.
24 * `keyword`: A Category is created for each scope that answers to the declared keyword. By default, the only guarantee relating to the overall order is that the first such child scope appears in the declared order. Categories for all other scopes that answer to the keyword display in an determinant order (but always after the first). You can use the additional `display_order` feature to set the order of child scopes answering to this keyword.
25+ * `featured`: A single Category is created for each `featured` that has at least one installed and enabled `pull_from` child scope. It contains a single result: the first result supplied by the current `pull_from` child scope. On each refresh, the next `pull_from` child scope is queried. After the final `pull_from` child scope, on refresh the first `pull_from` child scope is used.
26
27 ## Departments
28
29@@ -39,13 +41,13 @@
30
31 ## Categories
32
33-As noted, a Category is automatically created for each `scope`, `category`, and for each scope answering to a `keyword`.
34+As noted, a Category is automatically created for each `scope`, `category`, `featured`, and for each scope answering to a `keyword`.
35
36 You can set various Category items, including title, renderers, and, whether tapping the category header (which displays the title) switches focus to another child scope (called "link to child").
37
38-By default, the link to child feature is enabled for `scope` and `keyword` categories. It can be disabled with `"link_to_child": "false"`.
39+By default, the link to child feature is enabled for `scope`, `keyword` and `featured` categories. It can be disabled with `"link_to_child": "false"`. For `featured` objects, the link to child points to the scope providing the current featured result.
40
41-For `category` declaration, the link to child feature may or may not make sense. By default, there is no link to child for `category`. But, if the `category` always and only contains a single child scope, it usually makes sense. This is enabled with `"link_to_child_specified": "scope ID"`.
42+For `category` declaration, the link to child feature may or may not be appropriate. By default, there is no link to child for `category`. But, if the `category` always and only contains a single child scope, it usually makes sense. This is enabled with `"link_to_child_specified": "scope ID"`.
43
44 But, when a `category` contains multiple child scopes, either when the `category` is declared with more than one child scope or when it is declared to include a keyword (which may result in multiple child scopes, link to child may not be appropriate because there may be no reasonable single child scope to link to. But even with multiple child scopes, link to child may still be appropriate: for example, the Today scope has a `category` that aggregates news headlines. It has a link to child that takes the user to the News scope
45 Headlines department.
46@@ -65,9 +67,9 @@
47
48 In general, when no render is declared, the renderer is taken from the first incoming result in the Category. This works, but the layout and design is not in control of the aggregator scope developer, and there is no consistency in the layout and design among the multiple child scopes displayed. Therefore, designing and declaring renderers is a critical part for aggregator scope development.
49
50-Typically you declare your renderers right inside your `scope`, `category` or `keyword` object.
51+Typically you declare your renderers right inside your `scope`, `category`, `featured`, and `keyword` object.
52
53-If you wish to declare renderers once and reuse them, you may declare each type of renderer (except for the login renderer) once in a `common_templates` object and include them by ID inside your `scope`, `category` or `keyword` object, as explained below.
54+If you wish to declare renderers once and reuse them, you may declare each type of renderer (except for the login renderer) once in a `common_templates` object and include them by ID inside your `scope`, `category`, `featured` or `keyword` object, as explained below.
55
56 ## Cardinality
57
58@@ -82,6 +84,8 @@
59
60 You may use both approaches simultaneously. If so, declared cardinality for `scope`, `category` and `keyword` preempts settings.
61
62+Cardinality for `featured` objects is hard coded to 1 and cannot be modified.
63+
64 ### Cardinality with scope settings
65
66 Suppose you want the user to choose between cardinality of 1,2,4, and 8, and you want the default to be 2. Here is your settings.ini:
67@@ -138,7 +142,7 @@
68
69 Fallbacks allows you to declare that for a given attribute in your renderer (for example "art"), you want to check a specified series of fields in the incoming result and use the first one that exists. For example, to get a value for your "art" field, you might want to check the "art" field, then the "mascot" field, and finally the "emblem" field of each incoming result.
70
71-This is currently support using `common_templates`. Your template in common templates declares a `fallbacks` object. And of course, you declare a renderer to use the common template (instead of being declared in place).
72+This is currently supported using `common_templates`. Your template in common templates declares a `fallbacks` object. And of course, you declare a renderer to use the common template (instead of being declared in place).
73
74 This example accomplishes the above goal:
75
76@@ -179,10 +183,10 @@
77
78 * `departments`: Here you declared your departments or declare you do not want departments
79 * `cardinality`: Here you declared cardinality options for use with scope settings.
80-* `order`: Here you declare your specific scopes, keywords and categories for aggregation
81-* `commmon_templates`: Here you declare category renderer templates you may use for multiple specified categories
82+* `order`: Here you declare your specific `scope`, `keyword`, `featured`s, and `category`s for aggregation
83+* `commmon_templates`: Here you declare category renderer templates you may reuse conveniently
84
85-The order of these objects in the file has no affect.
86+The order of these top level objects in the file has no consequence.
87
88 ## `departments` (optional)
89
90@@ -289,6 +293,13 @@
91 "order":
92 [
93 {
94+ "featured":
95+ {
96+ [...]
97+ },
98+ },
99+
100+ {
101 "scope":
102 {
103 [...]
104@@ -576,6 +587,62 @@
105 }
106 [...]
107
108+### `featured` (optional)
109+
110+A `featured` object displays a single result on each refresh from the current `pull_from` child scope. On each refresh, the next `pull_from` child scope is used. All `pull_from` scopes must be separately declared as `scope` objects. The idea is that the featured category cycles through a set of declared scopes on each refresh, displaying the first result from the current scope. Scopes that are not installed and enabled (in the aggregator scope settings) are excluded from the `pull_from` list. If there are no current `pull_from` scopes, the featured category does not display. Featured categories do not display when a user enters a search string.
111+
112+ * A unique `id` is required.
113+ * The rendered can be set with `renderer` or with a `renderer_common_id`.
114+ * The category title can be hard coded and unvarying: set it with`_title` (child of `featured`).
115+ * The category title can vary depending on the current child scope: set it for each child scope as shown below.
116+ * `_title` supports i18n/l10n.
117+ * By default, the Category has a link to the current child scope. This can be disabled with `"link_to_child": "false"`.
118+ * Mutiple `featured` object are supported.
119+ * `featured` objects may be used with Departments.
120+ * Cardinality is hard coded to 1.
121+ * Fallbacks are supported when using `renderer_common_id` with `common_templates`.
122+
123+In this example, the category title varies with the current child scope and a link to current child scope is provided.
124+
125+ {
126+ "featured": {
127+ "id": "featured_1",
128+ "department": "dept1",
129+ "pull_from":[
130+ {
131+ "id": "developerx.mymoviechannel_movies",
132+ "_title": "Featured: My Movies"
133+ },
134+ {
135+ "id": "developery.hotmovies_movies",
136+ "_title": "Featured: Hot Movies"
137+ }
138+ ],
139+ "renderer_common_id": "featured_template_1",
140+ }
141+ },
142+
143+In this example, the category title is set once (it never changes) and link to child is not present.
144+
145+ {
146+ "featured": {
147+ "id": "featured_1",
148+ "department": "dept1",
149+ "_title": "Featured: Movies",
150+ "link_to_child": "false",
151+ "pull_from":[
152+ {
153+ "id": "developerx.mymoviechannel_movies",
154+ },
155+ {
156+ "id": "developery.hotmovies_movies",
157+ }
158+ ],
159+ "renderer_common_id": "featured_template_1",
160+ }
161+ },
162+
163+
164 ### `category` (optional)
165
166 You can declare a category that includes a specified set of scopes and keywords. All results from these child scopes are displayed in a single category.
167
168=== modified file 'gtests/CMakeLists.txt'
169--- gtests/CMakeLists.txt 2016-09-22 16:39:29 +0000
170+++ gtests/CMakeLists.txt 2016-11-10 18:47:16 +0000
171@@ -76,6 +76,7 @@
172 COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/login_1.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/login_1.json"
173 COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/login_2.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/login_2.json"
174 COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/login_3.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/login_3.json"
175+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/featured_1.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/featured_1.json"
176 COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/cache_directory"
177 )
178
179
180=== modified file 'gtests/MockRegistry.h'
181--- gtests/MockRegistry.h 2016-09-21 17:24:06 +0000
182+++ gtests/MockRegistry.h 2016-11-10 18:47:16 +0000
183@@ -154,13 +154,25 @@
184 {
185 mm.insert(pair<string, ScopeMetadata>("full_keyword_3", full_keyword_3));
186 }
187-
188 set<string> keywords_login = {"login-test"};
189 auto login_metadata = build_scope_metadata("login-test", keywords_login);
190 if (predicate(login_metadata))
191 {
192 mm.insert(pair<string, ScopeMetadata>("login-test", login_metadata));
193 }
194+ set<string> keywords_none = {};
195+ auto featured_1_metadata = build_scope_metadata("featured_1", keywords_none);
196+ if (predicate(featured_1_metadata))
197+ {
198+ mm.insert(pair<string, ScopeMetadata>("featured_1", featured_1_metadata));
199+ }
200+ auto featured_2_metadata = build_scope_metadata("featured_2", keywords_none);
201+ if (predicate(featured_2_metadata))
202+ {
203+ mm.insert(pair<string, ScopeMetadata>("featured_2", featured_2_metadata));
204+ }
205+
206+
207 return mm;
208 }
209 };
210
211=== added file 'gtests/scope_directory/featured_1.json'
212--- gtests/scope_directory/featured_1.json 1970-01-01 00:00:00 +0000
213+++ gtests/scope_directory/featured_1.json 2016-11-10 18:47:16 +0000
214@@ -0,0 +1,398 @@
215+{
216+ "departments":
217+ {
218+ "do_not_use_departments": "false",
219+ "declarations":
220+ [
221+ {
222+ "id": "dept1",
223+ "root": "true",
224+ "_title": "Dept 1"
225+ },
226+ {
227+ "id": "dept2",
228+ "root": "False",
229+ "_title": "Dept 2"
230+ },
231+ {
232+ "id": "dept3",
233+ "root": "False",
234+ "_title": "Dept 3"
235+ }
236+ ]
237+ },
238+ "common_templates": [
239+ {
240+ "id": "news",
241+ "fallbacks": [
242+ {
243+ "fields": [
244+ {
245+ "field": "art"
246+ },
247+ {
248+ "field": "mascot"
249+ },
250+ {
251+ "field": "emblem"
252+ }
253+ ],
254+ "key": "art"
255+ }
256+ ],
257+ "template": {
258+ "components": {
259+ "art": "art",
260+ "attributes": "attributes",
261+ "subtitle": "published",
262+ "title": "title"
263+ },
264+ "schema-version": 1,
265+ "template": {
266+ "card-size": "medium",
267+ "category-layout": "horizontal-list",
268+ "collapsed-rows": 1
269+ }
270+ }
271+ },
272+ {
273+ "id": "not_logged_in",
274+ "template": {
275+ "components": {
276+ "mascot": "art",
277+ "title": "title"
278+ },
279+ "schema-version": 1,
280+ "template": {
281+ "card-layout": "horizontal",
282+ "card-size": "large",
283+ "category-layout": "grid"
284+ }
285+ }
286+ }
287+ ],
288+ "order": [
289+ {
290+ "featured": {
291+ "id": "featured",
292+ "department": "dept1",
293+ "NOT_title": "Featured",
294+ "link_to_child": "falseNOT",
295+ "pull_from":[
296+ {
297+ "id": "featured_1",
298+ "_title": "Featured: One"
299+ },
300+ {
301+ "id": "featured_2",
302+ "_title": "Featured: Two"
303+ }
304+ ],
305+ "renderer_common_id": "news",
306+ "NOTrenderer": {
307+ "components": {
308+ "subtitle": "subtitle",
309+ "title": "title",
310+ "art": {
311+ "field": "art",
312+ "aspect-ration": 2.0
313+ }
314+ },
315+ "schema-version": 1,
316+ "template": {
317+ "card-layout": "vertical",
318+ "card-size": "large",
319+ "category-layout": "grid",
320+ "overlay": true,
321+ "collapsed-rows": 1
322+ }
323+ }
324+ }
325+ },
326+ {
327+ "scope": {
328+ "department": "dept1",
329+ "_category_title": "Featured One",
330+ "cardinality": 10,
331+ "id": "featured_1",
332+ "local_id": "featured_1_localId",
333+ "renderer": {
334+ "components": {
335+ "art": "art",
336+ "subtitle": "subtitle",
337+ "title": "title"
338+ },
339+ "schema-version": 1,
340+ "template": {
341+ "card-layout": "vertical",
342+ "card-size": "small",
343+ "category-layout": "grid",
344+ "collapsed-rows": 1
345+ }
346+ }
347+ }
348+ },
349+ {
350+ "scope": {
351+ "_category_title": "featured_2",
352+ "cardinality": 10,
353+ "id": "featured_2",
354+ "local_id": "featured_2_localID",
355+ "renderer": {
356+ "components": {
357+ "subtitle": "subtitle",
358+ "title": "title"
359+ },
360+ "schema-version": 1,
361+ "template": {
362+ "card-layout": "vertical",
363+ "card-size": "medium",
364+ "category-layout": "grid",
365+ "collapsed-rows": 1
366+ }
367+ }
368+ }
369+ },
370+ {
371+ "featured": {
372+ "id": "featured2",
373+ "department": "dept2",
374+ "NOT_title": "Featured",
375+ "link_to_child": "falseNOT",
376+ "pull_from":[
377+ {
378+ "id": "featured_2",
379+ "_title": "Featured: two"
380+ },
381+ {
382+ "id": "com.canonical.scopes.tasks_sctasks",
383+ "_title": "Featured: Tasks"
384+ }
385+ ],
386+ "renderer_common_id": "news",
387+ "NOTrenderer": {
388+ "components": {
389+ "subtitle": "subtitle",
390+ "title": "title",
391+ "art": {
392+ "field": "art",
393+ "aspect-ration": 2.0
394+ }
395+ },
396+ "schema-version": 1,
397+ "template": {
398+ "card-layout": "vertical",
399+ "card-size": "large",
400+ "category-layout": "grid",
401+ "overlay": true,
402+ "collapsed-rows": 1
403+ }
404+ }
405+ }
406+ },
407+ {
408+ "scope": {
409+ "department": "dept2",
410+ "category_title_use_incoming": "true",
411+ "cardinality": 10,
412+ "id": "com.canonical.scopes.events_events",
413+ "local_id": "events_localID",
414+ "renderer": {
415+ "components": {
416+ "art": "art",
417+ "subtitle": "subtitle",
418+ "title": "title"
419+ },
420+ "schema-version": 1,
421+ "template": {
422+ "card-layout": "horizontal",
423+ "card-size": "small",
424+ "category-layout": "grid",
425+ "collapsed-rows": 1
426+ }
427+ }
428+ }
429+ },
430+ {
431+ "scope": {
432+ "department": "dept2",
433+ "category_title_use_incoming": "true",
434+ "cardinality": 10,
435+ "id": "com.canonical.scopes.tasks_sctasks",
436+ "local_id": "tasks_localID",
437+ "renderer": {
438+ "components": {
439+ "art": "art",
440+ "subtitle": "subtitle",
441+ "title": "title"
442+ },
443+ "schema-version": 1,
444+ "template": {
445+ "card-layout": "horizontal",
446+ "card-size": "small",
447+ "category-layout": "grid",
448+ "collapsed-rows": 1
449+ }
450+ }
451+ }
452+ },
453+ {
454+ "scope": {
455+ "department": "dept2",
456+ "cardinality": 0,
457+ "_category_title": "Favorite Contacts",
458+ "id": "com.canonical.scopes.contacts_contacts",
459+ "local_id": "contacts",
460+ "renderer": {
461+ "components": {
462+ "art": "art",
463+ "attributes": "attributes",
464+ "subtitle": "published",
465+ "title": "title"
466+ },
467+ "schema-version": 1,
468+ "template": {
469+ "card-size": "medium",
470+ "category-layout": "carousel",
471+ "collapsed-rows": 1
472+ }
473+ }
474+ }
475+ },
476+ {
477+ "category": {
478+ "department": "dept2",
479+ "_title": "Today's News",
480+ "cardinality": 3,
481+ "id": "headlines",
482+ "keywords": [
483+ {
484+ "id": "news.headlines",
485+ "local_id": "headlines_categoryKeyword"
486+ }
487+ ],
488+ "link_to_child_specified": "com.canonical.scopes.news_unity-scope-news",
489+ "renderer_common_id": "news",
490+ "scopes": []
491+ }
492+ },
493+ {
494+ "category": {
495+ "department": "dept1",
496+ "_title": "Twitter",
497+ "cardinality": 10,
498+ "id": "twitter",
499+ "link_to_child_specified": "twitter.canonicalpartners_twitter",
500+ "keywords": [
501+ {
502+ "id": "twitter.home",
503+ "local_id": "twitter.home_localID"
504+ }
505+ ],
506+ "renderer": {
507+ "components": {
508+ "mascot": "art",
509+ "attributes": "attributes",
510+ "subttitle": "subtitle",
511+ "summary": "summary",
512+ "title": "title"
513+ },
514+ "schema-version": 1,
515+ "template": {
516+ "category-layout": "vertical-journal",
517+ "card-size": "medium",
518+ "card-layout": "horizontal",
519+ "collapsed-rows": 0
520+ }
521+ },
522+ "scopes": []
523+ }
524+ },
525+ {
526+ "featured": {
527+ "id": "featured3",
528+ "department": "dept3",
529+ "NOT_title": "Featured",
530+ "link_to_child": "falseNOT",
531+ "pull_from":[
532+ {
533+ "id": "notregistered_1",
534+ "_title": "Featured: Not Reg 1"
535+ },
536+ {
537+ "id": "notregistered_2",
538+ "_title": "Featured: Not Reg 2"
539+ },
540+ {
541+ "id": "notregested_3",
542+ "_title": "Featured: Not Reg 3"
543+ }
544+ ],
545+ "renderer_common_id": "news"
546+ }
547+ },
548+ {
549+ "scope": {
550+ "department": "dept3",
551+ "cardinality": 0,
552+ "_category_title": "art",
553+ "id": "knitzsche.testscope-art_testscope-art",
554+ "local_id": "art",
555+ "renderer": {
556+ "components": {
557+ "art": "art",
558+ "title": "title"
559+ },
560+ "schema-version": 1,
561+ "template": {
562+ "card-size": "medium",
563+ "category-layout": "grid",
564+ "collapsed-rows": 1
565+ }
566+ }
567+ }
568+ },
569+ {
570+ "scope": {
571+ "department": "dept3",
572+ "cardinality": 0,
573+ "_category_title": "mascot",
574+ "id": "knitzsche.testscope-mascot_testscope-mascot",
575+ "local_id": "mascot",
576+ "renderer": {
577+ "components": {
578+ "mascot": "mascot",
579+ "title": "title"
580+ },
581+ "schema-version": 1,
582+ "template": {
583+ "card-size": "medium",
584+ "category-layout": "grid",
585+ "collapsed-rows": 1
586+ }
587+ }
588+ }
589+ },
590+ {
591+ "scope": {
592+ "department": "dept3",
593+ "cardinality": 0,
594+ "_category_title": "emblem",
595+ "id": "knitzsche.testscope-emblem_testscope-emblem",
596+ "local_id": "emblem",
597+ "renderer": {
598+ "components": {
599+ "emblem": "emblem",
600+ "title": "title"
601+ },
602+ "schema-version": 1,
603+ "template": {
604+ "card-size": "medium",
605+ "category-layout": "grid",
606+ "collapsed-rows": 1
607+ }
608+ }
609+ }
610+ }
611+ ]
612+}
613
614=== modified file 'gtests/scope_test.cpp'
615--- gtests/scope_test.cpp 2016-09-28 21:01:43 +0000
616+++ gtests/scope_test.cpp 2016-11-10 18:47:16 +0000
617@@ -210,7 +210,7 @@
618 std::string path = scope->scope_directory() + "/" + scope->child_scopes_json_filename();
619 ASSERT_TRUE(scope->load_json_file(child_root, path, scope->scope_id())) << "Problem with: " << path;
620 scope->set_child_root(child_root);
621- std::vector<std::string> declared_scopes = scope->get_declared_scopes(AggScope::IdType::local_id);
622+ std::vector<std::string> declared_scopes = scope->get_declared_scopes();
623 ASSERT_EQ(declared_scopes.size(),7) << "csj1.json declares 7 sxscopes, but get_declared_scopes() found " << declared_scopes.size();
624 }
625
626@@ -365,7 +365,7 @@
627 ASSERT_TRUE(scope->load_json_file(child_root, path, scope->scope_id())) << "Problem with: " << path;
628 scope->set_child_root(child_root);
629 ChildScopeList csl = scope->find_child_scopes();
630- std::vector<std::string> declared_scopes = scope->get_declared_scopes(AggScope::IdType::local_id);
631+ std::vector<std::string> declared_scopes = scope->get_declared_scopes();
632 ASSERT_EQ(csl.size(),7);
633 }
634
635@@ -750,3 +750,131 @@
636 login_case = q->is_login_case(child, "NOT-login-category");
637 ASSERT_FALSE(login_case);
638 }
639+
640+/**
641+ * Verify Client::get_current_feeders()
642+ * That is, verify that only installed & enabled feeders scopes are returned
643+ */
644+TEST_F(TestAggScope, featured_verify_get_current_feeders){
645+ string csj = "featured_1.json";
646+ AggScope * scope_ptr;
647+ scope_ptr = &(*scope);
648+ scope->set_child_scopes_json_filename(csj);
649+ QJsonObject child_root = scope->get_child_root();
650+ std::string path = scope->scope_directory() + "/" + scope->child_scopes_json_filename();
651+ ASSERT_TRUE(scope->load_json_file(child_root, path, scope->scope_id())) << "Problem with: " << path;
652+ scope->set_child_root(child_root);
653+ SearchMetadata smd("en_US", "phone");
654+ CannedQuery cq("not_an_id");
655+ shared_ptr<MockRegistry> reg = make_shared<MockRegistry>();
656+ Query * q = new Query(cq, smd, string("scope_directory"), string("cache_directory"), reg, scope_ptr);
657+ q->load_declarations(scope_ptr->child_root());
658+ q->current_child_scopes = scope->find_child_scopes();
659+
660+ std::vector<std::string> feeders = {"featured_1", "featured_2"};
661+ auto cur = q->client.get_current_feeders(feeders, q->current_child_scopes);
662+ ASSERT_EQ(cur.size(), 2);
663+
664+ feeders = {"featured_1", "featured_NOT"};
665+ cur = q->client.get_current_feeders(feeders, q->current_child_scopes);
666+ ASSERT_EQ(cur.size(), 1);
667+
668+ feeders = {"featured_NOT1", "featured_NOT2"};
669+ cur = q->client.get_current_feeders(feeders, q->current_child_scopes);
670+ ASSERT_EQ(cur.size(), 0);
671+
672+ feeders = {"featured_NOT1", "featured_1"};
673+ cur = q->client.get_current_feeders(feeders, q->current_child_scopes);
674+ ASSERT_EQ(cur.size(), 1);
675+}
676+
677+/**
678+ * Verify Client::get_featured_feeder()
679+ * That is, verify that this method returns the NEXT feeder scope (from
680+ * which to post the featured result), including wrapping to the first
681+ * feeder when the cache file contains the last one, and that the first
682+ * feeder is returned when the existing cache file is removed
683+ */
684+TEST_F(TestAggScope, featured_verify_get_featured_feeder){
685+ string csj = "featured_1.json";
686+ AggScope * scope_ptr;
687+ scope_ptr = &(*scope);
688+ scope->set_child_scopes_json_filename(csj);
689+ QJsonObject child_root = scope->get_child_root();
690+ std::string path = scope->scope_directory() + "/" + scope->child_scopes_json_filename();
691+ ASSERT_TRUE(scope->load_json_file(child_root, path, scope->scope_id())) << "Problem with: " << path;
692+ scope->set_child_root(child_root);
693+ SearchMetadata smd("en_US", "phone");
694+ CannedQuery cq("not_an_id");
695+ shared_ptr<MockRegistry> reg = make_shared<MockRegistry>();
696+ Query * q = new Query(cq, smd, string("scope_directory"), string("cache_directory"), reg, scope_ptr);
697+ q->load_declarations(scope_ptr->child_root());
698+ q->current_child_scopes = scope->find_child_scopes();
699+ std::vector<std::string> feeders = {"featured_1", "featured_2"};
700+ auto cur = q->client.get_current_feeders(feeders, q->current_child_scopes);
701+ ASSERT_EQ(cur.size(), 2);
702+
703+ feeders = {"featured_1", "featured_2", "featured_3"};
704+ cur = q->client.get_current_feeders(feeders, q->current_child_scopes);
705+ ASSERT_EQ(cur.size(),2); // 2 because featured_3 is not registered
706+
707+ //clean previous cache file if any
708+ remove(std::string(scope->cache_directory() + "/featured").c_str());
709+
710+ auto next_feeder = q->client.get_featured_feeder("featured", feeders, scope->cache_directory());
711+ ASSERT_EQ(next_feeder, "featured_1");
712+ next_feeder = q->client.get_featured_feeder("featured", feeders, scope->cache_directory());
713+ ASSERT_EQ(next_feeder, "featured_2");
714+ next_feeder = q->client.get_featured_feeder("featured", feeders, scope->cache_directory());
715+ ASSERT_EQ(next_feeder, "featured_3");
716+ next_feeder = q->client.get_featured_feeder("featured", feeders, scope->cache_directory());
717+ ASSERT_EQ(next_feeder, "featured_1");
718+
719+ next_feeder = q->client.get_featured_feeder("featured", feeders, scope->cache_directory());
720+
721+ //remove cache file to verfiy first feeder is returned
722+ remove(std::string(scope->cache_directory() + "/featured").c_str());
723+ next_feeder = q->client.get_featured_feeder("featured", feeders, scope->cache_directory());
724+ ASSERT_EQ(next_feeder, "featured_1");
725+
726+}
727+
728+/**
729+ * Verify featured categories exist appropriately
730+ */
731+TEST_F(TestAggScope, featured_featured_categories_exist){
732+ string csj = "featured_1.json";
733+ AggScope * scope_ptr;
734+ scope_ptr = &(*scope);
735+ scope->set_child_scopes_json_filename(csj);
736+ QJsonObject child_root = scope->get_child_root();
737+ std::string path = scope->scope_directory() + "/" + scope->child_scopes_json_filename();
738+ ASSERT_TRUE(scope->load_json_file(child_root, path, scope->scope_id())) << "Problem with: " << path;
739+ scope->set_child_root(child_root);
740+ SearchMetadata smd("en_US", "phone");
741+ CannedQuery cq("not_an_id");
742+ shared_ptr<MockRegistry> reg = make_shared<MockRegistry>();
743+ Query * q = new Query(cq, smd, string("scope_directory"), string("cache_directory"), reg, scope_ptr);
744+ q->load_declarations(scope_ptr->child_root());
745+ q->current_child_scopes = scope->find_child_scopes();
746+
747+ q->make_featured_scopes();
748+ q->set_scope_order();
749+ bool featured_1_found = false;
750+ bool featured_2_found = false;
751+ bool featured_3_found = false;
752+ for (auto s : q->scopes_ordered)
753+ {
754+ if (s == "featured")
755+ featured_1_found = true;
756+ if (s == "featured2")
757+ featured_2_found = true;
758+ if (s == "featured3")
759+ featured_3_found = true;
760+ }
761+ ASSERT_TRUE(featured_1_found);
762+ ASSERT_TRUE(featured_2_found);
763+ ASSERT_FALSE(featured_3_found); // "featured3" IS declared but it has no installed and registered feeder
764+}
765+
766+
767
768=== modified file 'include/aggchildscope.h'
769--- include/aggchildscope.h 2016-09-20 17:00:53 +0000
770+++ include/aggchildscope.h 2016-11-10 18:47:16 +0000
771@@ -46,6 +46,7 @@
772 */
773 AggChildScope(std::string const & id);
774
775+ void set_id(std::string const & id);
776 void set_local_id(std::string const & local_id);
777 const std::string & local_id() const;
778
779@@ -370,6 +371,15 @@
780 void set_buffer(bool);
781 bool buffer();
782
783+ void set_featured_scope(bool);
784+ bool featured_scope();
785+
786+ void set_linked_featured_id(std::string);
787+ std::string linked_featured_id();
788+
789+ void set_featured_feeders(std::vector<std::string> feeders);
790+ std::vector<std::string> featured_feeders();
791+
792 void set_category_scope(bool);
793 bool category_scope();
794
795@@ -398,6 +408,9 @@
796 std::shared_ptr<us::ScopeMetadata> scope_metadata_;
797
798 bool buffer_ = true;
799+ bool featured_scope_ = false;
800+ std::string linked_featured_id_ = "";
801+ std::vector<std::string> featured_feeders_; //ids of feeder child scopes
802 bool category_scope_ = false;
803 std::string category_scope_id_;
804 bool keyword_scope_ = false;
805
806=== modified file 'include/client.h'
807--- include/client.h 2016-08-19 20:46:32 +0000
808+++ include/client.h 2016-11-10 18:47:16 +0000
809@@ -21,6 +21,7 @@
810
811 #include <unity/UnityExceptions.h>
812 #include <unity/scopes/Variant.h>
813+#include <unity/scopes/Registry.h>
814
815 #include <QDebug>
816 #include <QString>
817@@ -37,6 +38,8 @@
818 int get_cardinality_setting(unity::scopes::VariantMap const& settings, std::vector<int> const& setting_cardinalities);
819 QString qstr(std::string const& str) const;
820 std::string sstr(QString const& str) const;
821+ std::vector<std::string> get_current_feeders(std::vector<std::string> const& feeders_in, unity::scopes::ChildScopeList const& current);
822+ std::string get_featured_feeder(std::string const& featured_id, std::vector<std::string> const& feeders, std::string const& cache_dir);
823 };
824
825 #endif
826
827=== modified file 'include/query.h'
828--- include/query.h 2016-09-29 18:42:15 +0000
829+++ include/query.h 2016-11-10 18:47:16 +0000
830@@ -35,6 +35,16 @@
831
832 #include <QJsonObject>
833
834+struct featured {
835+ std::string id;
836+ std::string title; //or use titles (below)
837+ std::string dept_id;
838+ std::string renderer;
839+ bool link_to_child = false;
840+ std::vector<std::string> scopes; // id and local_id of feeder scopes
841+ std::map<std::string,std::string> titles; //scope id: category title
842+};
843+
844 struct category {
845 std::string id;
846 std::string title;
847@@ -128,6 +138,9 @@
848 void handle_category_child(unity::scopes::SearchReplyProxy const& upstream_reply,
849 std::shared_ptr<AggChildScope> scope,
850 unity::scopes::CategorisedResult & res);
851+ void handle_featured_child(unity::scopes::SearchReplyProxy const& upstream_reply,
852+ std::shared_ptr<AggChildScope> scope,
853+ unity::scopes::CategorisedResult & res);
854 void handle_declared_child(unity::scopes::SearchReplyProxy const& upstream_reply,
855 std::shared_ptr<AggChildScope> scope,
856 unity::scopes::CategorisedResult & res,
857@@ -136,15 +149,20 @@
858 bool is_login_case(std::shared_ptr<AggChildScope> scope, std::string const& res_cat_id);
859 void make_categories();
860 void make_declared_scopes();
861+ void make_featured_scopes();
862 std::vector<std::shared_ptr<category>> categories;
863 us::ChildScopeList current_child_scopes;
864 std::map<std::string, std::shared_ptr<AggChildScope>> scopes_m;//key is local_id
865 bool is_using_departments();
866+ void set_scope_order();
867+ std::vector<std::string> scopes_ordered;// scope local id or keyword
868
869 private:
870+ std::vector<std::shared_ptr<featured>> featureds;
871+ std::map<std::string,std::map<std::string,std::string>> featured_id_scopeId_title;
872+
873 bool using_departments = false;
874 void make_keyword_scopes();
875- void set_scope_order();
876 void display_local_hints_quickstart(us::SearchReplyProxy const& upstream_reply_);
877 void display_remote_hints_quickstart(us::SearchReplyProxy const& upstream_reply_);
878 bool show_hints(us::SearchReplyProxy const& upstream_reply_);
879@@ -182,7 +200,6 @@
880 std::vector<std::string> current_scopes;// scope local_id
881 std::vector<std::string> current_scopeIds;// keyword cate scope ids to prevent dups
882 std::vector<std::string> declared_order;// scope local_id or keyword
883- std::vector<std::string> scopes_ordered;// scope local id or keyword
884 /*
885 for type_ids_m:
886 declared scope:
887@@ -196,7 +213,6 @@
888 vector: scope ids (vector size varies)
889 */
890 std::map<std::string,std::vector<std::string>> type_ids_m;//
891-
892 std::map<std::string,std::string> localId_id_m;//use scope's local_id to get its fully qualified id
893 std::string dept_id_of_root;
894 std::string dept_id_of_root_keywords_only;
895
896=== modified file 'include/scope.h'
897--- include/scope.h 2016-08-19 20:23:21 +0000
898+++ include/scope.h 2016-11-10 18:47:16 +0000
899@@ -33,7 +33,6 @@
900 class AggScope : public unity::scopes::ScopeBase
901 {
902 public:
903- enum class IdType {id, local_id};
904 AggScope();
905 virtual void start(std::string const& scope_name) override;
906 virtual void stop() override;
907@@ -54,7 +53,7 @@
908 void set_child_root(QJsonObject const& root);
909 void set_hints_root(QJsonObject const& root);
910
911- std::vector<std::string> get_declared_scopes(IdType) const;
912+ std::vector<std::string> get_declared_scopes() const;
913 std::vector<std::string> get_declared_keywords() const;
914
915 const std::string & scope_id() const;
916
917=== modified file 'src/aggchildscope.cpp'
918--- src/aggchildscope.cpp 2016-09-20 17:00:53 +0000
919+++ src/aggchildscope.cpp 2016-11-10 18:47:16 +0000
920@@ -26,6 +26,12 @@
921 {
922 }
923
924+void AggChildScope::set_id(std::string const &id)
925+{
926+ id_ = id;
927+}
928+
929+
930 void AggChildScope::set_local_id(std::string const &local_id)
931 {
932 local_id_ = local_id;
933@@ -289,6 +295,37 @@
934 return buffer_;
935 }
936
937+void AggChildScope::set_featured_scope(bool b)
938+{
939+ featured_scope_ = b;
940+}
941+
942+bool AggChildScope::featured_scope()
943+{
944+ return featured_scope_;
945+}
946+
947+void AggChildScope::set_linked_featured_id(std::string id)
948+{
949+ linked_featured_id_ = id;
950+}
951+
952+std::string AggChildScope::linked_featured_id()
953+{
954+ return linked_featured_id_;
955+}
956+
957+void AggChildScope::set_featured_feeders(std::vector<std::string> feeders)
958+{
959+ featured_feeders_ = feeders;
960+}
961+
962+std::vector<std::string> AggChildScope::featured_feeders()
963+{
964+ return featured_feeders_;
965+}
966+
967+
968 void AggChildScope::set_category_scope(bool b)
969 {
970 category_scope_ = b;
971
972=== modified file 'src/client.cpp'
973--- src/client.cpp 2016-08-19 20:46:32 +0000
974+++ src/client.cpp 2016-11-10 18:47:16 +0000
975@@ -19,6 +19,10 @@
976 #include "client.h"
977 #include <unity-scopes.h>
978
979+#include <fstream>
980+#include <iostream>
981+#include <sstream>
982+
983 #include <QJsonDocument>
984 #include <QJsonObject>
985 #include <QDebug>
986@@ -92,3 +96,87 @@
987 {
988 return QString::fromStdString(str);
989 }
990+
991+/**
992+ * Get the feeder scope IDs that are installed and enabled
993+ */
994+std::vector<std::string> Client::get_current_feeders(std::vector<std::string> const& feeders_in, us::ChildScopeList const& current_child_scopes)
995+{
996+
997+ std::vector<std::string> feeders;
998+ // remove scopes that are not current
999+ for (std::string const& f : feeders_in)
1000+ {
1001+ for (auto const& ascope : current_child_scopes)
1002+ {
1003+ if (ascope.id != f)
1004+ {
1005+ continue;
1006+ }
1007+ if (ascope.enabled)
1008+ {
1009+ feeders.emplace_back(ascope.id);
1010+ }
1011+ }
1012+ }
1013+ return feeders;
1014+
1015+}
1016+
1017+/**
1018+ * Get the scope id of the the next scope id to display in the featured category
1019+ * where we cycle through feeders scopes using the scope in the cache file to find the next.
1020+ * If cache file does not exists or has any other problem, use the first feeder scope.
1021+ * In all cases, truncate the cache file and write it with the new & current scope id.
1022+ */
1023+std::string Client::get_featured_feeder(std::string const& featured_id, std::vector<std::string> const& feeders, std::string const& cache_dir)
1024+{
1025+
1026+ std::string feeder;
1027+ std::string fpath = cache_dir + "/" + featured_id;
1028+ //if cache file does not exist, no previous, so use first
1029+ std::ifstream f(fpath);
1030+ if (f.good())
1031+ {
1032+ std::stringstream previous_stream;
1033+ std::string previous;
1034+ previous_stream << f.rdbuf();
1035+ previous = previous_stream.str();
1036+ std::vector<std::string>::const_iterator iter;
1037+ unsigned int idx = 0;
1038+ bool using_cached = true;
1039+ for (iter = feeders.begin() ; iter < feeders.end() ; iter++,idx++)
1040+ {
1041+ if (*iter == previous || !using_cached)
1042+ {
1043+
1044+ if (idx == feeders.size()-1)
1045+ {
1046+ feeder = feeders[0];
1047+ }
1048+ else
1049+ {
1050+ feeder = feeders[idx+1];
1051+ }
1052+ break;
1053+ }
1054+ }
1055+ }
1056+ if (feeder.empty())
1057+ {
1058+ feeder = feeders[0];
1059+ }
1060+ //write current feeder scope id to cache file
1061+ try
1062+ {
1063+ std::ofstream f2;
1064+ f2.open (fpath, std::ios::trunc);
1065+ f2 << feeder;
1066+ f2.close();
1067+ }
1068+ catch (const std::exception& e)
1069+ {
1070+ qDebug() <<"=== FEATURED. can't write cache file: " << QString::fromStdString(fpath);
1071+ }
1072+ return feeder;
1073+}
1074
1075=== modified file 'src/handle_results.cpp'
1076--- src/handle_results.cpp 2016-09-29 18:42:15 +0000
1077+++ src/handle_results.cpp 2016-11-10 18:47:16 +0000
1078@@ -759,7 +759,7 @@
1079 bool login_case = is_login_case(scope, res.category()->id());
1080 qDebug() << "==== DECLARED. login_case: " << QString::number(login_case);
1081 bool link = scope->category_link_to_child();//this means the category links, not that we get a specific child scope's category
1082- std:: string link_to = scope->id();
1083+ std::string link_to = scope->id();
1084 bool has_rdr = !scope->surface_template().empty();
1085
1086 // Determine the current case based on state
1087@@ -958,3 +958,115 @@
1088 }
1089
1090 }
1091+
1092+void Query::handle_featured_child(unity::scopes::SearchReplyProxy const& upstream_reply,
1093+ std::shared_ptr<AggChildScope> scope,
1094+ us::CategorisedResult & res ){
1095+
1096+ //the supported cases
1097+ enum RunType {
1098+ Surface,
1099+ SurfaceNoRdr,
1100+ SurfaceNoLink,
1101+ SurfaceNoLinkNoRdr
1102+ };
1103+ RunType runType;
1104+
1105+ //get state
1106+ qDebug() << "==== handle FEATURED";
1107+ bool link = scope->category_link_to_child();//this means the category links, not that we get a specific child scope's category
1108+ qDebug() << "==== handle FEATURED. link: " << QString::number(link);
1109+ std:: string link_to = scope->id();
1110+ bool has_rdr = !scope->surface_template().empty();
1111+
1112+ qDebug() << "==== handle FEATUREDi. link_to:" << client.qstr(link_to);
1113+
1114+ // Determine the current case based on state
1115+ if (!query().query_string().empty())
1116+ {
1117+ //do nothing
1118+ }
1119+ else
1120+ {//surface
1121+ if (link)
1122+ {
1123+ if (has_rdr)
1124+ {
1125+ runType = Surface;
1126+ }
1127+ else
1128+ {
1129+ runType = SurfaceNoRdr;
1130+ }
1131+ }
1132+ else
1133+ { // no link
1134+ if (has_rdr)
1135+ {
1136+ runType = SurfaceNoLink;
1137+ }
1138+ else
1139+ {
1140+ runType = SurfaceNoLinkNoRdr;
1141+ }
1142+ }
1143+ }
1144+
1145+ // for use in category registration
1146+ std::string cat_id;
1147+ std::string cat_title;
1148+ std::string rdr;
1149+
1150+ if (!scope->category_title().empty())
1151+ {
1152+ cat_title = scope->category_title();
1153+ }
1154+ else //uses per scope category title declarations
1155+ {
1156+ cat_title = featured_id_scopeId_title[scope->local_id()][scope->id()];
1157+ }
1158+
1159+ switch (runType)
1160+ {
1161+ case RunType::Surface:
1162+ qDebug() << "=== run type: featured scope Surface";
1163+ cat_id = scope->local_id() + ":featured:surface";
1164+ rdr = scope->surface_template();
1165+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
1166+ break;
1167+ case RunType::SurfaceNoRdr:
1168+ qDebug() << "=== run type: featured scope SurfaceNoRdr";
1169+ cat_id = scope->local_id() + ":featured:no-rdr:surface";
1170+ rdr = res.category()->renderer_template().data();
1171+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
1172+ break;
1173+ case RunType::SurfaceNoLink:
1174+ qDebug() << "=== run type: featured scope SurfaceNoLink";
1175+ cat_id = scope->local_id() + ":featuredr:no-link:surface";
1176+ rdr = scope->surface_template();
1177+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
1178+ break;
1179+ case RunType::SurfaceNoLinkNoRdr:
1180+ qDebug() << "=== run type: featured scope SurfaceNoLinkNoRdr";
1181+ cat_id = scope->local_id() + ":featured:no-link:no-rdr:surface";
1182+ rdr = res.category()->renderer_template().data();
1183+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
1184+ break;
1185+ }
1186+
1187+ //TODO: put FALLBACK into method to avoid duplication needed by refactor
1188+ // perform res component fallback, as needed
1189+ if(!scope->surface_common_template_id().empty())
1190+ {
1191+ std::string res_str = us::Variant(res.serialize()).serialize_json();
1192+ auto change = this->client.check_result_fallbacks(res_str,
1193+ scope->surface_common_template_id(),
1194+ this->get_common_templates_fallbacks());
1195+ if (!std::get<0>(change).empty())
1196+ {
1197+ res[std::get<0>(change)] = std::get<1>(change);
1198+ qDebug() << "=== FEATURED handle. fallback change field:" << client.qstr(std::get<0>(change));
1199+ qDebug() << "=== FEATURED handle. fallback change value:" << client.qstr(std::get<1>(change));
1200+ }
1201+ }
1202+}
1203
1204=== modified file 'src/query.cpp'
1205--- src/query.cpp 2016-09-29 18:42:15 +0000
1206+++ src/query.cpp 2016-11-10 18:47:16 +0000
1207@@ -311,6 +311,10 @@
1208
1209 // create child scope instances from keywords and scopes
1210 make_categories();
1211+
1212+ // create child scope instances from featureds, only when not searching
1213+ if (empty_search)
1214+ make_featured_scopes();
1215
1216 // complete initializations including cutting child scopes to those present and enabled
1217 complete_child_scopes();
1218@@ -554,6 +558,11 @@
1219 {
1220 handle_category_child(upstream_reply, scope, res);
1221 }
1222+ else if (scope->featured_scope())
1223+ {
1224+ qDebug() << "==== handle_ is FEATURED scope: " << client.qstr(scope->id());
1225+ handle_featured_child(upstream_reply, scope, res);
1226+ }
1227 else // is a declared scope
1228 {
1229 handle_declared_child(upstream_reply, scope, res, inc_res_cat_id, inc_res_cat_title);
1230
1231=== modified file 'src/scope.cpp'
1232--- src/scope.cpp 2016-08-19 20:23:21 +0000
1233+++ src/scope.cpp 2016-11-10 18:47:16 +0000
1234@@ -27,10 +27,7 @@
1235 #include <sstream>
1236 #include <unity-scopes.h>
1237
1238-
1239 #include <QDebug>
1240-#include <QFile>
1241-#include <QFile>
1242 #include <QJsonParseError>
1243 #include <QJsonDocument>
1244 #include <QJsonObject>
1245@@ -158,7 +155,7 @@
1246 return preview;
1247 }
1248
1249-std::vector<std::string> AggScope::get_declared_scopes(IdType type) const
1250+std::vector<std::string> AggScope::get_declared_scopes() const
1251 {
1252 std::vector<std::string> declared_scopes;
1253
1254@@ -171,16 +168,8 @@
1255 if (item_o.contains(QStringLiteral("scope")))
1256 {
1257 QJsonObject scope_o = item_o[QStringLiteral("scope")].toObject();
1258- if (type == IdType::local_id)
1259- {
1260- std::string local_id = scope_o[QStringLiteral("local_id")].toString().toStdString();
1261- declared_scopes.emplace_back(local_id);
1262- }
1263- else
1264- {
1265- std::string id = scope_o[QStringLiteral("id")].toString().toStdString();
1266- declared_scopes.emplace_back(id);
1267- }
1268+ std::string id = scope_o[QStringLiteral("id")].toString().toStdString();
1269+ declared_scopes.emplace_back(id);
1270 }
1271 else if (item_o.contains(QStringLiteral("category")))
1272 {
1273@@ -191,16 +180,8 @@
1274 for (const auto &item : scopes_a)
1275 {
1276 QJsonObject scope_o = item.toObject();
1277- if (type == IdType::local_id)
1278- {
1279- std::string local_id = scope_o[QStringLiteral("local_id")].toString().toStdString();
1280- declared_scopes.emplace_back(local_id);
1281- }
1282- else
1283- {
1284- std::string id = scope_o[QStringLiteral("id")].toString().toStdString();
1285- declared_scopes.emplace_back(id);
1286- }
1287+ std::string id = scope_o[QStringLiteral("id")].toString().toStdString();
1288+ declared_scopes.emplace_back(id);
1289 }
1290 }
1291 }
1292@@ -249,7 +230,7 @@
1293
1294 us::ChildScopeList AggScope::find_child_scopes() const
1295 {
1296- std::vector<std::string> declared_scopes = get_declared_scopes(IdType::id);
1297+ std::vector<std::string> declared_scopes = get_declared_scopes();
1298
1299 us::ChildScopeList list;
1300 std::map<std::string,us::ScopeMetadata> declared_registered_scopes;
1301
1302=== modified file 'src/utils.cpp'
1303--- src/utils.cpp 2016-09-28 21:01:43 +0000
1304+++ src/utils.cpp 2016-11-10 18:47:16 +0000
1305@@ -140,8 +140,52 @@
1306 for (const auto & item_o_ : order_a)
1307 {
1308 QJsonObject item_o = item_o_.toObject();
1309-
1310- if (item_o.contains(QStringLiteral("category")))
1311+ if (item_o.contains(QStringLiteral("featured")))
1312+ {
1313+ qDebug() << "=== FEATURED. load json";
1314+ QJsonObject featured_o = item_o[QStringLiteral("featured")].toObject();
1315+ std::shared_ptr<featured> featured_ptr = std::make_shared<featured>();
1316+ featured_ptr->id = featured_o[QStringLiteral("id")].toString().toStdString();
1317+ declared_order.emplace_back(featured_ptr->id);
1318+ if (featured_o.contains(QStringLiteral("department")))
1319+ {
1320+ featured_ptr->dept_id = featured_o[QStringLiteral("department")].toString().toStdString();
1321+ }
1322+ featured_ptr->link_to_child = true;
1323+ if (featured_o.contains(QStringLiteral("link_to_child")))
1324+ {
1325+ if (featured_o[QStringLiteral("link_to_child")].toString() == FALSE)
1326+ featured_ptr->link_to_child = false;
1327+ }
1328+ if (featured_o.contains(QStringLiteral("renderer_common_id")))
1329+ {
1330+ std::string id = featured_o[QStringLiteral("renderer_common_id")].toString().toStdString();
1331+ featured_ptr->renderer = common_templates[id];
1332+ categoryId_surfaceRdrs_catId2commonId[featured_ptr->id] = id;
1333+ }
1334+ else
1335+ {
1336+ QJsonObject renderer_o = featured_o[QStringLiteral("renderer")].toObject();
1337+ QJsonDocument st_d(renderer_o);
1338+ featured_ptr->renderer = client.sstr(st_d.toJson());
1339+ }
1340+ QJsonArray scope_a = featured_o[QStringLiteral("pull_from")].toArray();
1341+ for (const auto item : scope_a)
1342+ {
1343+ QJsonObject scope_o = item.toObject();
1344+ featured_ptr->scopes.emplace_back(scope_o[QStringLiteral("id")].toString().toStdString());
1345+ if (scope_o.contains(QStringLiteral("_title")))
1346+ {
1347+ featured_ptr->titles[scope_o[QStringLiteral("id")].toString().toStdString()] = _(scope_o[QStringLiteral("_title")].toString().toStdString().c_str());
1348+ }
1349+ }
1350+ if (featured_o.contains(QStringLiteral("_title")))
1351+ {
1352+ featured_ptr->title = featured_o[QStringLiteral("_title")].toString().toStdString();
1353+ }
1354+ featureds.emplace_back(featured_ptr);
1355+ }
1356+ else if (item_o.contains(QStringLiteral("category")))
1357 {
1358 QJsonObject cat_o = item_o[QStringLiteral("category")].toObject();
1359 std::shared_ptr<category> cat_ptr = std::make_shared<category>();
1360@@ -621,7 +665,7 @@
1361 {
1362 for (std::string id : declared_order)
1363 {
1364- //qDebug() << "==== SET ORDER. DECLARED order id: " << client.qstr(id);
1365+ qDebug() << "==== SET ORDER. DECLARED order id: " << client.qstr(id);
1366 for (const auto & pr : type_ids_m)
1367 {
1368 if (pr.first == id)
1369@@ -674,7 +718,6 @@
1370 unorder_keyscopes.emplace_back(scope_id);
1371 }
1372 }
1373-
1374 scopes_ordered.insert(scopes_ordered.end(), order_keyscopes.begin(), order_keyscopes.end());
1375 scopes_ordered.insert(scopes_ordered.end(), unorder_keyscopes.begin(), unorder_keyscopes.end());
1376 }
1377@@ -696,11 +739,70 @@
1378 }
1379 }
1380
1381+void Query::make_featured_scopes()
1382+{
1383+ for (std::shared_ptr<featured> featured : featureds)
1384+ {
1385+ auto f_ptr = std::make_shared<AggChildScope>("temporary_id");
1386+ std::vector<std::string> feeders;
1387+ for (auto feeder : featured->scopes)
1388+ {
1389+ feeders.emplace_back(feeder);
1390+ }
1391+ if (feeders.size() == 0 )
1392+ {
1393+ continue;//this is a json config mistake
1394+ }
1395+
1396+ f_ptr->set_featured_feeders(client.get_current_feeders(feeders, current_child_scopes));
1397+
1398+ if (f_ptr->featured_feeders().size() == 0)
1399+ {
1400+ continue;//none of the featured feeders are installed and enabled
1401+ }
1402+ f_ptr->set_id(client.get_featured_feeder(featured->id, f_ptr->featured_feeders(), cache_dir_));
1403+ f_ptr->set_local_id(featured->id);
1404+ f_ptr->set_featured_scope(true);
1405+ f_ptr->set_cardinality(1);
1406+
1407+ if (!featured->title.empty())
1408+ {
1409+ f_ptr->set_category_title(_(featured->title.c_str()));
1410+ }
1411+ else if (featured->titles.size() > 0)
1412+ {//using per feeder scope category titles
1413+ std::map<std::string,std::string>::iterator iter;
1414+ for (iter = featured->titles.begin(); iter != featured->titles.end(); iter++)
1415+ {
1416+ featured_id_scopeId_title[featured->id][iter->first] = iter->second;
1417+ }
1418+ }
1419+ if (using_departments)
1420+ {
1421+ f_ptr->set_department(featured->dept_id);
1422+ }
1423+ f_ptr->set_surface_template(featured->renderer);
1424+
1425+ if (categoryId_surfaceRdrs_catId2commonId.find(featured->id) != categoryId_surfaceRdrs_catId2commonId.end())
1426+ {
1427+ f_ptr->set_surface_common_template_id(categoryId_surfaceRdrs_catId2commonId[featured->id]);
1428+ }
1429+ f_ptr->set_category_link_to_child(featured->link_to_child);
1430+
1431+ type_ids_m[f_ptr->local_id()].emplace_back(f_ptr->local_id());
1432+ localId_id_m[f_ptr->local_id()] = f_ptr->id();
1433+ scopes_m[f_ptr->local_id()] = f_ptr;
1434+ current_scopes.emplace_back(f_ptr->local_id());
1435+ qWarning () << QString("%1: ADDING FEATURED scope: %2")
1436+ .arg(client.qstr(agg_scope_->scope_id()), client.qstr(f_ptr->id()));
1437+ }
1438+}
1439+
1440+
1441 /*
1442 * makes the AggChildScope instances using values and options
1443 * declared in json, else default values from child_scope struct
1444 */
1445-
1446 void Query::make_declared_scopes()
1447 {
1448 //iterate declared child scopes

Subscribers

People subscribed via source and target branches