Merge lp:~knitzsche/scope-aggregator/featured into lp:scope-aggregator
- featured
- Merge into vivid-trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jin (community) | Approve | ||
Zhang Enwei (community) | Approve | ||
Gary.Wang | Pending | ||
Review via email: mp+309784@code.launchpad.net |
Commit message
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:/
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.
Jin (jindallo) wrote : | # |
Code looks good and verified pass on my local,
I approve.
- 205. By Kyle Nitzsche
-
fix typo in readme
- 206. By Kyle Nitzsche
-
supress log output
Preview Diff
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 |
LGTM except not suppress the log.
I verified on china-dashboard and news-china.