Merge lp:~knitzsche/scope-aggregator/refactor-handle-methods_add-login_remove-shared-category into lp:scope-aggregator

Proposed by Kyle Nitzsche
Status: Merged
Approved by: Kyle Nitzsche
Approved revision: 191
Merged at revision: 174
Proposed branch: lp:~knitzsche/scope-aggregator/refactor-handle-methods_add-login_remove-shared-category
Merge into: lp:scope-aggregator
Diff against target: 3725 lines (+1703/-1386)
15 files modified
DELETED_FEATURES.md (+1/-0)
README.md (+270/-286)
gtests/CMakeLists.txt (+4/-0)
gtests/MockRegistry.h (+7/-0)
gtests/scope_directory/login_1.json (+74/-0)
gtests/scope_directory/login_2.json (+73/-0)
gtests/scope_directory/login_3.json (+35/-0)
gtests/scope_test.cpp (+110/-3)
include/aggchildscope.h (+36/-1)
include/query.h (+16/-9)
src/CMakeLists.txt (+1/-0)
src/aggchildscope.cpp (+34/-1)
src/handle_results.cpp (+960/-0)
src/query.cpp (+2/-9)
src/utils.cpp (+80/-1077)
To merge this branch: bzr merge lp:~knitzsche/scope-aggregator/refactor-handle-methods_add-login_remove-shared-category
Reviewer Review Type Date Requested Status
Zhang Enwei (community) Approve
Gary.Wang Pending
Review via email: mp+307352@code.launchpad.net

Description of the change

For a proposed 4.13 release.

* Significant refactor of the three methods that handle results:
handle_category_child(), handle_keyword_child(), handle_declared_child(). These
were simply too hard to debug and understand, and they had redundant code to
lookup and, if needed, to register Categories. Each of the three methods is
refactored as follows: define an enum that states the possible run types for
the method: get state variables, analyze state and set run type, switch on run
type to select code. Category lookup and registration is now moved to new
set_result_category() method, which has two signatures depending on whether a
link to child is needed. The three methods (and related) are moved to a new
file for clarity: handle_results.cpp.

* Added support for a "login_renderer" and
"login_renderer_incoming_category_id" declarations in child_scopes.json. See
the updated README.md. I also added gtests for this.

* Removed "shared category" from "keywords". This feature is not used, I think.
I did not find it in all child_scopes.json files in clicks branch. It is mostly
redundant with a "category". That is, they both confine keywords to a Category
(although category type also can include scopes). We can reimplemented any
features that were supported only in keywords that are not supported in
categories if needed.

* Added Query::is_using_departments() method. And added gtesting for this.
Note: With this MP, an agg scope child_scopes.json file does not need a
"departments" object: it's absence is sufficient to signal no departments.

* Updated/rewrote README.md.

I tested this in Today, News, Nearby and Photos. It would be awesome if the .so
could be tested with our other aggregators!

To post a comment you must log in.
Revision history for this message
Kyle Nitzsche (knitzsche) wrote :

Note: I started doing this refactoring to help debug https://bugs.launchpad.net/canonical-devices-system-image/+bug/1622423. However, after the refactor that issue continues, and I suspect it may be a unity8 rendering issue (I am working with unity api team on this separately).

Revision history for this message
Zhang Enwei (zhangew401) wrote :

LGTM except some typos.
I verified on food, films, china-dashboard, news-china and baidu aggregator scopes.

review: Approve
Revision history for this message
Kyle Nitzsche (knitzsche) wrote :

thanks Enwei.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'DELETED_FEATURES.md'
2--- DELETED_FEATURES.md 1970-01-01 00:00:00 +0000
3+++ DELETED_FEATURES.md 2016-09-30 19:17:35 +0000
4@@ -0,0 +1,1 @@
5+4.12: keyword object with shared_shared category: just use category.
6
7=== modified file 'README.md'
8--- README.md 2016-08-04 01:11:41 +0000
9+++ README.md 2016-09-30 19:17:35 +0000
10@@ -1,65 +1,90 @@
11-# Scope aggregator introduction
12-
13-This is a *declarative* aggregator scope template. You declare aggregated scopes and keywords for inclusion in json files.
14-
15-Consistent with release tag 1.5.
16-
17-## No compilation
18+# Scope aggregator
19+
20+This is a *declarative* aggregator scope template. You declare aggregated scopes and keywords for inclusion in json files.
21
22 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.
23
24-## Including scopes
25-
26-You may declare:
27-
28- * *scopes* to include (called "declared scopes")
29- * *keywords* to aggregate scopes that advertise such keywords (called "declared keywords")
30- * *categories* that contain specific declared scopes and declared keywords (called "declared categories")
31-
32-## Order
33-
34-You declare scopes, keywords and categories in a specific *order* that dictates the overall order of display at run time.
35-
36-When declaring keywords and not using `shared_category` (which keeps its results together in a single category), the order it not entirely determinant. Therefore `shared_category` is recommended.
37-
38-This order is used whether or not you also declared departments. (A department is treated as a section of the overall order, so even though you see only part of the overall order, the part you see still adheres to the order)
39+Here are couple examples:
40+
41+ * Today Scope: https://launchpad.net/today-scope
42+ * News Scope: https://launchpad.net/news-scope
43+
44+## Declaring scopes and the overall order
45+
46+You may declare one or more:
47+
48+ * `scope`: includes a single scope
49+ * `category`: includes a set of scopes and keywords
50+ * `keyword`: includes child scopes that answer to the keywords
51+
52+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)
53+
54+The unity scope framework API provides Category objects. These are used to group together scope results. Scope-aggregator uses Categories as follows:
55+
56+ * `scope`: A single Category is created for each `scope`. It contains all results from the scope. The Category displays in the declared order.
57+ * `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.
58+ * `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.
59
60 ## Departments
61
62-You may declare departments. When doing so, every declared scope, declared keyword and declared category must be assigned to a department or it is not visible at run time.
63+The unity scope framework provides Department objects. When used, these are selectable by tapping the magnifying glass, which displays a tappable list of Departments.
64+
65+You may declare departments. When doing so, every declared `scope`, `category` and `keyword` must be assigned to a department or it is not visible at run time.
66+
67+The declared order applies with declared departments. A department is treated as a sub-section of the overall order, so even though you see only part of the overall order, the part you see still adheres to the order.
68+
69+Each Department requires a title and a unique ID that you use to place each `scope`, `category` and `keyword` in a department.
70+
71+One department is declared as the default root department (it is the one displayed before the user selects any other department).
72
73 ## Categories
74
75-A category is automatically created for each declared scope, declared category, and declared keyword (when using shared categories, see below).
76-
77-You can set various category items, including title, renderers, and, when a cagetegory contains a single child scope, whether tapping the category title switches focus to the child scope (called "link to child"). Note, enabling link to child on categories that contain multiple child scopes is not advisable because the category can only link to a single child but results from multiple child scopes are contained in the category.
78+As noted, a Category is automatically created for each `scope`, `category`, and for each scope answering to a `keyword`.
79+
80+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").
81+
82+By default, the link to child feature is enabled for `scope` and `keyword` categories. It can be disabled with `"link_to_child": "false"`.
83+
84+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"`.
85+
86+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
87+Headlines department.
88
89 ## Renderers
90
91-You can declare renderers to control display of results (of declared scopes, declared keywords and declared categories) for three cases:
92-
93- * surfacing: declare a `renderer` in place (or use a common renderer with `renderer_common_id`) to control result display for normal results
94- * searching: declare a `search_renderer` in place (or use a common renderer with `search_renderer_common_id`) a to control result display for results when the user is searching
95- * first result: declare a `first_result_renderer` in place (or use a common renderer with `first_result_renderer_common_id`) to provide a different display for the first result. For example, the first news result may have a large photo with overlay text and the rest may use a small photo. The rest of the results are displayed using the surfacing renderer. Note that there are cases where first result renderer is not supported, for example keyword scopes that do not use a shared category.
96-
97-In general, when no render is declared, the renderer is taken from the incoming result.
98-
99-For convenience, you may declare renderers once in `common_templates` and refer to them by id for reuse.
100+The unity scope framework has the concept of a layout renderer that controls the display of results. The renderer dictates what the displayed result consists of, that is, its "components" (for example a title and an art, or a title, a subtitle, and a mascot). Each component consist of the key (which dictates the type of component) and the name of the property in the result that contains the value to be used. For example, "art": "image", which means each result has an "art" attribute whose value is derived from the result field named "image".
101+
102+The renderer also dictates the layout (for example, large card with grid layout, or vertical-journal, etc.).
103+
104+You can declare renderers to control display as follows:
105+
106+ * Surfacing: declare a `renderer` in place (or use a common renderer with `renderer_common_id`) to control result display for normal results.
107+ * Login: declare a `login_renderer` with a `login_renderer_incoming_category_id` to control display of login results for the specified category_id. When declared, if the incoming result has the specified category, then ID the declared login renderer is used. This is not available for `keyword` declarations because one generally does not know the category ID for scopes that may answer to the keyword, and keyword scopes probably all use different category IDs for login results.
108+ * Searching: declare a `search_renderer` in place (or use a common renderer with `search_renderer_common_id`) a to control result display for results when the user is searching
109+ * First result: declare a `first_result_renderer` in place (or use a common renderer with `first_result_renderer_common_id`) to provide a different display for the first result. For example, the first news result may have a large photo with overlay text and the rest may use a small photo. The rest of the results are displayed using the surfacing renderer. This is only available for `category` declarations.
110+
111+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 or aggregator scope development.
112+
113+Typically you declare your renderers right inside your `scope`, `category` or `keyword` object.
114+
115+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.
116
117 ## Cardinality
118
119-Cardinality means the number of results returned and displayed by any child scope instance whether included as a declared scope, a declared keyword, or a declared category. For example, if a child scope has two instances and the cardinality is 2, four results are displayed.
120-
121-There are two apporaches to cardinality:
122-
123- * user settings: in this case, the user may use settings to to set cardinality, and the default cardinality is obtained from settings. This approach requires setttings.ini and matching declarations in child_scopes.json. (if they do not match the scope may not function.)
124- * declared for each declared scope, declared keyworrd and declared category. This approach only requires child_scopes.json.
125-
126-You may use botth approachest simultaneously. If so, declared cardinality for declared scopes, keywords and categories preempt user settings.
127-
128-### Cardinality by user settings
129-
130-Suppose you want the user to choose between cardinality of 1,2,4, and 8, and you want the defaul to be 2. Here is your settings.ini:
131+Cardinality is the number of results returned and displayed by any single child scope instance. That is, the cardinality is sent to each child scope when asking for results to display, and the child scope follows this and returns no more than the requested number of result (it may return less).
132+
133+Note that in the case of a `category` that contains more than one scope, the overall maximum number of results displayed is the cardinality multiplied by the number of child scopes in the `categoory`.
134+
135+There are two approaches to implementing cardinality:
136+
137+ * Scope settings: in this case, you implement the cardinality choices for scope settings, including the default. This allows user cardinality choice. This approach requires a settings.ini file and matching declarations in child_scopes.json. (if they do not match the scope may not function.)
138+ * Declared for each `scope`, `category` and `keyword`.
139+
140+You may use both approaches simultaneously. If so, declared cardinality for `scope`, `category` and `keyword` preempts settings.
141+
142+### Cardinality with scope settings
143+
144+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:
145
146 [cardinality]
147 type = list
148@@ -67,9 +92,9 @@
149 displayValues = 1,2,4,8
150 displayName = Results per source:
151
152-Note that the defaultValue is an *index*, thus a value of 0 would set the default to 1.
153+Note that the defaultValue is an *index* starting at 0, thus a value of 1 would set the default to 2.
154
155-With this setttings.ini you *must* declare the same values in child_scopes.json as follows:
156+With this settings.ini you *must* declare the same values in child_scopes.json as follows:
157
158 "cardinality_settings":
159 [
160@@ -87,12 +112,10 @@
161 }
162 ],
163
164-Note that the default value is set in settings.ini and is not mentioned elsewhere.
165
166 ### Cardinality for any declared scope, keyword and category
167
168-When not using user settings for cardinality you can set cardinalities for any declared scope, keyword and category. Any not set use a default value.
169-
170+You can set cardinalities for any `scope`, `category` and `keyword`. This locally preempts any settings cardinality.
171
172 "scope|keyword|category":
173 {
174@@ -100,23 +123,70 @@
175 "cardinality": 3,
176
177
178-## Declared keyword shared categories
179-
180-When you declare a keyword, you can optinally set it to display all results from all matching child scopes in a *single* category. This is a called a "shared category". If you do not, each matching child scope's results appear in a category unique to the child scope and the overall order of child scopes is less determinant. Therefore, if you choose not to use a shared category, you might consider placing the keyword in a department that contains nothing else, thus keeping the categories together.
181-
182-## Keyword results should use common template
183-
184-All keyword sccopes that advertise a particular keyword should provide results using a the same template (for example, title, art and summary, or title, subtitle and mascot) because the aggregator generally assumes such a template provides a renderer to display it. If any aggregated scope does not use the expected template, its results may be missing expected template data at run time when aggregated. Note: This is a social convention: child scope developers who add a keyword to their scope should check what the expected template is for that keyword.
185+## Fallbacks and Result Attribute Swaps
186+
187+Sometime child scopes provide results that contain the pieces of information the renderer needs, but they are not arranged as expected.
188+
189+For example, a scope might provide a news result with the url to the art in the "mascot" attribute, yet your aggegrator scope may expect the art to be in the "art" attribute.
190+
191+There are two methods for dealing with this:
192+
193+ * Fallbacks
194+ * Attribute swaps
195+
196+### Fallbacks
197+
198+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.
199+
200+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).
201+
202+This example accomplishes the above goal:
203+
204+ "common_templates":
205+ [
206+ {
207+ "id" : "news-first",
208+ "fallbacks":
209+ [
210+ {
211+ "key": "art",
212+ "fields":
213+ [
214+ {"field": "art"},
215+ {"field": "mascot"},
216+ {"field": "emblem"}
217+ ]
218+ }
219+ ],
220+ "template":
221+ {
222+ [...]
223+ }
224+ },
225+ [...]
226+ }
227+
228+
229+### Result Attribute Swaps
230+
231+For `scope` types, you may also declare a specific attribute swaps to make on every incoming result. For example, if you know the child scope puts the art url in the mascot field, you can declare that the "art" attribute should be populated by the incoming "mascot" field.
232+
233+See `swap_result_attributes`.
234
235 # Declarations in child_scopes.json
236
237-All declarations are made `data/child_scopes.json`. This json file is divided into the following objects:
238+All declarations are made `child_scopes.json`. This json file is divided into the following objects:
239
240 * `departments`: Here you declared your departments or declare you do not want departments
241-* `order`: Here you declare your specific scopes, keywords and cagtegories for aggregation
242-* `commmon_templates`: Here you declare category renderer tempalates you may use for multiple specified categories
243-
244-## `departments` (required)
245+* `cardinality`: Here you declared cardinality options for use with scope settings.
246+* `order`: Here you declare your specific scopes, keywords and categories for aggregation
247+* `commmon_templates`: Here you declare category renderer templates you may use for multiple specified categories
248+
249+The order of these objects in the file has no affect.
250+
251+## `departments` (optional)
252+
253+When not present, departments are not used.
254
255 Structure:
256
257@@ -146,7 +216,7 @@
258
259 Value: string. "true" or "false"
260
261-When this key is present and the value is "true" departments are not used (even if you separately declare departments). When not using departments, all current enabled scopes display in surfacing mode in the declared order.
262+When this key is present and the value is "true" departments are not used (even if you separately declare departments). When not using departments, all current enabled scopes display in surfacing mode in the declared order. This is useful during development. As noted, when the `departments` object is not present, objects are not used.
263
264 ### `declarations` (required)
265
266@@ -170,7 +240,7 @@
267
268 Value: string
269
270-The department title. If you include the value as a msgid in your po/mo files, the title displays localized as avaiable.
271+The department title. If you include the value as a msgid in your po/mo files, the title displays localized as available.
272
273 ### Examples
274
275@@ -212,7 +282,7 @@
276
277 ## `order` (required)
278
279-Here you declare scopes, keywords and categories, with options for each. The order of declaration determines the order of display at runtime, although not all scopes always display. (For example, if you are using departments, only the scopes in thee current department are displayed, but they still display in the declared order.)
280+Here you declare `scope`s, `keywords` and `category` objects, with options for each. The order of declaration determines the order of display at runtime, as described above.
281
282 {
283 [...]
284@@ -281,13 +351,13 @@
285
286 The id is a fully qualified scope name.
287
288-If you include only this with no further declared customization the child scope is aggregrated as is with a tappable category navigation link to child.
289+If you include only this with no further declared customization the child scope is aggregated as is with a tappable category navigation link to child.
290
291 #### `local_id` key (required)
292
293 Value: string
294
295-Must be unique in this file. This diffferentiates scopes and allows multiple instances of the same scope, for example to include the scope several times using `child_department` to pull different results from it.
296+Must be unique in this file. This differentiates scopes and allows multiple instances of the same scope, for example to include the scope several times using `child_department` to pull different results from it.
297
298 #### `department` (required when using departments)
299
300@@ -321,24 +391,6 @@
301
302 When this key exists, the category title is set from the first incoming child result that matches the various declared criteria.
303
304-#### `first_result_renderer_common_id` key (optional)
305-
306-Value: string
307-
308-The id must equal a template defined in `common_templates`.
309-
310-Creates a new category specifically for the first result using the refered to renderer.
311-
312-Only used if `_shared_category` present.
313-
314-#### `first_result_renderer` key (optional)
315-
316-Value: json defining a category renderer
317-
318-Only used if `_shared_category` present.
319-
320-Creates a new category specifically for the first result using the defined renderer.
321-
322 #### `renderer_common_id` key (optional)
323
324 Value: string
325@@ -347,14 +399,10 @@
326
327 Uses the renderer referred to and defined in `common_templates` for all results (except for the first result if a renderer is provided for that).
328
329-Only used if `_shared_category` present.
330-
331 #### `renderer` key (optional)
332
333 Value: json defining a category renderer
334
335-Only used if `_shared_category` present.
336-
337 This provides the CategoryRenderer template use for results in this category. When this is not present, the template used is taken from the first result of the keyword scopes.
338
339 #### `search_renderer_common_id` key (optional)
340@@ -365,48 +413,46 @@
341
342 Uses the renderer referred to and defined in `common_templates` for all results when searching.
343
344-Only used if `_shared_category` present.
345-
346 #### `search_renderer` key (optional)
347
348 Value: json defining a category renderer
349
350-Only used if `_shared_category` present.
351-
352 This provides the CategoryRenderer to use when searching.
353
354 #### `child_department` key (optional)
355
356+DEPRECATED. Use keywords in the child scope and here instead. There is no way to determine whether a child scope has a particular department at run time, and if you use this key and the specified department does not exists, there is an uncaught exception in the child scope that causes problems.
357+
358 Value: string: a department id of the child scope.
359
360 Displays only results from the specified child department.
361
362 Note: To limit the number of results displayed when using `child_department`, use `cardinality`.
363
364-Note: Because scope user searches by design only return matching results onnly from the top level department, searches in the aggregator scope will not return results when this is used.
365+Note: Because scope user searches by design return matching results only from the top level department, searches in the aggregator scope will not return results when this is used.
366
367 #### `child_category` key (optional) DEPRECATED
368
369+DEPRECATED. Use keywords, a much more robust approach.
370+
371 Value: string
372
373-When present, only child results from the specified child catagory are displayed, and you may also set the maximum number of child results using `child_category_max_results` key.
374+When present, only child results from the specified child category are displayed, and you may also set the maximum number of child results using `child_category_max_results` key.
375
376 Note: To limit the number of results displayed when using `child_category`, use `child_category_max_results`.
377
378-Note: If you also use `result_category_id_to_common_template`, results from that specified category *are* displayed (that is, they are not filterd out) using that specified common template (as documented).
379-
380-This is deprecated becuase keywords are a more robust approach.
381+Note: If you also use `result_category_id_to_common_template`, results from that specified category *are* displayed (that is, they are not filtered out) using that specified common template (as documented).
382
383 #### `child_category_max_results` key (optional) DEPRECATED
384
385+DEPRECATED along with `child_category`.
386+
387 Value: number
388
389 Only application when using `child_category`. (See `cardinality`.)
390
391 Sets the maximum number of results to display.
392
393-Deprecated along with `child_category`.
394-
395 #### `link_to_child` key (optional)
396
397 Value: string: "false"
398@@ -415,13 +461,13 @@
399
400 Note: When the category title is empty, this should not be set to "false" due to layout issues.
401
402-Note: When `"link_to_child": "true"` (either due to explicit declaration or when using the default value which is "true"), the scope framework does not currently support `"collapsed-rows": NUMBER` in the renderer template. When link_to_child is on, `"cardinality": NUMBER` may not appear to be respected if that number causes more results than display in the available vertical space for the child scope, although it is respected and does limit the number of results.
403+Note: When `"link_to_child": "true"` (either due to explicit declaration or when using the default value which is "true"), the scope framework does not currently support `"collapsed-rows": NUMBER` in the renderer template. When link_to_child is on, `"cardinality": NUMBER` may not appear to be respected if that number causes more results than display in the available vertical space for the child scope, although it is respected and does limit the number of results. (Note: this needs to be tested)
404
405 #### `swap_result_attributes` key (optional)
406
407 Value: json list of objects.
408
409-This allows you to move values among attibutes in the result.
410+This allows you to move values among attributes in the result.
411
412 Example:
413
414@@ -459,9 +505,7 @@
415
416 Value: json list of objects.
417
418-Allows you to display results with a specified category using a CategoryRenderer declared in `common_templates`. Useful for child scopes requiring login to use the same design for all login results from all child scopes.
419-
420-Note: When also `child_category`, results using the specified category id(s) do display (are not filtered out), using the specified template.
421+Allows you to display results with a specified category using a CategoryRenderer declared in `common_templates`.
422
423 [...]
424 "result_category_id_to_common_template":
425@@ -473,13 +517,15 @@
426 ]
427 [...]
428
429+Note: This has often been used for a common layout for login results from different child scopes. As of scope aggregator release 4.12 you can use the new `login_renderer`/`login_renderer_incoming_category_id` pair of keys for directly declaring a renderer in your declared `scope`, or `category`.
430+
431 #### `keyword_scope_ids` key (optional)
432
433 Value: json list of objects.
434
435 This key is only used on the special clickstore scope declared with "id": "com.canonical.scopes.clickstore".
436
437-This clickstore scope displays scopes that are not installed and allows the user to install them.
438+This clickstore scope can display scopes that are declared but are not installed and allows the user to install them.
439
440 By default, the clickstore scope (when declared) displays all child scopes declared in the child_scopes.json file by scope ID that are not currently installed.
441
442@@ -530,128 +576,21 @@
443 }
444 [...]
445
446-### `keyword` (optional)
447-
448-Declare a specific keyword for aggregation.
449-
450-The first result of aggregated child scopes is placed in the declared order. To keep all results in this order, use `shared_category`, which confines the results into a single category.
451-
452-When not using `shared_category`, each aggregated keyword scope appears in its own category whose title is the child scope display name.
453-
454-When using departments, you may assign these to a department with `department`. Or, use `_departmentt_title` to auto create a department for them.
455-
456-#### `keyword` key (required)
457-
458-Value: a keyword whose advertising scopes will be aggreagated in this position in the order.
459-
460-Defining two `keyword`s with identical values is not supported at this time.
461-
462-#### `department` key (optional)
463-
464-Value: string
465-
466-If present, places the keyword aggegrateed scopes into the department. The department must be declared in `departments`.
467-
468-#### `_department_title` key DEPRECATED
469-
470-Value: string
471-
472-When you have not assigned the keyword to a department, you create a department for this keyword automatically by declaring this key.
473-
474-Do not declare this key when you have assigned the keyword to a department.
475-
476-**Note**: If the value is a msgid in your po files, the department title is localized according to po files.
477-
478-This is deprecated because it is easy to declare a department and assign the keyword to it.
479-
480-#### `shared_category` key (optional)
481-
482-Value: string, "true" or "false"
483-
484-When "true", all results from all scopes aggregated by this keyword are displayed in one category.
485-
486-#### `_shared_category_title` key (optional)
487-
488-Value: string
489-
490-Only used if `_shared_category_title` present.
491-
492-String that will be used (localized) for the category title.
493-
494-### Renderers
495-
496-These are identical to those described for `scope`. See that section.
497-
498-### Examples
499-
500-Place all "sports" scopes in a certain position in the order:
501-
502- {
503- "order":
504- [
505- [...]
506- {
507- "keyword":
508- {
509- "keyword": "sports"
510- }
511- },
512- [...]
513- ]
514- }
515-
516-Example. Place all "sports" scopes in a certain position in the order and in a specific department:
517-
518- {
519- "order":
520- [
521- [...]
522- {
523- "keyword":
524- {
525- "keyword": "sports",
526- "department": "dept-sports"
527- }
528- },
529- [...]
530- ]
531- }
532-
533-Example: Place all "news" keyword scopes in a certain position in the order and in a deparmentt. Use a single shared category for all such results, providing the category title, and renderers (by reference to `common_templates` for first result, the result of the normal results, and search results:
534-
535- {
536- "order":
537- [
538- [...]
539- {
540- "keyword":
541- {
542- "keyword": "news",
543- "department": "dept-news",
544- "shared_category": "true",
545- "_shared_category_title": "News Headlines",
546- "first_result_renderer_common_id": "renderer-news-first-result",
547- "category_renderer_common_id": "renderer-news",
548- "search_renderer_common_id": "renderer-news-search",
549- }
550- },
551- [...]
552- ]
553- }
554-
555-### `category`
556+### `category` (optional)
557
558 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.
559
560-You can place the category in a department and define the renderers for it (first result, normal, and search) in place or by referent to `common_templates`.
561+You can place the category in a department and define the renderers for it (normal, first result, and search) in place or by reference to `common_templates`.
562
563-When not using user settings for cardinality, you can set it.
564+You can declare cardinality that overrides scope settings for cardinality, if used.
565
566 Each scope requires an `id` and a `local_id` (as described in `scope` material. It may optionally have a `child_department` that specifies to show only results from that.
567
568 Each keyword also requires an `id` and a `local_id`.
569
570-Example using all currently supported keys:
571+You may declare `"link_to_child_specified": "SCOPE_ID"` to provide a tappable link to the child scope on the Category header.
572+
573+Example:
574
575 "order":
576 [
577@@ -697,13 +636,103 @@
578 }
579 },
580
581+### `keyword` (optional)
582+
583+Declare a specific keyword for aggregation.
584+
585+The category for the first child scope that answers under the specified keyword is always placed in declared position in the overall order.
586+
587+If multiple child scopes reply under the specified keyword, a category is created for each child scope whose title is taken from the first such incoming result. The order of the second scope and later child scopes is not determinant: they are positioned in the over all order in the order in which they are received. Therefore, `keyword` may be useful as the last item declared in the `order` as a catch-all to pick up additional relevant content without affecting the declared order that precedes it. order of
588+
589+Note: To keep all results from all child scopes that answer a specific keyword together in a single category (thus preserving the overall order), use `category` with `keywords` instead.
590+
591+You can place the keyword scopes in a department and define the renderers for it (normal, first result, and search - but not login) in place or by reference to `common_templates`.
592+
593+You can declare cardinality that overrides scope settings for cardinality, if used.
594+
595+#### `keyword` key (required)
596+
597+Value: a keyword whose advertising scopes will be aggregated in this position in the order.
598+
599+This is the unique ID with file scope. `Defining two `keyword`s with identical values is not supported.
600+
601+#### `department` key (optional)
602+
603+Value: string
604+
605+If present, places the keyword aggregated scopes into the department. The department must be declared in `departments`.
606+
607+#### Renderers
608+
609+These are identical to those described for `scope` except `login_renderer` is not supported.
610+
611+### `exclude_scopes` key (optional)
612+
613+Value: json list of objects.
614+
615+Suppose you want some scopes which supports one certain keywords not to display results, you can specify scope id in the exclude_scopes and scope aggregator will not display results of specified scopes.
616+
617+Note: This key is only supposed to be declared in a `keywords` json object.
618+e.g. amazon supports 'books' keyword, but in most cases it returns something unrelated to 'books' keyword. we can get rid of its results in the way as following.
619+ "order":
620+
621+ [
622+ {
623+ "keyword":
624+ {
625+ "keyword": "books",
626+ "link_to_child" : "true",
627+ "exclude_scopes" :[
628+ {
629+ "id": "com.canonical.scopes.amazon"
630+ }
631+ ]
632+ }
633+ }
634+ ]
635+
636+
637+#### `display_order` key (optional)
638+
639+Value: json list of objects.
640+
641+display\_order allows developer to set child scopes display order of keyword scope. You can specify multiple scope id in the display\_order and scope aggregator will display child scopes according to the order you defined in display\_order.Installed keyword child scopes that are not listed in display_order are displayed after installed display_order scopes and in a indeterminate order.
642+
643+Note: `display_order` must be a direct child of a `keyword`.
644+e.g. the following scopes support 'bollywood.movie' keyword,
645+ 1.Shemaroo
646+ 2.Eros
647+ 3.ZeeTV
648+ 4.Star-plus
649+
650+if we define display_order as following, shemaroo scope will be shown in the first place, then eros at 2nd place.And zeeTV and star-plus will be shown randomly
651+ "order":
652+
653+ [
654+ {
655+ "keyword":
656+ {
657+ "keyword": "bollywood.movie",
658+ "link_to_child" : "true",
659+ "display_order" :[
660+ {
661+ "id": "com.canonical.scopes.shemaroo_shemaroo"
662+ },
663+ {
664+ "id": "com.canonical.scopes.eros_eros"
665+ }
666+ ]
667+ }
668+ }
669+ ]
670
671 ## `common_templates` key (optional)
672
673-Allows you to declare a category renderer once and use it multiple times. Useful for a common login result design. Each template requires a unique `id` and a `template`.
674+Allows you to declare a renderer once and use it multiple times. Each template requires a unique `id` and a `template`.
675+
676+
677
678 {
679- [...]
680 "common_templates":
681 [
682 {
683@@ -715,13 +744,18 @@
684 },
685 {
686 "id" : "renderer-news-first-result",
687+ "fallbacks:
688+ {
689+ [...]
690+ }
691 "template":
692 {
693 [...]
694 }
695 }
696 ],
697- ['...]
698+ "order":
699+ [...]
700 }
701
702 ### `id` (required)
703@@ -737,6 +771,9 @@
704 The `template` is the categoryRenderer template to use for categories whose id matches `id`.
705
706 {
707+ "departments":
708+ [...]
709+ "order":
710 [...]
711 "common_templates":
712 [
713@@ -762,21 +799,25 @@
714 [...]
715 }
716
717-
718-#### `default_query_string` key (optional)
719+### `fallbacks` (optional)
720+
721+See Fallbacks discussion.
722+
723+## Other Features
724+
725+### `default_query_string` key (optional)
726
727 Value: string: a query string passed to child scope
728
729 The key is useful for developer to display results with specific query string by default instead of results fetched with empty query string.
730
731-
732-#### `only_in_search` key (optional)
733+### `only_in_search` key (optional)
734
735 Value: string: "true" or "false", default value: "false"
736
737 Displays only results of specified scope on search, not in surface.
738
739-Note:If aggregator has multiple departments, in order to display results in current departments, you need to declare the scope with different deparment and local_id separately.
740+Note: If aggregator has multiple departments, in order to display results in current departments, you need to declare the scope with different department and local_id separately.
741
742 {
743 "departments":
744@@ -818,60 +859,3 @@
745 ]
746
747
748-#### `exclude_scopes` key (optional)
749-
750-Value: json list of objects.
751-
752-Suppose you want some scopes which supports one certain keywords not to display results, you can specify scope id in the exclude_scopes and scope aggregator will not display results of specified scopes.
753-
754-Note:This key is only supposed to be declared in a `keywords` json object, which doesn't not use a shared category.
755-e.g. amazon supports 'books' keyword, but in most cases it returns something unrelated to 'books' keyword. we can get rid of its results in the way as following.
756- "order":
757- [
758- {
759- "keyword":
760- {
761- "keyword": "books",
762- "link_to_child" : "true",
763- "exclude_scopes" :[
764- {
765- "id": "com.canonical.scopes.amazon"
766- }
767- ]
768- }
769- }
770- ]
771-
772-
773-#### `display_order` key (optional)
774-
775-Value: json list of objects.
776-
777-display_order allows developer to set child scopes display order of keyword scope. You can specify multiple scope id in the display_order and scope aggregator will display child scopes according to the order you defined in display_order.Installed keyword child scopes that are not listed in display_order are displayed after installed display_order scopes and in a indeterminate order.
778-
779-Note: `display_order` must be a direct child of a `keyword`.
780-e.g. the following scopes support 'bollywood.movie' keyword,
781- 1.Shemaroo
782- 2.Eros
783- 3.ZeeTV
784- 4.Star-plus
785-
786-if we define display_order as following, shemaroo scope will be shown in the first place, then eros at 2nd place.And zeeTV and star-plus will be shown randomly
787- "order":
788- [
789- {
790- "keyword":
791- {
792- "keyword": "bollywood.movie",
793- "link_to_child" : "true",
794- "display_order" :[
795- {
796- "id": "com.canonical.scopes.shemaroo_shemaroo"
797- },
798- {
799- "id": "com.canonical.scopes.eros_eros"
800- }
801- ]
802- }
803- }
804- ]
805
806=== modified file 'gtests/CMakeLists.txt'
807--- gtests/CMakeLists.txt 2016-08-19 20:20:58 +0000
808+++ gtests/CMakeLists.txt 2016-09-30 19:17:35 +0000
809@@ -34,6 +34,7 @@
810 ../src/resultforwarder.cpp
811 ../src/scope.cpp
812 ../src/utils.cpp
813+ ../src/handle_results.cpp
814 ../src/aggchildscope.cpp
815 ../src/listener.cpp
816 ../src/action.cpp
817@@ -72,6 +73,9 @@
818 COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/hints_local.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/hints_local.json"
819 COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/hints.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/hints.json"
820 COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/hints_remote.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/hints_remote.json"
821+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/login_1.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/login_1.json"
822+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/login_2.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/login_2.json"
823+ COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/scope_directory/login_3.json" "${CMAKE_CURRENT_BINARY_DIR}/scope_directory/login_3.json"
824 COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/cache_directory"
825 )
826
827
828=== modified file 'gtests/MockRegistry.h'
829--- gtests/MockRegistry.h 2016-08-17 16:17:03 +0000
830+++ gtests/MockRegistry.h 2016-09-30 19:17:35 +0000
831@@ -154,6 +154,13 @@
832 {
833 mm.insert(pair<string, ScopeMetadata>("full_keyword_3", full_keyword_3));
834 }
835+
836+ set<string> keywords_login = {"login-test"};
837+ auto login_metadata = build_scope_metadata("login-test", keywords_login);
838+ if (predicate(login_metadata))
839+ {
840+ mm.insert(pair<string, ScopeMetadata>("login-test", login_metadata));
841+ }
842 return mm;
843 }
844 };
845
846=== added file 'gtests/scope_directory/login_1.json'
847--- gtests/scope_directory/login_1.json 1970-01-01 00:00:00 +0000
848+++ gtests/scope_directory/login_1.json 2016-09-30 19:17:35 +0000
849@@ -0,0 +1,74 @@
850+{
851+ "common_templates":
852+ [
853+ {
854+ "id" : "default-renderer",
855+ "template":
856+ {
857+ "components":
858+ {
859+ "art": "art",
860+ "title": "title",
861+ "subtitle": "subtitle",
862+ "summary": "summary",
863+ "emblem": "emblem"
864+ },
865+ "schema-version": 1,
866+ "template":
867+ {
868+ "card-layout": "horizontal",
869+ "card-size": "large",
870+ "collapsed-rows": 0,
871+ "category-layout": "vertical-journal"
872+ }
873+ }
874+ }
875+ ],
876+ "order":
877+ [
878+ {
879+ "scope": {
880+ "id": "scope_login",
881+ "local_id": "local_scope_2",
882+ "renderer_common_id": "fallbacks"
883+ }
884+ },
885+ {
886+ "category":
887+ {
888+ "id": "headlines",
889+ "department": "dept-headlines",
890+ "_title": "Headlines",
891+ "renderer_common_id": "default-renderer",
892+ "login_renderer_incoming_category_id": "login-category",
893+ "login_renderer":
894+ {
895+ "components":
896+ {
897+ "mascot": "login-art",
898+ "title": "login-title"
899+ },
900+ "schema-version": 1,
901+ "template":
902+ {
903+ "card-layout": "horizontal",
904+ "card-size": "small",
905+ "collapsed-rows": 1,
906+ "category-layout": "grid"
907+ }
908+ },
909+ "scopes":
910+ [
911+ ],
912+ "keywords":
913+ [
914+ {
915+ "id": "login-test",
916+ "local_id": "login1-localid"
917+ }
918+
919+ ]
920+ }
921+ }
922+ ]
923+}
924
925=== added file 'gtests/scope_directory/login_2.json'
926--- gtests/scope_directory/login_2.json 1970-01-01 00:00:00 +0000
927+++ gtests/scope_directory/login_2.json 2016-09-30 19:17:35 +0000
928@@ -0,0 +1,73 @@
929+{
930+ "common_templates":
931+ [
932+ {
933+ "id" : "default-renderer",
934+ "template":
935+ {
936+ "components":
937+ {
938+ "art": "art",
939+ "title": "title",
940+ "subtitle": "subtitle",
941+ "summary": "summary",
942+ "emblem": "emblem"
943+ },
944+ "schema-version": 1,
945+ "template":
946+ {
947+ "card-layout": "horizontal",
948+ "card-size": "large",
949+ "collapsed-rows": 0,
950+ "category-layout": "vertical-journal"
951+ }
952+ }
953+ }
954+ ],
955+ "order":
956+ [
957+ {
958+ "scope": {
959+ "id": "scope_login",
960+ "local_id": "local_scope_2",
961+ "renderer_common_id": "fallbacks"
962+ }
963+ },
964+ {
965+ "category":
966+ {
967+ "id": "headlines",
968+ "department": "dept-headlines",
969+ "_title": "Headlines",
970+ "renderer_common_id": "default-renderer",
971+ "login_renderer_incoming_category_id": "login-category",
972+ "login_renderer":
973+ {
974+ "components":
975+ {
976+ "mascot": "login-art",
977+ "title": "login-title"
978+ },
979+ "schema-version": 1,
980+ "template":
981+ {
982+ "card-layout": "horizontal",
983+ "card-size": "small",
984+ "collapsed-rows": 1,
985+ "category-layout": "grid"
986+ }
987+ },
988+ "scopes":
989+ [
990+ {
991+ "id": "login-test",
992+ "local_id": "login1-localid"
993+ }
994+ ],
995+ "keywords":
996+ [
997+ ]
998+ }
999+ }
1000+ ]
1001+}
1002
1003=== added file 'gtests/scope_directory/login_3.json'
1004--- gtests/scope_directory/login_3.json 1970-01-01 00:00:00 +0000
1005+++ gtests/scope_directory/login_3.json 2016-09-30 19:17:35 +0000
1006@@ -0,0 +1,35 @@
1007+{
1008+ "order":
1009+ [
1010+ {
1011+ "scope": {
1012+ "_category_title": "Login Declared Scope Test",
1013+ "id": "login-test",
1014+ "local_id": "login1-localid",
1015+ "renderer": {
1016+ "components": {
1017+ "art": "art"
1018+ },
1019+ "schema-version": 1,
1020+ "template": {
1021+ "card-size": "small",
1022+ "category-layout": "grid",
1023+ "collapsed-rows": 10
1024+ }
1025+ },
1026+ "login_renderer_incoming_category_id": "login-category",
1027+ "login_renderer": {
1028+ "components": {
1029+ "mascot": "mascot",
1030+ "title": "title"
1031+ },
1032+ "schema-version": 1,
1033+ "template": {
1034+ "card-size": "large",
1035+ "category-layout": "grid"
1036+ }
1037+ }
1038+ }
1039+ }
1040+ ]
1041+}
1042
1043=== modified file 'gtests/scope_test.cpp'
1044--- gtests/scope_test.cpp 2016-08-19 20:46:32 +0000
1045+++ gtests/scope_test.cpp 2016-09-30 19:17:35 +0000
1046@@ -81,6 +81,7 @@
1047 QJsonArray depts_a = depts["declarations"].toArray();
1048 ASSERT_EQ(depts_a.size(), 3);
1049
1050+
1051 NiceMock<unity::scopes::testing::MockSearchReply> reply;
1052 unity::scopes::SearchReplyProxy search_reply_proxy
1053 {
1054@@ -97,6 +98,7 @@
1055
1056 Query * q = new Query(cq, smd, string("scope_directory"), string("cache_directory"), reg, scope_ptr);
1057 q->load_declarations(scope_ptr->child_root());
1058+ ASSERT_TRUE(q->is_using_departments());
1059 q->create_departments(search_reply_proxy);
1060
1061 ASSERT_TRUE(child_root.contains("cardinality_settings"));
1062@@ -105,8 +107,11 @@
1063 ASSERT_TRUE(child_root.contains("common_templates"));
1064 ASSERT_TRUE(child_root.contains("order"));
1065 }
1066+
1067+
1068 /**
1069 * Verifies that no call to register_departments() made when no depts declared in json
1070+ * and that is_using_departments() returns false
1071 */
1072 TEST_F(TestAggScope, verify_no_departments){
1073 AggScope * scope_ptr;
1074@@ -127,9 +132,6 @@
1075 {
1076 &reply, [](unity::scopes::SearchReply*) {}
1077 };
1078-
1079- //verify a call is made to register departments
1080- //EXPECT_CALL(reply, register_departments(_)).Times(0);
1081
1082 shared_ptr<MockRegistry> reg = make_shared<MockRegistry>();
1083 SearchMetadata smd("en_US", "phone");
1084@@ -139,6 +141,7 @@
1085 Query * q = new Query(cq, smd, string("scope_directory"), string("cache_directory"), reg, scope_ptr);
1086 q->load_declarations(scope_ptr->child_root());
1087 q->create_departments(search_reply_proxy);
1088+ ASSERT_FALSE(q->is_using_departments());
1089 }
1090
1091 /**
1092@@ -643,3 +646,107 @@
1093 //tests that hints DOES iNOT display when hints_is_hidden file exists in cachee dir
1094 ASSERT_FALSE(q->assert_hints_display((proxy)));
1095 }
1096+
1097+/*
1098+ * Verifies that a category keyword defined to use a login renderer uses it.
1099+ * Particularly, the return value of Query::is_login_case() method is verified as true
1100+ * when the incoming result category id is the same as the declared one, and false otherwise.
1101+ * As with many tests, the MockRegistry is configured to have a scope that answers to
1102+ * the keyword defined in the login_1.json file used here.
1103+ */
1104+TEST_F(TestAggScope, is_login_case_category_keyword){
1105+ AggScope * scope_ptr;
1106+ scope_ptr = &(*scope);
1107+ string fbj = "login_1.json";
1108+ scope_ptr->set_child_scopes_json_filename(fbj);
1109+ ASSERT_EQ(scope_ptr->child_scopes_json_filename(), fbj);
1110+ QJsonObject child_root = scope_ptr->get_child_root();
1111+ std::string path = scope_ptr->scope_directory() + "/" + scope_ptr->child_scopes_json_filename();
1112+ ASSERT_TRUE(scope_ptr->load_json_file(child_root, path, scope_ptr->scope_id())) << "Problem with: " << path;
1113+ scope_ptr->set_child_root(child_root);
1114+ SearchMetadata smd("en_US", "phone");
1115+ CannedQuery cq("not_an_id");
1116+ shared_ptr<MockRegistry> reg = make_shared<MockRegistry>();
1117+ Query * q = new Query(cq, smd, string("scope_directory"), string("cache_directory"), reg, scope_ptr);
1118+ q->load_declarations(scope_ptr->child_root());
1119+ ChildScopeList csl = scope->find_child_scopes();
1120+ ASSERT_EQ(csl.size(), 1);
1121+ ASSERT_EQ(csl.front().id, "login-test");
1122+ q->current_child_scopes = csl;
1123+ q->make_categories();
1124+ auto cat = q->categories[0];
1125+ auto child = q->scopes_m["login-test:category=headlines:keyword=login-test"];
1126+ bool login_case = q->is_login_case(child, "login-category");
1127+ ASSERT_TRUE(login_case);
1128+ login_case = q->is_login_case(child, "NOT-login-category");
1129+ ASSERT_FALSE(login_case);
1130+}
1131+
1132+/*
1133+ * Verifies that a category scope defined to use a login renderer uses it.
1134+ * Particularly, the return value of Query::is_login_case() method is verified as true
1135+ * when the incoming result category id is the same as the declared one, and false otherwise.
1136+ * As with many tests, the MockRegistry is configured to have a scope with the id
1137+ * defined in login_2.json used here.
1138+ */
1139+TEST_F(TestAggScope, is_login_case_category_scope){
1140+ AggScope * scope_ptr;
1141+ scope_ptr = &(*scope);
1142+ string fbj = "login_2.json";
1143+ scope_ptr->set_child_scopes_json_filename(fbj);
1144+ ASSERT_EQ(scope_ptr->child_scopes_json_filename(), fbj);
1145+ QJsonObject child_root = scope_ptr->get_child_root();
1146+ std::string path = scope_ptr->scope_directory() + "/" + scope_ptr->child_scopes_json_filename();
1147+ ASSERT_TRUE(scope_ptr->load_json_file(child_root, path, scope_ptr->scope_id())) << "Problem with: " << path;
1148+ scope_ptr->set_child_root(child_root);
1149+ SearchMetadata smd("en_US", "phone");
1150+ CannedQuery cq("not_an_id");
1151+ shared_ptr<MockRegistry> reg = make_shared<MockRegistry>();
1152+ Query * q = new Query(cq, smd, string("scope_directory"), string("cache_directory"), reg, scope_ptr);
1153+ q->load_declarations(scope_ptr->child_root());
1154+ ChildScopeList csl = scope->find_child_scopes();
1155+ ASSERT_EQ(csl.size(), 1);
1156+ ASSERT_EQ(csl.front().id, "login-test");
1157+ q->current_child_scopes = csl;
1158+ q->make_categories();
1159+ auto cat = q->categories[0];
1160+ auto child = q->scopes_m["login1-localid"];
1161+ bool login_case = q->is_login_case(child, "login-category");
1162+ ASSERT_TRUE(login_case);
1163+ login_case = q->is_login_case(child, "NOT-login-category");
1164+ ASSERT_FALSE(login_case);
1165+}
1166+
1167+/*
1168+ * Verifies that a declared scope defined to use a login renderer uses it.
1169+ * Particularly, the return value of Query::is_login_case() method is verified as true
1170+ * when the incoming result category id is the same as the declared one, and false otherwise.
1171+ * As with many tests, the MockRegistry is configured to have a scope with the id
1172+ * defined in login_2.json used here.
1173+ */
1174+TEST_F(TestAggScope, is_login_case_declared_scope){
1175+ AggScope * scope_ptr;
1176+ scope_ptr = &(*scope);
1177+ string fbj = "login_3.json";
1178+ scope_ptr->set_child_scopes_json_filename(fbj);
1179+ ASSERT_EQ(scope_ptr->child_scopes_json_filename(), fbj);
1180+ QJsonObject child_root = scope_ptr->get_child_root();
1181+ std::string path = scope_ptr->scope_directory() + "/" + scope_ptr->child_scopes_json_filename();
1182+ ASSERT_TRUE(scope_ptr->load_json_file(child_root, path, scope_ptr->scope_id())) << "Problem with: " << path;
1183+ scope_ptr->set_child_root(child_root);
1184+ SearchMetadata smd("en_US", "phone");
1185+ CannedQuery cq("not_an_id");
1186+ shared_ptr<MockRegistry> reg = make_shared<MockRegistry>();
1187+ Query * q = new Query(cq, smd, string("scope_directory"), string("cache_directory"), reg, scope_ptr);
1188+ q->load_declarations(scope_ptr->child_root());
1189+ ChildScopeList csl = scope->find_child_scopes();
1190+ ASSERT_EQ(csl.front().id, "login-test");
1191+ q->current_child_scopes = csl;
1192+ ASSERT_EQ(csl.size(), 1);
1193+ q->make_declared_scopes();
1194+ auto child = q->scopes_m["login1-localid"];
1195+ bool login_case = q->is_login_case(child, "login-category");
1196+ ASSERT_TRUE(login_case);
1197+ login_case = q->is_login_case(child, "NOT-login-category");
1198+ ASSERT_FALSE(login_case);
1199+}
1200
1201=== modified file 'include/aggchildscope.h'
1202--- include/aggchildscope.h 2016-06-29 10:29:37 +0000
1203+++ include/aggchildscope.h 2016-09-30 19:17:35 +0000
1204@@ -146,6 +146,37 @@
1205 us::Category::SCPtr category();
1206
1207 /**
1208+ \brief Whether scope uses login renderer
1209+ \return Return whether scope uses login renderer
1210+ */
1211+ bool using_login_renderer();
1212+
1213+ /**
1214+ \brief Enable using login renderer
1215+ */
1216+ void enable_using_login_renderer();
1217+
1218+ /**
1219+ \brief Set the incoming category id which triggers use a the login_renderer
1220+ */
1221+ void set_login_renderer_incoming_category_id(std::string const& cat_id);
1222+
1223+ /**
1224+ \brief Get the incoming category id which triggers use a the login_renderer
1225+ */
1226+ std::string login_renderer_incoming_category_id();
1227+
1228+ /**
1229+ \brief The json defining the CategoryRenderer json used when when incoming result has catgegory id == login_renderer_incoming_category_id.
1230+ */
1231+ void set_login_template(std::string const& login_template);
1232+
1233+ /**
1234+ \brief Return the json defining the CategoryRenderer template used for login when login_renderer_incoming_category_id set and current incoming result matches.
1235+ */
1236+ const std::string & login_template() const;
1237+
1238+ /**
1239 \brief The json defining the CategoryRenderer json used when surfacing the first result. You must also declare surface_template to handle the rest.
1240 */
1241 void set_first_result_template(std::string const &first_result_template);
1242@@ -154,8 +185,9 @@
1243 \brief Return the json defining the CategoryRenderer template used when surfacing first result.
1244 */
1245 const std::string & first_result_template() const;
1246+
1247 /**
1248- *
1249+ *
1250 \brief The json defining the CategoryRenderer template used when surfacing results.
1251 \param surface_template The json passed to the CategoryRenderer
1252 */
1253@@ -375,6 +407,8 @@
1254 std::string id_;
1255 std::string local_id_;
1256 std::string cat_title = "";
1257+ std::string login_renderer_incoming_category_id_;
1258+ bool using_login_renderer_ =false;
1259 bool using_category_link_to_child;
1260 bool using_category_title_incoming_ = false;
1261 bool using_category_title_display_name_ = false;
1262@@ -396,6 +430,7 @@
1263 us::Category::SCPtr cat;
1264 bool override_surface_template_ = true;
1265 bool override_search_template_ = true;
1266+ std::string template_login_;
1267 std::string TEMPLATE_NOT_LOGGED_IN;
1268 std::string template_first_result_;
1269 std::string first_common_template_id_;
1270
1271=== modified file 'include/query.h'
1272--- include/query.h 2016-08-19 17:04:10 +0000
1273+++ include/query.h 2016-09-30 19:17:35 +0000
1274@@ -44,6 +44,8 @@
1275 std::string category_link_to_child_specified;
1276 std::string renderer;
1277 std::string search_renderer;
1278+ std::string login_renderer;
1279+ std::string login_renderer_incoming_category_id;
1280 std::vector<std::pair<std::string,std::string>> scopes; // id and local_id of scopes
1281 std::vector<std::string> keywords; // keywords in this cat
1282 std::vector<std::pair<std::string,std::string>> keyword_ids; // id and local id of keywords
1283@@ -90,6 +92,8 @@
1284 bool using_child_category_max_results = false;//NOT A JSON KEY
1285 bool overriding_surface_template = false;//NOT A JSON KEY
1286 bool overriding_search_template = false;//NOT A JSON KEY
1287+ std::string login_renderer;
1288+ std::string login_renderer_incoming_category_id;
1289 std::vector<std::shared_ptr<std::pair<std::string,std::string>>> swap_result_attributes;
1290 std::pair<std::string,std::string> if_category_use_template;
1291 std::map<std::string, std::string> result_category_id_to_common_template;//id is result category id. maps to id to use in common_templates
1292@@ -110,7 +114,6 @@
1293
1294 virtual void run(unity::scopes::SearchReplyProxy const& upstream_reply) override;
1295
1296- bool using_departments = true;
1297
1298 Client client;
1299
1300@@ -130,26 +133,32 @@
1301 unity::scopes::CategorisedResult & res,
1302 std::string const& inc_res_cat_id,
1303 std::string const& inc_res_cat_title);
1304+ bool is_login_case(std::shared_ptr<AggChildScope> scope, std::string const& res_cat_id);
1305+ void make_categories();
1306+ void make_declared_scopes();
1307+ std::vector<std::shared_ptr<category>> categories;
1308+ us::ChildScopeList current_child_scopes;
1309+ std::map<std::string, std::shared_ptr<AggChildScope>> scopes_m;//key is local_id
1310+ bool is_using_departments();
1311+
1312 private:
1313- void make_declared_scopes();
1314+ bool using_departments = false;
1315 void make_keyword_scopes();
1316- void make_categories();
1317-
1318 void set_scope_order();
1319-
1320 void display_local_hints_quickstart(us::SearchReplyProxy const& upstream_reply_);
1321 void display_remote_hints_quickstart(us::SearchReplyProxy const& upstream_reply_);
1322 bool show_hints(us::SearchReplyProxy const& upstream_reply_);
1323 bool hints_exists();
1324 bool hints_hidden();
1325 void dismiss_hints_quickstart();
1326+ us::Category::SCPtr set_result_catgory(us::SearchReplyProxy const& upstream_reply, us::CategorisedResult & res, std::string const& id, std::string const& title, std::string const& rdr, std::string const& link_to);
1327+ us::Category::SCPtr set_result_catgory(us::SearchReplyProxy const& upstream_reply, us::CategorisedResult & res, std::string const& id, std::string const& title, std::string const& rdr);
1328
1329- void handle_current_child_scopes(bool empty_search, us::SearchReplyProxy const& upstream_reply);
1330+ void complete_child_scopes();
1331 void handle_child_scope_results(std::string const &query_string, bool empty_search, us::SearchReplyProxy const& upstream_reply);
1332 void noSources(unity::scopes::SearchReplyProxy const& reply);
1333 std::map<std::string,std::string> localId_defaultQryStr;
1334 std::map<std::string,std::string> scopeLocalId_childDept_m; // local_id of scope to scope's child department
1335- std::vector<std::shared_ptr<category>> categories;
1336 std::map<std::string,std::shared_ptr<category>> id_category_m;
1337 std::map<std::string,std::string> categoryId_titleMsgid;
1338 std::vector<std::string> categoryIds;
1339@@ -200,7 +209,6 @@
1340 std::map<std::string,std::string> keyword_deptId;
1341 std::map<std::string,std::string> keyword_surfaceRdr;
1342
1343- us::ChildScopeList current_child_scopes;
1344
1345 std::string first_result_owner; // set to first declaared keyword or scope that declared a first result renderer
1346 std::list<std::string> declared_keywords;// the declared list of keywords, in declared order
1347@@ -217,7 +225,6 @@
1348 std::map<std::string,bool> sharedcat_catregistered;//stores whether the shared cat's ctegory is registered yet
1349
1350 std::vector<us::ScopeProxy> scopes; // will hold all enabled and present child scopes
1351- std::map<std::string, std::shared_ptr<AggChildScope>> scopes_m;//key is local_id
1352 std::map<int,std::string> scopes_m_int_localId;
1353 std::string scope_dir_;
1354 std::string cache_dir_;
1355
1356=== modified file 'src/CMakeLists.txt'
1357--- src/CMakeLists.txt 2016-08-11 20:24:03 +0000
1358+++ src/CMakeLists.txt 2016-09-30 19:17:35 +0000
1359@@ -10,6 +10,7 @@
1360 notify-strategy.cpp
1361 aggchildscope.cpp
1362 utils.cpp
1363+ handle_results.cpp
1364 action.cpp
1365 )
1366 find_package(Qt5Core REQUIRED)
1367
1368=== modified file 'src/aggchildscope.cpp'
1369--- src/aggchildscope.cpp 2016-06-29 10:29:37 +0000
1370+++ src/aggchildscope.cpp 2016-09-30 19:17:35 +0000
1371@@ -71,12 +71,45 @@
1372 using_category_title_display_name_ = b;
1373 }
1374
1375-
1376 us::Category::SCPtr AggChildScope::category()
1377 {
1378 return cat;
1379 }
1380
1381+bool AggChildScope::using_login_renderer()
1382+{
1383+ return using_login_renderer_;
1384+}
1385+
1386+void AggChildScope::enable_using_login_renderer()
1387+{
1388+ using_login_renderer_ = true;;
1389+}
1390+
1391+/*
1392+ * When login renderer is used, this required call defines the category id of the
1393+ * invoming result for which the login renderer is used.
1394+ */
1395+void AggChildScope::set_login_renderer_incoming_category_id(std::string const& cat_id)
1396+{
1397+ login_renderer_incoming_category_id_ = cat_id;
1398+}
1399+
1400+std::string AggChildScope::login_renderer_incoming_category_id()
1401+{
1402+ return login_renderer_incoming_category_id_;
1403+}
1404+
1405+void AggChildScope::set_login_template(std::string const& login_template)
1406+{
1407+ template_login_ = login_template;
1408+}
1409+
1410+const std::string & AggChildScope::login_template() const
1411+{
1412+ return template_login_;
1413+}
1414+
1415 void AggChildScope::set_first_result_template(std::string const &first_result_template)
1416 {
1417 template_first_result_ = first_result_template;
1418
1419=== added file 'src/handle_results.cpp'
1420--- src/handle_results.cpp 1970-01-01 00:00:00 +0000
1421+++ src/handle_results.cpp 2016-09-30 19:17:35 +0000
1422@@ -0,0 +1,960 @@
1423+/*
1424+ * Copyright (C) 2016 Canonical Ltd
1425+ *
1426+ * This program is free software: you can redistribute it and/or modify
1427+ * it under the terms of the GNU General Public License version 3 as
1428+ * published by the Free Software Foundation.
1429+ *
1430+ * This program is distributed in the hope that it will be useful,
1431+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1432+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1433+ * GNU General Public License for more details.
1434+ *
1435+ * You should have received a copy of the GNU General Public License
1436+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1437+ *
1438+ * Authored by: Kyle Nitzsche <kyle.nitzsche@canonical.com>
1439+ * Scott Sweeny <scott.sweeny@canonical.com>
1440+ *
1441+ */
1442+
1443+#include "query.h"
1444+#include "aggchildscope.h"
1445+#include "i18n.h"
1446+
1447+#include <unity/UnityExceptions.h>
1448+
1449+#include <QFile>
1450+#include <QJsonParseError>
1451+#include <QJsonDocument>
1452+#include <QJsonObject>
1453+#include <QJsonArray>
1454+#include <QDateTime>
1455+#include <iostream>
1456+#include <fstream>
1457+#include <sstream>
1458+
1459+using namespace unity::scopes;
1460+
1461+#define UNUSED(x) (void)(x);
1462+
1463+bool Query::is_login_case(std::shared_ptr<AggChildScope> scope, std::string const& res_cat_id)
1464+{
1465+ return scope->using_login_renderer() &&
1466+ res_cat_id == scope->login_renderer_incoming_category_id();
1467+
1468+}
1469+
1470+void Query::complete_child_scopes()
1471+{
1472+ // Cycle through child scopes (in current_scopes) and, if enabled in settings, complete set up
1473+ try
1474+ {
1475+ for (const std::string & local_id : current_scopes)
1476+ {
1477+ qDebug() << "==== CURRENT. local_id: " << client.qstr(local_id);
1478+ std::string id = localId_id_m[local_id];
1479+ qDebug() << "==== CURRENT. id: " << client.qstr(id);
1480+ std::shared_ptr<AggChildScope> scope = scopes_m[local_id];
1481+
1482+ scope->set_proxy(registry_);
1483+
1484+ if (!scope->exists()) {
1485+ std::string sep = query_store_ == "" ? "name:" : "+";
1486+ std::string name = std::string(id, 0, id.find('_'));
1487+ query_store_.append(sep + name);
1488+ }
1489+ else
1490+ {
1491+ if (!scope->source_finder())
1492+ has_one_source = true;
1493+ }
1494+ }
1495+ for (auto source : sources_for_clickstore)
1496+ {
1497+ AggChildScope scope(source);
1498+ scope.set_proxy(registry_);
1499+ if (!scope.exists()) {
1500+ std::string sep = query_store_ == "" ? "name:" : "+";
1501+ std::string name = std::string(source, 0, source.find('_'));
1502+ query_store_.append(sep + name);
1503+ }
1504+ }
1505+ }
1506+ catch (unity::Exception const& e)
1507+ {
1508+ qWarning() << "NotFoundException: " << e.what();
1509+ }
1510+}
1511+
1512+void Query::noSources(unity::scopes::SearchReplyProxy const& reply) {
1513+ std::string MESS_GRID = R"(
1514+ {
1515+ "schema-version" : 1,
1516+ "template" : {
1517+ "category-layout" : "grid",
1518+ "card-layout": "horizontal",
1519+ "card-size": "small",
1520+ "non-interactive": true
1521+ },
1522+ "components" : {
1523+ "title" : "title"
1524+ }
1525+ }
1526+ )";
1527+
1528+ CategoryRenderer emptyCat(MESS_GRID);
1529+ auto messGrid = reply->register_category("emptyMess", _("Welcome"), "", emptyCat);
1530+ CategorisedResult res(messGrid);
1531+ res.set_uri("http://ubuntu.com");
1532+ res["title"]=_("To start using this scope please install sources from the list below");
1533+ reply->push(res);
1534+}
1535+
1536+bool Query::hints_exists()
1537+{
1538+ auto scopes = registry_->list();
1539+ us::MetadataMap::const_iterator scope;
1540+ scope = scopes.find(HINTS_SCOPE_ID);
1541+ if (scope != scopes.end()) {
1542+ auto meta = registry_->get_metadata(HINTS_SCOPE_ID);
1543+ hints_scope = meta.proxy();
1544+ return true;
1545+ }
1546+ return false;
1547+}
1548+
1549+void Query::display_local_hints_quickstart(us::SearchReplyProxy const& upstream_reply_)
1550+{
1551+ if (!local_hints)
1552+ return;
1553+ if (!hints_local_json.contains(QStringLiteral("content")))
1554+ return;
1555+ auto content = hints_local_json[QStringLiteral("content")].toObject();
1556+ if (!content.contains(QStringLiteral("categories")))
1557+ return;
1558+
1559+ QJsonArray cats = content[QStringLiteral("categories")].toArray();
1560+ for (const auto & cat_ : cats)
1561+ {
1562+ auto cat = cat_.toObject();
1563+
1564+ std::string id = client.sstr(cat[QStringLiteral("id")].toString());
1565+ std::string title = _(client.sstr(cat[QStringLiteral("_title")].toString()).c_str());
1566+ QJsonDocument layout_d(cat[QStringLiteral("layout")].toObject());
1567+ std::string local_hints_template = client.sstr(layout_d.toJson());
1568+ auto category = upstream_reply_->lookup_category(id);
1569+ if (!category) {
1570+ category = upstream_reply_->register_category(id, title, "", us::CategoryRenderer(local_hints_template));
1571+ }
1572+ QJsonArray items = cat[QStringLiteral("items")].toArray();
1573+ for (const auto & res__ : items)
1574+ {
1575+ auto res_ = res__.toObject();
1576+ us::CategorisedResult res(category);
1577+ res.set_title(_(client.sstr(res_[QStringLiteral("_title")].toString()).c_str()));
1578+ res["subtitle"] = _(client.sstr(res_[QStringLiteral("_subtitle")].toString()).c_str());
1579+ res["description"] = _(client.sstr(res_[QStringLiteral("_description")].toString()).c_str());
1580+ std::string uri = "http://www.ubuntu.com";
1581+ if (res_.contains(QStringLiteral("hide_in_locales")))
1582+ {
1583+ QStringList hide_in_locales = res_[QStringLiteral("hide_in_locales")].toString().split(",");
1584+ std::string lcRaw = setlocale(LC_ALL, "");
1585+ std::string lc_lang = lcRaw.size() > 5 ? lcRaw.substr(0, 2) : "";
1586+ std::string lc_country = lcRaw.size() > 5 ? lcRaw.substr(3, 2) : "";
1587+ std::string lc = lc_lang + "_" + lc_country;
1588+ if (hide_in_locales.contains(client.qstr(lc)))
1589+ {
1590+ continue;
1591+ }
1592+ }
1593+ if (res_.contains("uri"))
1594+ {
1595+ uri = client.sstr(res_["uri"].toString());
1596+ }
1597+ if (res_.contains(QStringLiteral("action")))
1598+ {
1599+ auto action = res_[QStringLiteral("action")].toObject();
1600+ VariantBuilder builder;
1601+ builder.add_tuple({
1602+ {"id", Variant("Open")},
1603+ {"label", Variant(_(client.sstr(action[QStringLiteral("_name")].toString()).c_str()))},
1604+ {"uri", Variant(client.sstr(action[QStringLiteral("uri")].toString()))}
1605+ });
1606+ res["actions"]=builder.end();
1607+ if (action.contains(QStringLiteral("uri")))
1608+ {
1609+ uri = client.sstr(action[QStringLiteral("uri")].toString());
1610+ }
1611+ }
1612+ if (res_.contains(QStringLiteral("oaccount")))
1613+ {
1614+ auto oaccount = res_[QStringLiteral("oaccount")].toObject();
1615+ auto queryScope = client.sstr(oaccount[QStringLiteral("QueryScope")].toString());
1616+
1617+ // before framework 15.0.4.4 the service name is the FQ scope id and contains one "_"
1618+ // with 15.04.4, it is FQ scope id += _PROVIDER
1619+ // we also want the fully qualified scope id to check with registry if it is installed.
1620+ // to support both cases:
1621+ QStringList parts = oaccount[QStringLiteral("ServiceName")].toString().split("_");
1622+ QString provider = oaccount[QStringLiteral("ProviderName")].toString();
1623+ QString sc_id = parts[0] + "_" + parts[1];
1624+ QString id = sc_id;
1625+ if (parts.size() > 2) // framework 15.04.4 or higher
1626+ {
1627+ id += "_" + provider;
1628+ }
1629+ us::OnlineAccountClient oa_client(client.sstr(id),
1630+ client.sstr(oaccount[QStringLiteral("ServiceType")].toString()),
1631+ client.sstr(provider));
1632+ bool alreadyLoggedIn = false;
1633+ oa_client.refresh_service_statuses();
1634+ for (auto const& status : oa_client.get_service_statuses())
1635+ {
1636+ if (status.service_enabled)
1637+ {
1638+ alreadyLoggedIn = true;
1639+ break;
1640+ }
1641+ }
1642+ // do not display quick start help for scopes that are not registered in the system
1643+ auto reg_scopes = registry_->list();
1644+ us::MetadataMap::const_iterator scope_it;
1645+ scope_it = reg_scopes.find(client.sstr(sc_id));
1646+ if (scope_it == reg_scopes.end()) {
1647+ qWarning () << QString("%1: OA scope is NOT REGISTERED, skipping for HINTS: %2").arg(oaccount[QStringLiteral("ServiceName")].toString());
1648+ continue;
1649+ }
1650+ if (alreadyLoggedIn) {
1651+ //the hints code does this when the person is already logged in to the service.
1652+ //auto tmp = oaccount["_loggedin"].toString().toStdString();
1653+ //res["title"] = std::string(_(tmp.c_str()));
1654+ //
1655+ //this continue statement means if the person is logged in, we do not show
1656+ //a result for the item
1657+ continue;
1658+ }
1659+ us::CannedQuery query(queryScope);
1660+ oa_client.register_account_login_item(res,
1661+ query,
1662+ OnlineAccountClient::InvalidateResults,
1663+ OnlineAccountClient::DoNothing);
1664+
1665+ }
1666+ if (res_.contains(QStringLiteral("hide_hints")))
1667+ {
1668+ qDebug() << "=== NHINTS. utils. hide hints found in json";
1669+ res["action"] = "hide_hints";
1670+ res.set_intercept_activation();
1671+ }
1672+ res.set_uri(uri);
1673+ res.set_art(scope_dir_ + "/images/" + client.sstr(res_["art"].toString()));
1674+ upstream_reply_->push(res);
1675+ }
1676+ }
1677+}
1678+
1679+void Query::display_remote_hints_quickstart(us::SearchReplyProxy const& upstream_reply_)
1680+{
1681+ qDebug() << "==== HINTS in display_remote_hints_quickstart()";
1682+ Category::SCPtr hint_cat;
1683+ SearchMetadata metadata(search_metadata());
1684+ auto reply = std::make_shared<ResultForwarder>(upstream_reply_,
1685+ [this, &hint_cat](CategorisedResult& res) -> bool {
1686+ UNUSED(res)
1687+ return true;
1688+ });
1689+
1690+ subsearch(hints_scope, "", HINTS_THIS_SCOPE, FilterState(),
1691+ metadata, reply);
1692+}
1693+
1694+bool Query::hints_hidden() {
1695+
1696+ auto filepath1 = QString::fromStdString(cache_dir_) +"/" + hints_file;
1697+ auto filepath2 = QString::fromStdString(cache_dir_) +"/" + firstboot;
1698+ if (QFile::exists(filepath1) || QFile::exists(filepath2))
1699+ return true;
1700+ return false; //don't show hints as fall back
1701+}
1702+
1703+void Query::dismiss_hints_quickstart() {
1704+ qDebug() << "==== HINTS in dismiss()";
1705+ auto filepath= QString::fromStdString(cache_dir_) +"/" + hints_file;
1706+ QFile file(filepath);
1707+ file.open(QIODevice::WriteOnly);
1708+ QString msg = QString("Timestamp for %1 %2").arg(client.qstr(HINTS_THIS_SCOPE), QDateTime::currentDateTime().toString());
1709+ file.write(client.sstr(msg).c_str(), client.sstr(msg).size());
1710+ file.close();
1711+}
1712+
1713+void Query::handle_keyword_child(unity::scopes::SearchReplyProxy const& upstream_reply,
1714+ std::shared_ptr<AggChildScope> scope,
1715+ us::CategorisedResult & res){
1716+
1717+ //the supported cases
1718+ enum RunType {
1719+ Search,
1720+ SearchNoLink,
1721+ SearchNoRdr,
1722+ SearchNoRdrNoLink,
1723+ Surface,
1724+ SurfaceNoLink,
1725+ SurfaceNoRdr,
1726+ SurfaceNoRdrNoLink
1727+ };
1728+ RunType runType;
1729+
1730+ std::string keyword = scope->keyword();
1731+ std::string scope_id = scope->id();
1732+ std::string cat_id = keyword + ":" + scope_id;
1733+ std::string cat_title = registry_->get_metadata(scope->id()).display_name();
1734+ std::string search_rdr = scope->search_template();
1735+ std::string rdr = scope->surface_template();
1736+ bool link = scope->category_link_to_child();
1737+
1738+ qDebug() << "==== KW scope cat_id prefix: " << client.qstr(rdr);
1739+ qDebug() << "==== KW scope: " << client.qstr(scope->id()) << " keyword: " << client.qstr(keyword);;
1740+ qDebug() << "==== KW scope shared cat name: " << client.qstr(scope->keyword_scope_shared_cat_name());
1741+ qDebug() << "==== KW scope search rdr: " << client.qstr(search_rdr);
1742+ qDebug() << "==== KW scope rdr: " << client.qstr(rdr);
1743+ qDebug() << "==== KW scope link: " << QString::number(link);
1744+
1745+ // Determine the current case based on state
1746+ if (!query().query_string().empty())
1747+ {//search
1748+ if (!search_rdr.empty())
1749+ {//rdr
1750+ if (link)
1751+ {//link
1752+ runType = Search;
1753+ }
1754+ else
1755+ {//no link
1756+ runType = SearchNoLink;
1757+ }
1758+ }
1759+ else
1760+ {//no rdr
1761+ if (link)
1762+ {//link
1763+ runType = SearchNoRdr;
1764+ }
1765+ else
1766+ {//link
1767+ runType = SearchNoRdrNoLink;
1768+ }
1769+ }
1770+ }
1771+ else
1772+ {//surface
1773+ if (!rdr.empty())
1774+ {
1775+ if (link)
1776+ {
1777+ runType = Surface;
1778+ }
1779+ else
1780+ {
1781+ runType = SurfaceNoLink;
1782+ }
1783+ }
1784+ else
1785+ {
1786+ if (link)
1787+ {
1788+ runType = SurfaceNoRdr;
1789+ }
1790+ else
1791+ {
1792+ runType = SurfaceNoRdrNoLink;
1793+ }
1794+ }
1795+ }
1796+
1797+ switch (runType)
1798+ {
1799+ case RunType::Search:
1800+ qDebug() << "=== run type: keyword Search";
1801+ cat_id += ":keyword:search:rdr:" + cat_title;
1802+ set_result_catgory(upstream_reply, res, cat_id, cat_title, search_rdr, scope_id);
1803+ break;
1804+ case RunType::SearchNoLink:
1805+ qDebug() << "=== run type: keyword SearchNoLink";
1806+ cat_id += ":keyword:search:rdrnolink:" + cat_title;
1807+ set_result_catgory(upstream_reply, res, cat_id, cat_title, search_rdr);
1808+ break;
1809+ case RunType::SearchNoRdr:
1810+ qDebug() << "=== run type: keyword SearchNoRdr";
1811+ cat_id = ":keyword:search:nordr:" + cat_title;
1812+ rdr = res.category()->renderer_template().data();
1813+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, scope_id);
1814+ break;
1815+ case RunType::SearchNoRdrNoLink:
1816+ qDebug() << "=== run type: keyword SearchNoRdrNoLink";
1817+ cat_id = ":keyword:search:nordr:nolink:" + cat_title;
1818+ rdr = res.category()->renderer_template().data();
1819+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
1820+ break;
1821+ case RunType::Surface:
1822+ qDebug() << "=== run type: keyword Surface";
1823+ cat_id += ":keyword:surface:rdr:" + cat_title;
1824+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, scope_id);
1825+ break;
1826+ case RunType::SurfaceNoLink:
1827+ qDebug() << "=== run type: keyword SurfaceNoLink";
1828+ cat_id += ":keyword:surface:rdr:noLink::" + cat_title;
1829+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
1830+ break;
1831+ case RunType::SurfaceNoRdr:
1832+ qDebug() << "=== run type: keyword SurfaceNoRdr";
1833+ cat_id += ":keyword:surface:nordr:" + cat_title;
1834+ rdr = res.category()->renderer_template().data();
1835+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, scope_id);
1836+ break;
1837+ case RunType::SurfaceNoRdrNoLink:
1838+ qDebug() << "=== run type: keyword SurfaceNoRdrNoLink";
1839+ cat_id += ":keyword:surface:nordr:nolink:" + cat_title;
1840+ rdr = res.category()->renderer_template().data();
1841+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
1842+ break;
1843+ }
1844+}
1845+
1846+us::Category::SCPtr Query::set_result_catgory(us::SearchReplyProxy const& upstream_reply, us::CategorisedResult & res, std::string const& id, std::string const& title, std::string const& rdr, std::string const& link_to)
1847+{
1848+ qDebug() << "=== category type in set result with child link";
1849+ qDebug() << QString("=== in set_result_category() with link. id: %1, title: %2, link_to: %3, rdr: %4").arg(client.qstr(id), client.qstr(title), client.qstr(link_to), client.qstr(rdr));
1850+
1851+ us::Category::SCPtr cat = upstream_reply->lookup_category(id);
1852+ if (cat == nullptr)
1853+ {
1854+ qDebug() << QString("=== in set_result_category() with link. lookup_category() returns nullptr, so register it");
1855+ cat = upstream_reply->register_category
1856+ (
1857+ id,
1858+ title,
1859+ "",
1860+ us::CannedQuery(link_to),
1861+ us::CategoryRenderer(rdr)
1862+ );
1863+ }
1864+ res.set_category(cat);
1865+ qDebug() << "=== in set_result_category() with link. here's the res category json: " << client.qstr(Variant(res.category()->query()->serialize()).serialize_json());
1866+ return cat;
1867+}
1868+
1869+us::Category::SCPtr Query::set_result_catgory(us::SearchReplyProxy const& upstream_reply, us::CategorisedResult & res, std::string const& id, std::string const& title, std::string const& rdr)
1870+{
1871+ qDebug() << "=== category type in set result no link";
1872+ qDebug() << QString("=== in set_result_category() no link. id: %1, title: %2, rdr: %3").arg(client.qstr(id), client.qstr(title), client.qstr(rdr) );
1873+
1874+ us::Category::SCPtr cat = upstream_reply->lookup_category(id);
1875+ if (cat == nullptr)
1876+ {
1877+ qDebug() << QString("=== in set_result_category() no link. lookup_category() returns nullptr, so register it");
1878+ cat = upstream_reply->register_category
1879+ (
1880+ id,
1881+ title,
1882+ "",
1883+ us::CategoryRenderer(rdr)
1884+ );
1885+ }
1886+ res.set_category(cat);
1887+ return cat;
1888+}
1889+
1890+void Query::handle_category_child(unity::scopes::SearchReplyProxy const& upstream_reply,
1891+ std::shared_ptr<AggChildScope> scope,
1892+ us::CategorisedResult & res){
1893+
1894+ enum RunType {
1895+ Search,
1896+ SearchNoRdr,
1897+ SearchNoLink,
1898+ SearchNoLinkNoRdr,
1899+ Login,
1900+ LoginNoLink,
1901+ SurfaceUsesFirstIsFirst,
1902+ SurfaceUsesFirstIsFirstNoLink,
1903+ SurfaceUsesFirstNotFirst,
1904+ SurfaceUsesFirstNotFirstNoLink,
1905+ Surface,
1906+ SurfaceNoRdr,
1907+ SurfaceNoLink,
1908+ SurfaceNoLinkNoRdr
1909+ };
1910+ RunType runType;
1911+
1912+ //get state
1913+ bool login_case = is_login_case(scope, res.category()->id());
1914+ bool uses_first_result_rdr = categoryId_first_result_renderers.find(scope->category_id()) != categoryId_first_result_renderers.end();
1915+ bool is_first_result = categoryId_isFirstResult[scope->category_id()];
1916+ bool not_first_result = categoryId_isSecondResult[scope->category_id()];
1917+ bool link = false;
1918+ bool has_rdr = !scope->surface_template().empty();
1919+ std::string category_id = scope->category_id();
1920+ std::string link_to = categoryId_linkToChildSpecified[category_id];
1921+ //qDebug() << "link: scope->category_id " << client.qstr(scope->category_id());
1922+
1923+ for (auto i : categoryId_linkToChildSpecified)
1924+ {
1925+ if ( i.first == scope->category_id())
1926+ {
1927+ if ( !i.second.empty() )
1928+ {
1929+ link = true;
1930+ }
1931+ }
1932+ }
1933+
1934+ // Determine the current case based on state
1935+ if (!query().query_string().empty())
1936+ {
1937+ if (!scope->search_template().empty())
1938+ {
1939+ if (link)
1940+ {
1941+ runType = Search;
1942+ }
1943+ else
1944+ {
1945+ runType = SearchNoLink;
1946+ }
1947+ }
1948+ else
1949+ {
1950+ if (link)
1951+ {
1952+ runType = SearchNoRdr;
1953+ }
1954+ else
1955+ {
1956+ runType = SearchNoLinkNoRdr;
1957+ }
1958+ }
1959+ }
1960+ else if (login_case)
1961+ {
1962+ if (link)
1963+ {
1964+ runType = Login;
1965+ }
1966+ else
1967+ {
1968+ runType = LoginNoLink;
1969+ }
1970+ }
1971+ else
1972+ {//surface
1973+ if (uses_first_result_rdr)
1974+ {//uses first
1975+ if (is_first_result)
1976+ {//is first
1977+ if (link)
1978+ {
1979+ runType = SurfaceUsesFirstIsFirst;
1980+ }
1981+ else
1982+ {
1983+ runType = SurfaceUsesFirstIsFirstNoLink;
1984+ }
1985+ }
1986+ else if (not_first_result)
1987+ {// not first
1988+ if (link)
1989+ {
1990+ runType = SurfaceUsesFirstNotFirst;
1991+ }
1992+ else
1993+ {
1994+ runType = SurfaceUsesFirstNotFirstNoLink;
1995+ }
1996+ }
1997+ }
1998+ else {//not uses first
1999+ if (link)
2000+ {
2001+ if (has_rdr)
2002+ {
2003+ runType = Surface;
2004+ }
2005+ else
2006+ {
2007+ runType = SurfaceNoRdr;
2008+ }
2009+ }
2010+ else
2011+ { // no link
2012+ if (has_rdr)
2013+ {
2014+ runType = SurfaceNoLink;
2015+ }
2016+ else
2017+ {
2018+ runType = SurfaceNoLinkNoRdr;
2019+ }
2020+ }
2021+ }
2022+ }
2023+
2024+ qDebug() << "=== login: using login renderer: " << QString::number(scope->using_login_renderer());
2025+ qDebug() << "=== login: res cat id: " << client.qstr(res.category()->id());
2026+ qDebug() << "=== login: login_renderer_incoming_category_id: " << client.qstr(scope->login_renderer_incoming_category_id());
2027+
2028+ // for use in category registration
2029+ std::string cat_id;
2030+ std::string cat_title;
2031+ std::string rdr;
2032+
2033+ if (!categoryId_titleMsgid[category_id].empty())
2034+ cat_title = _(categoryId_titleMsgid[category_id].c_str());
2035+ else
2036+ cat_title = "";
2037+
2038+ switch (runType)
2039+ {
2040+ case RunType::Search:
2041+ qDebug() << "=== run type: category Search";
2042+ cat_id = scope->local_id() + ":category:search-renderer:searching:" + cat_title;
2043+ rdr = scope->search_template();
2044+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2045+ break;
2046+ case RunType::SearchNoLink:
2047+ qDebug() << "=== run type: category SearchNoLink";
2048+ cat_id = scope->local_id() + ":category:search-renderer:no-link:searching:" + cat_title;
2049+ rdr = scope->search_template();
2050+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2051+ break;
2052+ case RunType::SearchNoRdr:
2053+ qDebug() << "=== run type: category SearchNoRdr";
2054+ cat_id = scope->local_id() + ":category:no-renderer:searching:" + cat_title;
2055+ rdr = res.category()->renderer_template().data();
2056+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2057+ case RunType::SearchNoLinkNoRdr:
2058+ qDebug() << "=== run type: category SearchNoLinkNoRdr";
2059+ cat_id = scope->local_id() + ":category:no-renderer:no-link:searching:" + cat_title;
2060+ rdr = res.category()->renderer_template().data();
2061+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2062+ case RunType::Login:
2063+ qDebug() << "=== run type: category Login";
2064+ cat_id = scope->local_id() + ":category:login-renderer:" + cat_title;
2065+ rdr = scope->login_template();
2066+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2067+ break;
2068+ case RunType::LoginNoLink:
2069+ qDebug() << "=== run type: category LoginNoLink";
2070+ cat_id = scope->local_id() + ":category:login-renderer:no-link" + cat_title;
2071+ rdr = scope->login_template();
2072+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2073+ break;
2074+ case RunType::SurfaceUsesFirstIsFirst:
2075+ qDebug() << "=== run type: category SurfaceUsesFirstIsFirst";
2076+ if ((first_result_owner.empty()) || (first_result_owner == category_id))
2077+ {
2078+ qDebug() << "==== CATREG. uses FIRST result. FIRST result. NO LINK to child ";
2079+ first_result_owner = category_id;
2080+ cat_id = category_id + ":category:uses-first-result:is-first:surfacing";
2081+ rdr = categoryId_first_result_renderers[category_id];
2082+ categoryId_isFirstResult[category_id] = false;
2083+ categoryId_isSecondResult[category_id] = true;
2084+ catname_catptr[category_id] = set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2085+ scope->set_category(catname_catptr[category_id]);
2086+ }
2087+ break;
2088+ case RunType::SurfaceUsesFirstIsFirstNoLink:
2089+ qDebug() << "=== run type: category SurfaceUsesFirstIsFirstNoLink";
2090+ if ((first_result_owner.empty()) || (first_result_owner == category_id))
2091+ {
2092+ qDebug() << "==== CATREG. uses FIRST result. FIRST result. NO LINK to child ";
2093+ first_result_owner = category_id;
2094+ cat_id = category_id + ":category:uses-first-result:is-first:no-link:surfacing";
2095+ rdr = categoryId_first_result_renderers[category_id];
2096+ categoryId_isFirstResult[category_id] = false;
2097+ categoryId_isSecondResult[category_id] = true;
2098+ catname_catptr[category_id] = set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2099+ scope->set_category(catname_catptr[category_id]);
2100+ }
2101+ break;
2102+ case RunType::SurfaceUsesFirstNotFirst:
2103+ qDebug() << "=== run type: cateogry SurfaceUsesFirstNotFirst";
2104+ cat_id = category_id + ":category:uses-first-result:not-first:surfacing:" + cat_title;
2105+ rdr = scope->surface_template();
2106+ catname_catptr[category_id] = set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2107+ scope->set_category(catname_catptr[category_id]);
2108+ break;
2109+ case RunType::SurfaceUsesFirstNotFirstNoLink:
2110+ qDebug() << "=== run type: category SurfaceUsesFirstNotFirstNoLink";
2111+ cat_id = category_id + ":category:uses-first-result:not-first:no-rdr:no-linksurfacing:" + cat_title;
2112+ rdr = scope->surface_template();
2113+ catname_catptr[category_id] = set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2114+ scope->set_category(catname_catptr[category_id]);
2115+ break;
2116+ case RunType::Surface:
2117+ qDebug() << "=== run type: category Surface";
2118+ cat_id = category_id + ":category:surface-rdr:surfacing:" + cat_title;
2119+ rdr = scope->surface_template();
2120+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2121+ break;
2122+ case RunType::SurfaceNoRdr:
2123+ qDebug() << "=== run type: category SurfaceNoRdr";
2124+ cat_id = category_id + ":category:no-rdr::surfacing:" + cat_title;
2125+ rdr = res.category()->renderer_template().data();
2126+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2127+ break;
2128+ case RunType::SurfaceNoLink:
2129+ qDebug() << "=== run type: category SurfaceNoLink";
2130+ cat_id = category_id + ":category:surface-rdr:no-link:surfacing:" + cat_title;
2131+ rdr = scope->surface_template();
2132+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2133+ //qDebug() << "CQ json: " << client.qstr(Variant(catname_catptr[category_id]->query()->serialize()).serialize_json());
2134+ break;
2135+ case RunType::SurfaceNoLinkNoRdr:
2136+ qDebug() << "=== run type: category SurfaceNoLinkNoRdr";
2137+ cat_id = category_id + ":category:no-rdr:surfacing:" + cat_title;
2138+ rdr = res.category()->renderer_template().data();
2139+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2140+ //qDebug() << "CQ json: " << client.qstr(Variant(upstream_reply->lookup_category(cat_id)->query()->serialize()).serialize_json());
2141+ break;
2142+ }
2143+
2144+ //TODO: put FALLBACK into method to avoid duplication needed by refactor
2145+ // perform res component fallback, as needed
2146+ if(!scope->surface_common_template_id().empty())
2147+ {
2148+ std::string res_str = us::Variant(res.serialize()).serialize_json();
2149+ auto change = this->client.check_result_fallbacks(res_str,
2150+ scope->surface_common_template_id(),
2151+ this->get_common_templates_fallbacks());
2152+ if (!std::get<0>(change).empty())
2153+ {
2154+ res[std::get<0>(change)] = std::get<1>(change);
2155+ }
2156+ }
2157+}
2158+
2159+void Query::handle_declared_child(unity::scopes::SearchReplyProxy const& upstream_reply,
2160+ std::shared_ptr<AggChildScope> scope,
2161+ us::CategorisedResult & res,
2162+ std::string const& inc_res_cat_id,
2163+ std::string const& inc_res_cat_title) {
2164+
2165+ //the supported cases
2166+ enum RunType {
2167+ Search,
2168+ SearchNoRdr,
2169+ SearchNoLink,
2170+ SearchNoLinkNoRdr,
2171+ Login,
2172+ LoginNoLink,
2173+ Surface,
2174+ SurfaceNoRdr,
2175+ SurfaceNoLink,
2176+ SurfaceNoLinkNoRdr
2177+ };
2178+ RunType runType;
2179+
2180+ //get state
2181+ bool login_case = is_login_case(scope, res.category()->id());
2182+ qDebug() << "==== DECLARED. login_case: " << QString::number(login_case);
2183+ bool link = scope->category_link_to_child();//this means the category links, not that we get a specific child scope's category
2184+ std:: string link_to = scope->id();
2185+ bool has_rdr = !scope->surface_template().empty();
2186+
2187+ // Determine the current case based on state
2188+ if (!query().query_string().empty())
2189+ {
2190+ if (!scope->search_template().empty())
2191+ {
2192+ if (link)
2193+ {
2194+ runType = Search;
2195+ }
2196+ else
2197+ {
2198+ runType = SearchNoLink;
2199+ }
2200+ }
2201+ else
2202+ {
2203+ if (link)
2204+ {
2205+ runType = SearchNoRdr;
2206+ }
2207+ else
2208+ {
2209+ runType = SearchNoLinkNoRdr;
2210+ }
2211+ }
2212+ }
2213+ else if (login_case)
2214+ {
2215+ if (link)
2216+ {
2217+ runType = Login;
2218+ }
2219+ else
2220+ {
2221+ runType = LoginNoLink;
2222+ }
2223+ }
2224+ else
2225+ {//surface
2226+ if (link)
2227+ {
2228+ if (has_rdr)
2229+ {
2230+ runType = Surface;
2231+ }
2232+ else
2233+ {
2234+ runType = SurfaceNoRdr;
2235+ }
2236+ }
2237+ else
2238+ { // no link
2239+ if (has_rdr)
2240+ {
2241+ runType = SurfaceNoLink;
2242+ }
2243+ else
2244+ {
2245+ runType = SurfaceNoLinkNoRdr;
2246+ }
2247+ }
2248+ }
2249+
2250+ // for use in category registration
2251+ std::string cat_id;
2252+ std::string cat_title;
2253+ std::string rdr;
2254+
2255+ qDebug() << "==== DECLARED. scope is declared: " << client.qstr(scope->local_id());
2256+
2257+ // set the category tiile
2258+ if (scope->using_category_title_incoming() )
2259+ {
2260+ scope->set_category_title(inc_res_cat_title);
2261+ }
2262+ else if (scope->using_category_title_display_name() )
2263+ {
2264+ scope->set_category_title(scope->metadata()->display_name());
2265+ }
2266+
2267+ cat_title = scope->category_title();
2268+
2269+ switch (runType)
2270+ {
2271+ case RunType::Login:
2272+ qDebug() << "=== run type: declared scope Login";
2273+ cat_id = scope->local_id() + ":declared:login";
2274+ rdr = scope->login_template();
2275+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2276+ break;
2277+ case RunType::LoginNoLink:
2278+ qDebug() << "=== run type: declared scope LoginNoLink";
2279+ cat_id = scope->local_id() + ":declared:login:no-link";
2280+ rdr = scope->login_template();
2281+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2282+ break;
2283+ case RunType::Search:
2284+ qDebug() << "=== run type: declared scope Search";
2285+ cat_id = scope->local_id() + ":declared:search";
2286+ rdr = scope->search_template();
2287+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2288+ break;
2289+ case RunType::SearchNoRdr:
2290+ qDebug() << "=== run type: declared scope SearchNoRdr";
2291+ cat_id = scope->local_id() + ":declared:no-rdr:search";
2292+ rdr = res.category()->renderer_template().data();
2293+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2294+ break;
2295+ case RunType::SearchNoLink:
2296+ qDebug() << "=== run type: declared scope Search";
2297+ cat_id = scope->local_id() + ":declared:no-link:search";
2298+ rdr = scope->search_template();
2299+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2300+ break;
2301+ case RunType::SearchNoLinkNoRdr:
2302+ qDebug() << "=== run type: declared scope SearchNoRdr";
2303+ cat_id = scope->local_id() + ":declared:no_rdr:no-link:search";
2304+ rdr = res.category()->renderer_template().data();
2305+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2306+ break;
2307+ case RunType::Surface:
2308+ qDebug() << "=== run type: declared scope Surface";
2309+ cat_id = scope->local_id() + ":declared:rdr:surface";
2310+ rdr = scope->surface_template();
2311+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2312+ break;
2313+ case RunType::SurfaceNoRdr:
2314+ qDebug() << "=== run type: declared scope SurfaceNoRdr";
2315+ cat_id = scope->local_id() + ":declared:no-rdr:surface";
2316+ rdr = res.category()->renderer_template().data();
2317+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr, link_to);
2318+ break;
2319+ case RunType::SurfaceNoLink:
2320+ qDebug() << "=== run type: declared scope SurfaceNoLink";
2321+ cat_id = scope->local_id() + ":declared:rdr:no-link:surface";
2322+ rdr = scope->surface_template();
2323+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2324+ break;
2325+ case RunType::SurfaceNoLinkNoRdr:
2326+ qDebug() << "=== run type: declared scope SurfaceNoLinkNoRdr";
2327+ cat_id = scope->local_id() + ":declared:link:no-link:no-rdr:surface";
2328+ rdr = res.category()->renderer_template().data();
2329+ set_result_catgory(upstream_reply, res, cat_id, cat_title, rdr);
2330+ break;
2331+ }
2332+
2333+ //TODO: put FALLBACK into method to avoid duplication needed by refactor
2334+ // perform res component fallback, as needed
2335+ if(!scope->surface_common_template_id().empty())
2336+ {
2337+ std::string res_str = us::Variant(res.serialize()).serialize_json();
2338+ auto change = this->client.check_result_fallbacks(res_str,
2339+ scope->surface_common_template_id(),
2340+ this->get_common_templates_fallbacks());
2341+ if (!std::get<0>(change).empty())
2342+ {
2343+ res[std::get<0>(change)] = std::get<1>(change);
2344+ }
2345+ }
2346+
2347+ //for child scopes that declare attribute swaps, do them
2348+ for (std::shared_ptr<std::pair<std::string,std::string>> swap : this->child_scopes_m[scope->local_id()]->swap_result_attributes)
2349+ {
2350+ try
2351+ {
2352+ if (!res[swap->first].get_string().empty())
2353+ {
2354+ res[swap->second] = res[swap->first];
2355+ }
2356+ }
2357+ catch (unity::LogicException)
2358+ {
2359+ qWarning() << QString("Scope %1 swap: result does not contain attribute 2").arg(client.qstr(scope->local_id()), client.qstr(swap->second));
2360+ }
2361+ }
2362+
2363+ //TODO: deprecate child category as brittle. should use keywords instead.
2364+ // if child_category set, filter out other child categories by adding "dont_use" key to result
2365+ // also impose max category results if set
2366+ if (!scope->child_category().empty())
2367+ {
2368+ if (client.qstr(inc_res_cat_id) != client.qstr(scope->child_category()))
2369+ {
2370+ res["dont_use"] = "true";
2371+ }
2372+ else if (scope->using_child_category_max_results())
2373+ {
2374+ scope->inc_result_idx(); // allows constraining num results to child_category_max_results declaration
2375+ if (scope->result_idx() > scope->child_category_max_results() )
2376+ {
2377+ res["dont_use"] = "true";
2378+ }
2379+ }
2380+ }
2381+
2382+}
2383
2384=== modified file 'src/query.cpp'
2385--- src/query.cpp 2016-08-19 20:23:21 +0000
2386+++ src/query.cpp 2016-09-30 19:17:35 +0000
2387@@ -312,8 +312,8 @@
2388 // create child scope instances from keywords and scopes
2389 make_categories();
2390
2391- // complete initializations inlcuding cutting child scopes to those present and enabled
2392- handle_current_child_scopes(empty_search, upstream_reply);
2393+ // complete initializations including cutting child scopes to those present and enabled
2394+ complete_child_scopes();
2395
2396 // convert current_scopes into scope_order based on declared order
2397 set_scope_order();
2398@@ -546,11 +546,6 @@
2399
2400 qDebug() << "==== RESULT scope: " << client.qstr(scope->id());
2401
2402- // for use in category registration
2403- std::string cat_id;
2404- std::string cat_title;
2405- std::string rdr;
2406-
2407 if (scope->keyword_scope())
2408 {
2409 handle_keyword_child(upstream_reply, scope, res);
2410@@ -670,7 +665,6 @@
2411 {
2412 for (auto ch : current_child_scopes)
2413 {
2414-
2415 if (ch.id == localId_id_m[locID])
2416 {
2417 qDebug() << "=== subsearch non KEYWORD scope local_ id " << client.qstr(scope->local_id());
2418@@ -692,7 +686,6 @@
2419 adj_qry = localId_defaultQryStr[locID];
2420 else
2421 adj_qry = "";
2422-
2423 subsearch
2424 (
2425 ch,
2426
2427=== modified file 'src/utils.cpp'
2428--- src/utils.cpp 2016-08-19 20:46:32 +0000
2429+++ src/utils.cpp 2016-09-30 19:17:35 +0000
2430@@ -155,6 +155,7 @@
2431
2432 if (cat_o.contains(QStringLiteral("link_to_child_specified")))
2433 {
2434+ qDebug() << "=== CAT UTILs. id: link_to_child_specified FOUND for category: " << client.qstr(cat_ptr->id);
2435 categoryId_linkToChildSpecified[cat_ptr->id] = cat_o[QStringLiteral("link_to_child_specified")].toString().toStdString();
2436 }
2437 int card = client.get_cardinality_setting(settings(), setting_cardinalities);
2438@@ -221,7 +222,20 @@
2439 QJsonDocument st_d(renderer_o);
2440 cat_ptr->search_renderer = client.sstr(st_d.toJson());
2441 }
2442-
2443+ if (cat_o.contains(QStringLiteral("login_renderer")))
2444+ {
2445+ qDebug() << "==== UTILs. load. CATRES. login_renderer";
2446+ QJsonObject renderer_o = cat_o[QStringLiteral("login_renderer")].toObject();
2447+ QJsonDocument st_d(renderer_o);
2448+ cat_ptr->login_renderer = client.sstr(st_d.toJson());
2449+ if (cat_o.contains(QStringLiteral("login_renderer_incoming_category_id")))
2450+ {
2451+ qDebug() << "==== UTILs. load. CATRES. login_renderer_incoming_category_id";
2452+ cat_ptr->login_renderer_incoming_category_id = cat_o[QStringLiteral("login_renderer_incoming_category_id")].toString().toStdString();
2453+ }
2454+ }
2455+ qDebug() << "==== UTILs. load. CATRES. login_renderer: " << client.qstr(cat_ptr->login_renderer);
2456+ qDebug() << "==== UTILs. load. CATRES. login_renderer_incoming_category_id: " << client.qstr(cat_ptr->login_renderer_incoming_category_id);
2457 if (cat_o.contains(QStringLiteral("scopes")))
2458 {
2459 QJsonArray scope_a = cat_o[QStringLiteral("scopes")].toArray();
2460@@ -298,6 +312,17 @@
2461 if (scope_o.contains(QStringLiteral("department")))
2462 cs->department = scope_o[QStringLiteral("department")].toString().toStdString();
2463
2464+ if (scope_o.contains(QStringLiteral("login_renderer")))
2465+ {
2466+ QJsonObject renderer_o = scope_o[QStringLiteral("login_renderer")].toObject();
2467+ QJsonDocument st_d(renderer_o);
2468+ cs->login_renderer = client.sstr(st_d.toJson());
2469+ if (scope_o.contains(QStringLiteral("login_renderer_incoming_category_id")))
2470+ {
2471+ cs->login_renderer_incoming_category_id = scope_o[QStringLiteral("login_renderer_incoming_category_id")].toString().toStdString();
2472+ }
2473+ }
2474+
2475 cs->link_to_child = true;
2476 if (scope_o.contains(QStringLiteral("link_to_child")))
2477 {
2478@@ -532,85 +557,32 @@
2479 else if (dept_id_of_root_keywords_only.empty())
2480 dept_id_of_root_keywords_only = keyword_id;
2481 }
2482- std::string category;
2483- if (keyword_o.contains(QStringLiteral("shared_category")))
2484- {
2485- category = keyword_id; // note category is synomous with keyword and is therefore used even when not shared category
2486- //note: removed support for shared_category value other than "true"
2487- if (keyword_o[QStringLiteral("shared_category")] == TRUE)
2488- {
2489- std::string title_msgid = keyword_o[QStringLiteral("_shared_category_title")].toString().toStdString();
2490- shared_keyword_cat_titlemsgid[category] = title_msgid;
2491- shared_keyword_cats[keyword_id] = category;
2492- shared_keywords.emplace_back(keyword_id);
2493- }
2494- }
2495- //we do not support first result with non shared category keyword scopes
2496- if (category == keyword_id && keyword_o.contains(QStringLiteral("first_result_renderer_common_id")))
2497- {
2498- std::string id = keyword_o[QStringLiteral("first_result_renderer_common_id")].toString().toStdString();
2499- keyword_catname_first_result_renderers[category] = common_templates[id];
2500- }
2501- else if (category == keyword_id && keyword_o.contains(QStringLiteral("first_result_renderer")))
2502- {
2503- QJsonObject template_o = keyword_o[QStringLiteral("first_result_renderer")].toObject();
2504- QJsonDocument t_d(template_o);
2505- keyword_catname_first_result_renderers[category] = client.sstr(t_d.toJson());
2506- }
2507- else if (category == keyword_id && keyword_o.contains(QStringLiteral("first_result_template")))
2508- {
2509- //support deprecated for backwards compatibiliity
2510- qWarning () << QString("%1: DEPRECATED use of 'first_result_template'. Switch to 'first_result_renderer'")
2511- .arg(client.qstr(agg_scope_->scope_id()));
2512- QJsonObject template_o = keyword_o[QStringLiteral("first_result_template")].toObject();
2513- QJsonDocument t_d(template_o);
2514- keyword_catname_first_result_renderers[category] = client.sstr(t_d.toJson());
2515- }
2516- //NOTE: README.md does not mention "category" renders. they exist for backwards compatibility.
2517- //It says render declaration
2518- //is the same as with a declared scope, which means "renderer_common_id", "renderer",
2519- //"first_result_renderer_common_id", "first_result_renderer", "search_renderer_common_id",
2520- //and "search_renderer"
2521 if (keyword_o.contains(QStringLiteral("renderer_common_id")))
2522 {
2523- qDebug() << "==== UTILs. load. KW CAT. renderer_common_id. keyword&category: " << client.qstr(category);;
2524+ qDebug() << "==== UTILs. load. KW CAT. renderer_common_id. keyword&category: " << client.qstr(keyword_id);
2525 std::string id = keyword_o[QStringLiteral("renderer_common_id")].toString().toStdString();
2526- keyword_catname_renderer[category] = common_templates[id];
2527+ keyword_catname_renderer[keyword_id] = common_templates[id];
2528
2529- qDebug() << "==== UTILs. load. KW CAT. renderer_common_id: "<< client.qstr(keyword_catname_renderer[category]).replace("\n", " ");
2530+ qDebug() << "==== UTILs. load. KW CAT. renderer_common_id: "<< client.qstr(keyword_catname_renderer[keyword_id]).replace("\n", " ");
2531 }
2532 else if (keyword_o.contains(QStringLiteral("renderer")))
2533 {
2534 qDebug() << "==== UTILs. load. KW CAT. renderer";
2535 QJsonObject renderer_o = keyword_o[QStringLiteral("renderer")].toObject();
2536 QJsonDocument rdr_d(renderer_o);
2537- keyword_catname_renderer[category] = client.sstr(rdr_d.toJson());
2538-
2539- qDebug() << "==== UTILs. load. KW CAT. renderer:" << client.qstr(keyword_catname_renderer[category]).replace("\n", " ");
2540- }
2541- else if (keyword_o.contains(QStringLiteral("category_renderer_common_id")))
2542- {
2543- qDebug() << "==== UTILs. load. KW SHARED CAT. category_renderer_common_id";
2544- std::string id = keyword_o[QStringLiteral("category_renderer_common_id")].toString().toStdString();
2545- keyword_catname_renderer[category] = common_templates[id];
2546- }
2547- else if (keyword_o.contains(QStringLiteral("category_renderer")))
2548- {
2549- qDebug() << "==== UTILs. load. KW CAT. category_renderer";
2550- QJsonObject renderer_o = keyword_o[QStringLiteral("category_renderer")].toObject();
2551- QJsonDocument rdr_d(renderer_o);
2552- keyword_catname_renderer[category] = client.sstr(rdr_d.toJson());
2553+ keyword_catname_renderer[keyword_id] = client.sstr(rdr_d.toJson());
2554+ qDebug() << "==== UTILs. load. KW CAT. renderer:" << client.qstr(keyword_catname_renderer[keyword_id]).replace("\n", " ");
2555 }
2556 if (keyword_o.contains(QStringLiteral("search_renderer_common_id")))
2557 {
2558 std::string id = keyword_o[QStringLiteral("search_renderer_common_id")].toString().toStdString();
2559- keyword_catname_search_renderer[category] = common_templates[id];
2560+ keyword_catname_search_renderer[keyword_id] = common_templates[id];
2561 }
2562 else if (keyword_o.contains(QStringLiteral("search_renderer")))
2563 {
2564 QJsonObject renderer_o = keyword_o[QStringLiteral("search_renderer")].toObject();
2565 QJsonDocument rdr_d(renderer_o);
2566- keyword_catname_search_renderer[category] = client.sstr(rdr_d.toJson());
2567+ keyword_catname_search_renderer[keyword_id] = client.sstr(rdr_d.toJson());
2568 }
2569 if (keyword_o.contains(QStringLiteral("link_to_child"))
2570 && keyword_o[QStringLiteral("link_to_child")].toString() == TRUE) {
2571@@ -643,8 +615,6 @@
2572 id_keyword_map[keyword_id] = keyword_;
2573 }
2574 }
2575- //for (auto str : declared_order)
2576- // qDebug() << "==== ORDER. declared scope: " << client.qstr(str);
2577 }
2578
2579 void Query::set_scope_order()
2580@@ -800,6 +770,15 @@
2581 ch_ptr->set_override_search_template(false);
2582 }
2583
2584+ if (!child->login_renderer_incoming_category_id.empty() &&
2585+ !child->login_renderer.empty())
2586+ {
2587+ qDebug() << "==== SCOPE LOGIN";
2588+ ch_ptr->enable_using_login_renderer();
2589+ ch_ptr->set_login_template(child->login_renderer);
2590+ ch_ptr->set_login_renderer_incoming_category_id(child->login_renderer_incoming_category_id);
2591+ }
2592+
2593 ch_ptr->set_only_in_search(child->only_in_search);
2594
2595 ch_ptr->set_source_finder(child->source_finder);
2596@@ -810,6 +789,10 @@
2597 }
2598 }
2599
2600+bool Query::is_using_departments(){
2601+ return using_departments;
2602+}
2603+
2604 void Query::create_departments(us::SearchReplyProxy const &reply_)
2605 {
2606 if (!using_departments)
2607@@ -856,7 +839,6 @@
2608 if (!keyword_child.enabled)
2609 continue;
2610 auto keych_ptr = std::make_shared<AggChildScope>(keyword_child.id);
2611- QString time_str = QDateTime::currentDateTimeUtc().toString();
2612 keych_ptr->set_keyword_scope(true);
2613 auto dept_ptr = std::make_shared<dept>();
2614 dept_ptr->id = keyword_child.id;
2615@@ -890,14 +872,22 @@
2616 keych_ptr->set_department(kw);
2617 keych_ptr->set_keyword(kw);
2618 keych_ptr->set_category_link_to_child(id_keyword_map[kw]->link_to_child);
2619+ qDebug() << "===_ UTILs. KW. link to child: " << QString::number(keych_ptr->category_link_to_child());
2620+ //get rdr if any
2621 if (!keyword_catname_renderer[kw].empty())
2622 {
2623- qDebug()<< "==== UTILs. adding renderer to: " << client.qstr(keyword_child.id);
2624+ qDebug()<< "==== UTILs. KW adding renderer to: " << client.qstr(keyword_child.id);
2625 keych_ptr->set_surface_template(keyword_catname_renderer[kw]);// this is overwritten below for shared cat scopes
2626 }
2627 else
2628 {
2629- qDebug()<< "==== UTILs. renderer not declared: " << client.qstr(keyword_child.id);
2630+ qDebug()<< "==== UTILs. KW renderer not declared: " << client.qstr(keyword_child.id);
2631+ }
2632+ //get search rdr if any
2633+ if (!keyword_catname_search_renderer[kw].empty())
2634+ {
2635+ qDebug()<< "==== UTILs. KW adding searchrenderer to: " << client.qstr(keyword_child.id);
2636+ keych_ptr->set_search_template(keyword_catname_search_renderer[kw]);// this is overwritten below for shared cat scopes
2637 }
2638
2639 bool a_dup = false;
2640@@ -946,7 +936,6 @@
2641 depts[kw] = dept_ptr;
2642 }
2643 }
2644-
2645 auto iter = find(shared_keywords.begin(), shared_keywords.end(), kw);
2646 if (iter != shared_keywords.end())
2647 {
2648@@ -954,18 +943,11 @@
2649 qDebug() << QString("==== UTILs. set ky scope kw %1 as a a shared cat in kw: %2").arg(client.qstr(keych_ptr->id()), client.qstr(kw));
2650 keych_ptr->set_keyword_scope_shared_cat_name(shared_keyword_cats[kw]);
2651
2652- //initially the category is set to false meaning there have been
2653- //no results yet for it and it has not yet been registered
2654- sharedcat_catregistered[shared_keyword_cats[kw]] = false;
2655- // set more default values
2656- keywordSharedCat_isFirstResult[kw] = true;
2657 qDebug() << "===== UTILs. kw: " << client.qstr(kw);
2658 qDebug() << "===== UTILs. keywordSharedCat_isFirstResult[kw] " << keywordSharedCat_isFirstResult[kw];
2659 keywordSharedCat_isSecondResult[kw] = false;
2660 qDebug() << "===== UTILs. kw shared surface: " << client.qstr(keyword_catname_renderer[kw]).replace("\n"," ");
2661 keych_ptr->set_surface_template(keyword_catname_renderer[kw]);
2662-// if (keyword_catname_first_result_renderers[kw].empty())
2663-// keych.set_first_resulte_template(keyword_catname_first_result_renderer[kw]);
2664 }
2665 found = true;
2666 //this break ensures the scope is associated with *first* decared keyword, thus
2667@@ -1002,6 +984,19 @@
2668 auto scope_ptr = std::make_shared<AggChildScope>(ids.first);
2669 scope_ptr->set_category_scope(true);
2670 scope_ptr->set_category_id(cat->id);
2671+
2672+ if (!cat->login_renderer_incoming_category_id.empty() &&
2673+ !cat->login_renderer.empty())
2674+ {
2675+ scope_ptr->enable_using_login_renderer();
2676+ scope_ptr->set_login_template(cat->login_renderer);
2677+ scope_ptr->set_login_renderer_incoming_category_id(cat->login_renderer_incoming_category_id);
2678+ }
2679+
2680+ qDebug() << "==== MKCAT SCOPE cat->login_renderer CAT incoming_category_id: "<< QString::fromStdString(cat->login_renderer_incoming_category_id);
2681+ qDebug() << "==== MKCAT SCOPE cat->login_renderer: "<< QString::fromStdString(cat->login_renderer);
2682+ qDebug() << "==== MKCAT SCOPE login_renderer SCOPE incoming_category_id: "<< QString::fromStdString(scope_ptr->login_renderer_incoming_category_id());
2683+
2684 qDebug() << "==== MKCAT SCOPE cat card"<< QString::number(cat->cardinality);
2685 scope_ptr->set_cardinality(cat->cardinality);
2686 qDebug() << "==== MKCAT SCOPE get cat card"<< QString::number(scope_ptr->cardinality());
2687@@ -1058,6 +1053,7 @@
2688 if (found_scopes.size() == 0 )
2689 continue;
2690
2691+
2692 qDebug() << client.qstr("==== CATKW ID found: %1 %2").arg(client.qstr(kw),client.qstr(id));
2693 auto keych_ptr = std::make_shared<AggChildScope>(id);
2694 std::string local_id = id + ":category=" + cat->id + ":keyword=" + kw;
2695@@ -1066,6 +1062,7 @@
2696 keych_ptr->set_category_scope(true);
2697 keych_ptr->set_category_id(cat->id);
2698 qDebug() << "==== CATKW adding id: " << client.qstr(id);
2699+ qDebug() << "==== CATKW adding. local_id: " << client.qstr(local_id);
2700 qDebug() << "==== CATKW adding kw: " << client.qstr(kw);
2701 type_ids_m[cat->id].emplace_back(keych_ptr->local_id());
2702 keych_ptr->set_keyword(kw);
2703@@ -1073,7 +1070,15 @@
2704 keych_ptr->set_department(cat->dept_id);
2705 keych_ptr->set_department(cat->dept_id);
2706 keych_ptr->set_cardinality(cat->cardinality);
2707-
2708+ //set up login renderer usage, if any
2709+ if (!cat->login_renderer_incoming_category_id.empty() &&
2710+ !cat->login_renderer.empty())
2711+ {
2712+ qDebug() << "==== CATKW: login renderer in use";
2713+ keych_ptr->enable_using_login_renderer();
2714+ keych_ptr->set_login_template(cat->login_renderer);
2715+ keych_ptr->set_login_renderer_incoming_category_id(cat->login_renderer_incoming_category_id);
2716+ }
2717 // set more default values
2718 categoryId_isFirstResult[kw] = true;
2719 categoryId_isSecondResult[kw] = false;
2720@@ -1100,1005 +1105,3 @@
2721 }
2722 }
2723 }
2724-
2725-void Query::handle_current_child_scopes(bool empty_search, us::SearchReplyProxy const& upstream_reply)
2726-{
2727- UNUSED(empty_search)
2728- UNUSED(upstream_reply)
2729- // Cycle through child scopes (in current_scopes) and, if enabled in settings, complete set up
2730- try
2731- {
2732- for (const std::string & local_id : current_scopes)
2733- {
2734- qDebug() << "==== CURRENT. local_id: " << client.qstr(local_id);
2735- std::string id = localId_id_m[local_id];
2736- qDebug() << "==== CURRENT. id: " << client.qstr(id);
2737- std::shared_ptr<AggChildScope> scope = scopes_m[local_id];
2738-
2739- scope->set_proxy(registry_);
2740-
2741- if (!scope->exists()) {
2742- std::string sep = query_store_ == "" ? "name:" : "+";
2743- std::string name = std::string(id, 0, id.find('_'));
2744- query_store_.append(sep + name);
2745- }
2746- else
2747- {
2748- if (!scope->source_finder())
2749- has_one_source = true;
2750- }
2751- }
2752- for (auto source : sources_for_clickstore)
2753- {
2754- AggChildScope scope(source);
2755- scope.set_proxy(registry_);
2756- if (!scope.exists()) {
2757- std::string sep = query_store_ == "" ? "name:" : "+";
2758- std::string name = std::string(source, 0, source.find('_'));
2759- query_store_.append(sep + name);
2760- }
2761- }
2762- }
2763- catch (unity::Exception const& e)
2764- {
2765- qWarning() << "NotFoundException: " << e.what();
2766- }
2767-}
2768-
2769-void Query::noSources(unity::scopes::SearchReplyProxy const& reply) {
2770- std::string MESS_GRID = R"(
2771- {
2772- "schema-version" : 1,
2773- "template" : {
2774- "category-layout" : "grid",
2775- "card-layout": "horizontal",
2776- "card-size": "small",
2777- "non-interactive": true
2778- },
2779- "components" : {
2780- "title" : "title"
2781- }
2782- }
2783- )";
2784-
2785- CategoryRenderer emptyCat(MESS_GRID);
2786- auto messGrid = reply->register_category("emptyMess", _("Welcome"), "", emptyCat);
2787- CategorisedResult res(messGrid);
2788- res.set_uri("http://ubuntu.com");
2789- res["title"]=_("To start using this scope please install sources from the list below");
2790- reply->push(res);
2791-}
2792-
2793-bool Query::hints_exists()
2794-{
2795- auto scopes = registry_->list();
2796- us::MetadataMap::const_iterator scope;
2797- scope = scopes.find(HINTS_SCOPE_ID);
2798- if (scope != scopes.end()) {
2799- auto meta = registry_->get_metadata(HINTS_SCOPE_ID);
2800- hints_scope = meta.proxy();
2801- return true;
2802- }
2803- return false;
2804-}
2805-
2806-void Query::display_local_hints_quickstart(us::SearchReplyProxy const& upstream_reply_)
2807-{
2808- if (!local_hints)
2809- return;
2810- if (!hints_local_json.contains(QStringLiteral("content")))
2811- return;
2812- auto content = hints_local_json[QStringLiteral("content")].toObject();
2813- if (!content.contains(QStringLiteral("categories")))
2814- return;
2815-
2816- QJsonArray cats = content[QStringLiteral("categories")].toArray();
2817- for (const auto & cat_ : cats)
2818- {
2819- auto cat = cat_.toObject();
2820-
2821- std::string id = client.sstr(cat[QStringLiteral("id")].toString());
2822- std::string title = _(client.sstr(cat[QStringLiteral("_title")].toString()).c_str());
2823- QJsonDocument layout_d(cat[QStringLiteral("layout")].toObject());
2824- std::string local_hints_template = client.sstr(layout_d.toJson());
2825- auto category = upstream_reply_->lookup_category(id);
2826- if (!category) {
2827- category = upstream_reply_->register_category(id, title, "", us::CategoryRenderer(local_hints_template));
2828- }
2829- QJsonArray items = cat[QStringLiteral("items")].toArray();
2830- for (const auto & res__ : items)
2831- {
2832- auto res_ = res__.toObject();
2833- us::CategorisedResult res(category);
2834- res.set_title(_(client.sstr(res_[QStringLiteral("_title")].toString()).c_str()));
2835- res["subtitle"] = _(client.sstr(res_[QStringLiteral("_subtitle")].toString()).c_str());
2836- res["description"] = _(client.sstr(res_[QStringLiteral("_description")].toString()).c_str());
2837- std::string uri = "http://www.ubuntu.com";
2838- if (res_.contains(QStringLiteral("hide_in_locales")))
2839- {
2840- QStringList hide_in_locales = res_[QStringLiteral("hide_in_locales")].toString().split(",");
2841- std::string lcRaw = setlocale(LC_ALL, "");
2842- std::string lc_lang = lcRaw.size() > 5 ? lcRaw.substr(0, 2) : "";
2843- std::string lc_country = lcRaw.size() > 5 ? lcRaw.substr(3, 2) : "";
2844- std::string lc = lc_lang + "_" + lc_country;
2845- if (hide_in_locales.contains(client.qstr(lc)))
2846- {
2847- continue;
2848- }
2849- }
2850- if (res_.contains("uri"))
2851- {
2852- uri = client.sstr(res_["uri"].toString());
2853- }
2854- if (res_.contains(QStringLiteral("action")))
2855- {
2856- auto action = res_[QStringLiteral("action")].toObject();
2857- VariantBuilder builder;
2858- builder.add_tuple({
2859- {"id", Variant("Open")},
2860- {"label", Variant(_(client.sstr(action[QStringLiteral("_name")].toString()).c_str()))},
2861- {"uri", Variant(client.sstr(action[QStringLiteral("uri")].toString()))}
2862- });
2863- res["actions"]=builder.end();
2864- if (action.contains(QStringLiteral("uri")))
2865- {
2866- uri = client.sstr(action[QStringLiteral("uri")].toString());
2867- }
2868- }
2869- if (res_.contains(QStringLiteral("oaccount")))
2870- {
2871- auto oaccount = res_[QStringLiteral("oaccount")].toObject();
2872- auto queryScope = client.sstr(oaccount[QStringLiteral("QueryScope")].toString());
2873-
2874- // before framework 15.0.4.4 the service name is the FQ scope id and contains one "_"
2875- // with 15.04.4, it is FQ scope id += _PROVIDER
2876- // we also want the fully qualified scope id to check with registry if it is installed.
2877- // to support both cases:
2878- QStringList parts = oaccount[QStringLiteral("ServiceName")].toString().split("_");
2879- QString provider = oaccount[QStringLiteral("ProviderName")].toString();
2880- QString sc_id = parts[0] + "_" + parts[1];
2881- QString id = sc_id;
2882- if (parts.size() > 2) // framework 15.04.4 or higher
2883- {
2884- id += "_" + provider;
2885- }
2886- us::OnlineAccountClient oa_client(client.sstr(id),
2887- client.sstr(oaccount[QStringLiteral("ServiceType")].toString()),
2888- client.sstr(provider));
2889- bool alreadyLoggedIn = false;
2890- oa_client.refresh_service_statuses();
2891- for (auto const& status : oa_client.get_service_statuses())
2892- {
2893- if (status.service_enabled)
2894- {
2895- alreadyLoggedIn = true;
2896- break;
2897- }
2898- }
2899- // do not display quick start help for scopes that are not registered in the system
2900- auto reg_scopes = registry_->list();
2901- us::MetadataMap::const_iterator scope_it;
2902- scope_it = reg_scopes.find(client.sstr(sc_id));
2903- if (scope_it == reg_scopes.end()) {
2904- qWarning () << QString("%1: OA scope is NOT REGISTERED, skipping for HINTS: %2").arg(oaccount[QStringLiteral("ServiceName")].toString());
2905- continue;
2906- }
2907- if (alreadyLoggedIn) {
2908- //the hints code does this when the person is already logged in to the service.
2909- //auto tmp = oaccount["_loggedin"].toString().toStdString();
2910- //res["title"] = std::string(_(tmp.c_str()));
2911- //
2912- //this continue statement means if the person is logged in, we do not show
2913- //a result for the item
2914- continue;
2915- }
2916- us::CannedQuery query(queryScope);
2917- oa_client.register_account_login_item(res,
2918- query,
2919- OnlineAccountClient::InvalidateResults,
2920- OnlineAccountClient::DoNothing);
2921-
2922- }
2923- if (res_.contains(QStringLiteral("hide_hints")))
2924- {
2925- qDebug() << "=== NHINTS. utils. hide hints found in json";
2926- res["action"] = "hide_hints";
2927- res.set_intercept_activation();
2928- }
2929- res.set_uri(uri);
2930- res.set_art(scope_dir_ + "/images/" + client.sstr(res_["art"].toString()));
2931- upstream_reply_->push(res);
2932- }
2933- }
2934-}
2935-
2936-void Query::display_remote_hints_quickstart(us::SearchReplyProxy const& upstream_reply_)
2937-{
2938- qDebug() << "==== HINTS in display_remote_hints_quickstart()";
2939- Category::SCPtr hint_cat;
2940- SearchMetadata metadata(search_metadata());
2941- auto reply = std::make_shared<ResultForwarder>(upstream_reply_,
2942- [this, &hint_cat](CategorisedResult& res) -> bool {
2943- UNUSED(res)
2944- return true;
2945- });
2946-
2947- subsearch(hints_scope, "", HINTS_THIS_SCOPE, FilterState(),
2948- metadata, reply);
2949-}
2950-
2951-bool Query::hints_hidden() {
2952-
2953- auto filepath1 = QString::fromStdString(cache_dir_) +"/" + hints_file;
2954- auto filepath2 = QString::fromStdString(cache_dir_) +"/" + firstboot;
2955- if (QFile::exists(filepath1) || QFile::exists(filepath2))
2956- return true;
2957- return false; //don't show hints as fall back
2958-}
2959-
2960-void Query::dismiss_hints_quickstart() {
2961- qDebug() << "==== HINTS in dismiss()";
2962- auto filepath= QString::fromStdString(cache_dir_) +"/" + hints_file;
2963- QFile file(filepath);
2964- file.open(QIODevice::WriteOnly);
2965- QString msg = QString("Timestamp for %1 %2").arg(client.qstr(HINTS_THIS_SCOPE), QDateTime::currentDateTime().toString());
2966- file.write(client.sstr(msg).c_str(), client.sstr(msg).size());
2967- file.close();
2968-}
2969-
2970-void Query::handle_keyword_child(unity::scopes::SearchReplyProxy const& upstream_reply,
2971- std::shared_ptr<AggChildScope> scope,
2972- us::CategorisedResult & res){
2973-
2974- // for use in category registration
2975- std::string cat_id;
2976- std::string cat_title;
2977- std::string rdr;
2978-
2979- std::string keyword = scope->keyword();
2980- qDebug() << "==== KW scope: " << client.qstr(scope->id()) << " keyword: " << client.qstr(keyword);;
2981- qDebug() << "==== KW scope shared cat name: " << client.qstr(scope->keyword_scope_shared_cat_name());
2982-
2983- if (!query().query_string().empty())
2984- {
2985- if (!keyword_catname_search_renderer[keyword].empty()) // keyword has declared cat search renderer
2986- {
2987- qDebug() << "==== KW SEARCH keyword using search renderer";
2988- cat_id = keyword + ":keyword:search_renderer:searching:" + cat_title;
2989- rdr = keyword_catname_search_renderer[keyword];
2990- }
2991- else
2992- {
2993- qDebug() << "==== KW SEARCH using incoming result renderer";
2994- cat_id = keyword + ":keyword:incoming_renderer:searching:" + cat_title;
2995- rdr = res.category()->renderer_template().data();
2996- }
2997- if (!upstream_reply->lookup_category(cat_id))
2998- {
2999- catname_catptr[scope->keyword_scope_shared_cat_name()] = upstream_reply->register_category
3000- (
3001- cat_id,
3002- cat_title,
3003- "",
3004- us::CategoryRenderer(rdr)
3005- );
3006- res.set_category(catname_catptr[scope->keyword_scope_shared_cat_name()]);
3007- }
3008- else
3009- res.set_category(upstream_reply->lookup_category(cat_id));
3010- qDebug() << "==== KW SEARCH. cat_id: " << client.qstr(cat_id);
3011- }
3012- else if (!scope->keyword_scope_shared_cat_name().empty())
3013- {//uses shared category
3014- cat_title = _(shared_keyword_cat_titlemsgid[keyword].c_str());
3015- qDebug() << "==== KW SHAREDCAT uses shared cat";
3016- if (!keyword_catname_first_result_renderers[keyword].empty()) // uses first result rdr
3017- {
3018- if ((first_result_owner.empty()) || (first_result_owner == keyword))
3019- {
3020- qDebug() << "==== KW SHAREDCAT uses first result: " << client.qstr(scope->id());
3021- if (keywordSharedCat_isFirstResult[keyword])
3022- {
3023- first_result_owner = keyword;
3024- keywordSharedCat_isFirstResult[keyword] = false;
3025- keywordSharedCat_isSecondResult[keyword] = true;
3026- qDebug() << "==== KW SHAREDCAT is FIRST result: " << client.qstr(scope->keyword_scope_shared_cat_name());
3027- qDebug() << "==== KW SHAREDCAT claims FIRST result: " << client.qstr(scope->keyword_scope_shared_cat_name());
3028- cat_id = keyword + ":keyword:is_shared_category:is_first_result:" + cat_title;
3029- rdr = keyword_catname_first_result_renderers[keyword];
3030- if (!upstream_reply->lookup_category(cat_id))
3031- {
3032- catname_catptr[scope->keyword_scope_shared_cat_name()] = upstream_reply->register_category
3033- (
3034- cat_id,
3035- cat_title,
3036- "",
3037- us::CategoryRenderer(rdr)
3038- );
3039- }
3040- res.set_category(upstream_reply->lookup_category(cat_id));
3041- }
3042- else if ( keywordSharedCat_isSecondResult[keyword] )// second result
3043- {
3044- //keywordSharedCat_isSecondResult[keyword] = false;
3045- qDebug() << "==== KW SHAREDCAT uses FIRST result. is SECOND+ result: " << client.qstr(scope->keyword_scope_shared_cat_name());
3046- if (!keyword_catname_renderer[keyword].empty()) // keyword has declared cat renderer
3047- {
3048- qDebug() << "==== KW SHAREDCAT uses FIRST result. is SECOND+ result. NOT uses surface_template ";
3049- cat_id = scope->keyword_scope_shared_cat_name() + ":keyword:is_shared_category:uses_first_result:second_result+:surface_template:surfacing:" + cat_title;
3050- rdr = keyword_catname_renderer[keyword];
3051- }
3052- else //no surface template found, use incoming result
3053- {
3054- qDebug() << "==== KW SHAREDCAT uses FIRST result. is SECOND+ result. NOT uses result template ";
3055- cat_id = scope->keyword_scope_shared_cat_name() + ":keyword:is_shared_category:uses_first_result:second_result+:result_template:surfacing:" + cat_title;
3056- rdr = res.category()->renderer_template().data();
3057- }
3058- if (!upstream_reply->lookup_category(cat_id))
3059- {
3060- catname_catptr[scope->keyword_scope_shared_cat_name()] = upstream_reply->register_category
3061- (
3062- cat_id,
3063- cat_title,
3064- "",
3065- us::CategoryRenderer(rdr)
3066- );
3067- }
3068- res.set_category(upstream_reply->lookup_category(cat_id));
3069- }
3070- qDebug() << "==== KW SHAREDCAT uses FIRST result. cat_id: " << client.qstr(cat_id);
3071-
3072- }
3073- }
3074- else // not search and does not use declared first result rdr
3075- {
3076- if (!scope->surface_template().empty())// has a decared surface template
3077- {
3078- qDebug() << "==== KW SHAREDCAT not uses first result. uses surface template";
3079- cat_id = scope->keyword_scope_shared_cat_name() + ":keyword:is_shared_category:not_uses_first_result:surface_template:surfacing:" + cat_title;
3080- rdr = scope->surface_template();
3081- qDebug() << "==== KW SHAREDCAT. rdr" << client.qstr(rdr).replace("\n", " ");
3082- }
3083- else // use result template
3084- {
3085- qDebug() << "==== KW SHAREDCAT not uses first result. NOT uses surface template";
3086- cat_id = scope->keyword_scope_shared_cat_name() + ":keyword:is_shared_category:not_uses_first_result:result_template:surfacing:" + cat_title;
3087- rdr = res.category()->renderer_template().data();
3088- }
3089- if (catname_catptr.find(scope->keyword_scope_shared_cat_name()) == catname_catptr.end())
3090- {
3091- if (categoryId_linkToChildSpecified.find(scope->category_id()) != categoryId_linkToChildSpecified.end())
3092- {
3093- if (!upstream_reply->lookup_category(cat_id))
3094- {
3095- catname_catptr[scope->keyword_scope_shared_cat_name()] = upstream_reply->register_category
3096- (
3097- cat_id,
3098- cat_title,
3099- "",
3100- us::CannedQuery(categoryId_linkToChildSpecified[scope->category_id()]),
3101- us::CategoryRenderer(rdr)
3102- );
3103- }
3104- else
3105- {
3106- res.set_category(upstream_reply->lookup_category(cat_id));
3107- }
3108- }
3109- else if (!upstream_reply->lookup_category(cat_id))
3110- {
3111- catname_catptr[scope->keyword_scope_shared_cat_name()] = upstream_reply->register_category
3112- (
3113- cat_id,
3114- cat_title,
3115- "",
3116- us::CategoryRenderer(rdr)
3117- );
3118- res.set_category(upstream_reply->lookup_category(cat_id));
3119- }
3120- else
3121- {
3122- res.set_category(upstream_reply->lookup_category(cat_id));
3123- }
3124- }
3125- else
3126- {
3127- res.set_category(upstream_reply->lookup_category(cat_id));
3128- qDebug() << "==== KW SHAREDCAT not uses first result. cat_id: " << client.qstr(cat_id);
3129- }
3130- }
3131- }
3132- else // keyword scope does not use a shared category
3133- {
3134- qDebug() << "==== KW not SHAREDCAT";
3135- qDebug() << "==== KW not SHAREDCAT. first result for scope: " << client.qstr(scope->local_id());
3136- scope->set_is_first_result(false);
3137- //cat_title = inc_res_cat_title;
3138- //if (cat_title.empty()){
3139- cat_title = registry_->get_metadata(scope->id()).display_name();
3140- //}
3141- qDebug() << "==== KW not SHAREDCAT. local_id: " << client.qstr(scope->local_id());
3142- qDebug() << "==== KW not SHAREDCAT. cat_ttile: " << client.qstr(cat_title);
3143- //if (keyword_catname_renderer.find(keyword) != keyword_catname_renderer.end())// has a decared surface template
3144- if (!keyword_catname_renderer[keyword].empty())
3145- {
3146- qDebug() << "==== KW not SHAREDCAT. declared renderer ";
3147- qDebug() << "==== KW not SHAREDCAR. keyword: " << client.qstr(scope->keyword());
3148- cat_id = scope->keyword() + ":keyword:not_shared_category:declared_renderer:surfacing:" + cat_title;
3149- rdr = keyword_catname_renderer[keyword];
3150- }
3151- else // use result template
3152- {
3153- qDebug() << "==== KW not SHAREDCAT. incoming renderer";
3154- cat_id = scope->keyword() + ":keyword:not_shared_category:incoming_renderer:surfacing:" + cat_title;
3155- rdr = res.category()->renderer_template().data();
3156- }
3157- qDebug() << "==== KW not SHAREDCAT. cat_id: " << client.qstr(cat_id);
3158- if (scope->category_link_to_child())
3159- {
3160- if (!upstream_reply->lookup_category(cat_id))
3161- {
3162- upstream_reply->register_category
3163- (
3164- cat_id,
3165- cat_title,
3166- "",
3167- us::CannedQuery(scope->id(), query().query_string(), scope->child_department()),
3168- us::CategoryRenderer(rdr)
3169- );
3170- }
3171- res.set_category(upstream_reply->lookup_category(cat_id));
3172- }
3173- else
3174- {
3175- if (!upstream_reply->lookup_category(cat_id))
3176- {
3177- upstream_reply->register_category
3178- (
3179- cat_id,
3180- cat_title,
3181- "",
3182- us::CategoryRenderer(rdr)
3183- );
3184- }
3185- res.set_category(upstream_reply->lookup_category(cat_id));
3186- }
3187- }
3188-}
3189-
3190-void Query::handle_category_child(unity::scopes::SearchReplyProxy const& upstream_reply,
3191- std::shared_ptr<AggChildScope> scope,
3192- us::CategorisedResult & res){
3193-
3194- // for use in category registration
3195- std::string cat_id;
3196- std::string cat_title;
3197- std::string rdr;
3198-
3199- std::string category_id = scope->category_id();
3200- qDebug() << "==== CATREG RESULT scope: " << client.qstr(scope->id()) << " title: " <<client.qstr(cat_title);
3201-
3202- if (!categoryId_titleMsgid[category_id].empty())
3203- cat_title = _(categoryId_titleMsgid[category_id].c_str());
3204- else
3205- cat_title = "";
3206-
3207- bool uses_first_result_rdr = categoryId_first_result_renderers.find(category_id) != categoryId_first_result_renderers.end();
3208-
3209- if (!query().query_string().empty())
3210- {
3211- qDebug()<<client.qstr("==== CATREG scope's (%1) search template: %2").arg(client.qstr(scope->local_id()), client.qstr(scope->search_template()).replace("\n", " "));
3212- qDebug() << "==== CATREG search renderer:" << client.qstr(scope->search_template()).replace("\n", "");
3213- if (!scope->search_template().empty())
3214- {
3215- qDebug() << "==== CATREG using search renderer";
3216- cat_id = scope->local_id() + ":category:search-renderer:searching:" + cat_title;
3217- rdr = scope->search_template();
3218- }
3219- else
3220- {
3221- qDebug() << "==== CATREG using incoming eesult renderer";
3222- cat_id = scope->local_id() + ":category:incoming-renderer:searching:" + cat_title;
3223- rdr = res.category()->renderer_template().data();
3224- }
3225- if (!upstream_reply->lookup_category(cat_id))
3226- {
3227- upstream_reply->register_category
3228- (
3229- cat_id,
3230- cat_title,
3231- "",
3232- us::CategoryRenderer(rdr)
3233- );
3234- res.set_category(upstream_reply->lookup_category(cat_id));
3235- }
3236- else
3237- res.set_category(upstream_reply->lookup_category(cat_id));
3238- qDebug() << "==== CATREG search cat_id: " << client.qstr(cat_id);
3239- }
3240- else if (uses_first_result_rdr )
3241- {
3242- qDebug() << "==== CATREG uses first result. scope category_id: " << client.qstr(category_id);
3243-
3244- if ((first_result_owner.empty()) || (first_result_owner == category_id))
3245- {
3246- // perform fallbacks, if any
3247- if(!scope->first_common_template_id().empty())
3248- {
3249- qDebug() << "==== first check FALLB. scope->first_common_template_id():" << client.qstr(scope->first_common_template_id());
3250- std::string res_str = us::Variant(res.serialize()).serialize_json();
3251- auto change = this->client.check_result_fallbacks(res_str,
3252- scope->first_common_template_id(),
3253- this->get_common_templates_fallbacks());
3254- if (!std::get<0>(change).empty())
3255- {
3256- res[std::get<0>(change)] = std::get<1>(change);
3257- }
3258- }
3259- if (categoryId_isFirstResult[category_id])
3260- {
3261- first_result_owner = category_id;
3262- cat_id = category_id + ":category:uses-first-result:first-result-rdr:surfacing";
3263- qDebug() << "==== CATREG. uses FIRST result. FIRST result. cat_id: " << client.qstr(cat_id);
3264- rdr = categoryId_first_result_renderers[category_id];
3265- categoryId_isFirstResult[category_id] = false;
3266- categoryId_isSecondResult[category_id] = true;
3267- if (categoryId_linkToChildSpecified.find(scope->category_id()) != categoryId_linkToChildSpecified.end())
3268- {
3269- qDebug() << "==== CATREG. uses FIRST result. FIRST result. LINK to child ";
3270- if (!upstream_reply->lookup_category(cat_id))
3271- {
3272- catname_catptr[category_id] = upstream_reply->register_category
3273- (
3274- cat_id,
3275- cat_title,
3276- "",
3277- us::CannedQuery(categoryId_linkToChildSpecified[scope->category_id()]),
3278- us::CategoryRenderer(rdr)
3279- );
3280- scope->set_category(catname_catptr[category_id]);
3281- }
3282- res.set_category(upstream_reply->lookup_category(cat_id));
3283- }
3284- else
3285- {
3286- qDebug() << "==== CATREG. uses FIRST result. FIRST result. NO LINK to child ";
3287- if (!upstream_reply->lookup_category(cat_id))
3288- {
3289- catname_catptr[category_id] = upstream_reply->register_category
3290- (
3291- cat_id,
3292- cat_title,
3293- "",
3294- us::CategoryRenderer(rdr)
3295- );
3296- scope->set_category(catname_catptr[category_id]);
3297- }
3298- res.set_category(upstream_reply->lookup_category(cat_id));
3299- }
3300- }
3301- else if ( categoryId_isSecondResult[category_id] )// second result
3302- {
3303- //categoryId_isSecondResult[category_id] = false;
3304- qDebug() << "==== CATREG RESULT uses FIRST result is SECOND result: " << client.qstr(category_id);
3305- // perform fallbacks, if any
3306- if(!scope->surface_common_template_id().empty())
3307- {
3308- std::string res_str = us::Variant(res.serialize()).serialize_json();
3309- auto change = this->client.check_result_fallbacks(res_str,
3310- scope->surface_common_template_id(),
3311- this->get_common_templates_fallbacks());
3312-
3313- if (!std::get<0>(change).empty())
3314- {
3315- res[std::get<0>(change)] = std::get<1>(change);
3316- }
3317- }
3318- if (!scope->surface_template().empty()) // keyword has declared cat renderer
3319- {
3320- qDebug() << "==== CATREG RESULT uses FIRST result. is SECOND result. uses surface_renderer ";
3321- cat_id = category_id + ":category:uses-first-result:second-or-later-result:surface-rdr:surfacing:" + cat_title;
3322- rdr = scope->surface_template();
3323- }
3324- else // use result template
3325- {
3326- qDebug() << "==== CATREG RESULT uses FIRST result. is SECOND result. NOT uses surface_template ";
3327- cat_id = category_id + ":category:uses-first-result:second-or-later-result:incoming-rdr:surfacing:" + cat_title;
3328- rdr = res.category()->renderer_template().data();
3329- }
3330- qDebug() << "==== CATREG. uses FIRST result. SECOND result. cat_id: " << client.qstr(cat_id);
3331- if (!upstream_reply->lookup_category(cat_id))
3332- {
3333- catname_catptr[category_id] = upstream_reply->register_category
3334- (
3335- cat_id,
3336- cat_title,
3337- "",
3338- us::CategoryRenderer(rdr)
3339- );
3340- scope->set_category(catname_catptr[category_id]);
3341- }
3342- res.set_category(upstream_reply->lookup_category(cat_id));
3343- }
3344- }
3345- }
3346- else // not search and does not use first result rdr
3347- {
3348- qDebug() << "==== CATREG RESULT not uses first result. res title: " << client.qstr(res["title"].get_string());
3349- // if uses common template, perform fallbacks, if any
3350- qDebug () << "==== CATREG RESULT not uses first result: common id" << client.qstr(scope->surface_common_template_id());
3351- if(!scope->surface_common_template_id().empty())
3352- {
3353- qDebug() << "==== surface check FALLB. scope->surface_common_template_id():" << client.qstr(scope->surface_common_template_id());
3354- std::string res_str = us::Variant(res.serialize()).serialize_json();
3355- auto change = this->client.check_result_fallbacks(res_str,
3356- scope->surface_common_template_id(),
3357- this->get_common_templates_fallbacks());
3358- if (!std::get<0>(change).empty())
3359- {
3360- res[std::get<0>(change)] = std::get<1>(change);
3361- }
3362- }
3363- if (!scope->surface_template().empty())// has a decared surface template
3364- {
3365- qDebug() << "==== CATREG RESULT not uses first result. uses surface template";
3366- cat_id = scope->category_id() + ":category:not-uses-first-result:surface-rdr:surfacing:" + cat_title;
3367- rdr = scope->surface_template();
3368- }
3369- else // use result template
3370- {
3371- qDebug() << "==== CATREG RESULT not uses first result. NOT uses result template";
3372- cat_id = scope->category_id() + ":category:not-uses-first-result:incoming-rdr:surfacing:" + cat_title;
3373- rdr = res.category()->renderer_template().data();
3374- }
3375- qDebug() << "== rdr: " << client.qstr(rdr);
3376- if (catname_catptr.find(category_id) == catname_catptr.end())
3377- {
3378- qDebug() << "==== CATREG RESULT not uses first result. category not found in catname_catptr";
3379- if (categoryId_linkToChildSpecified.find(scope->category_id()) != categoryId_linkToChildSpecified.end())
3380- {
3381- qDebug() << "==== CATREG RESULT not uses first result. category not found in catname_catptr. is link to child";
3382- if (!upstream_reply->lookup_category(cat_id))
3383- {
3384- qDebug() << "==== CATREG RESULT not uses first result. category not found in catname_catptr. is link to child. category not found on lookup";
3385- catname_catptr[category_id] = upstream_reply->register_category
3386- (
3387- cat_id,
3388- cat_title,
3389- "",
3390- us::CannedQuery(categoryId_linkToChildSpecified[scope->category_id()]),
3391- us::CategoryRenderer(rdr)
3392- );
3393- }
3394- res.set_category(upstream_reply->lookup_category(cat_id));
3395- }
3396- else
3397- {
3398- qDebug() << "==== CATREG RESULT not uses first result. category not found in catname_catptr. not link to child";
3399- if (!upstream_reply->lookup_category(cat_id))
3400- {
3401- qDebug() << "==== CATREG RESULT not uses first result. category not found in catname_catptr. not link to child. category not found on lookup";
3402- catname_catptr[category_id] = upstream_reply->register_category
3403- (
3404- cat_id,
3405- cat_title,
3406- "",
3407- us::CategoryRenderer(rdr)
3408- );
3409- }
3410- res.set_category(upstream_reply->lookup_category(cat_id));
3411- }
3412- }
3413- else // category is found so use it
3414- res.set_category(catname_catptr[category_id]);
3415- qDebug() << "==== CATREG RESULT not uses first result. cat_id" << client.qstr(cat_id);
3416- }
3417-}
3418-
3419-void Query::handle_declared_child(unity::scopes::SearchReplyProxy const& upstream_reply,
3420- std::shared_ptr<AggChildScope> scope,
3421- us::CategorisedResult & res,
3422- std::string const& inc_res_cat_id,
3423- std::string const& inc_res_cat_title) {
3424-
3425- // for use in category registration
3426- std::string cat_id;
3427- std::string cat_title;
3428- std::string rdr;
3429-
3430- qDebug() << "==== DECLARED. scope is declared: " << client.qstr(scope->local_id());
3431- bool set_declared_category = false;
3432-
3433- // if child_category set, filter out others by adding "dont_use" key to result
3434- // also impose max category results if set
3435- qDebug() << QString("=== DECLARED: child_categpry: %1").arg(client.qstr(scope->child_category()));
3436- qDebug() << QString("=== DECLARED: inc_res_cat_id: %1").arg(client.qstr(inc_res_cat_id));
3437- if (!scope->child_category().empty())
3438- {
3439- if (client.qstr(inc_res_cat_id) != client.qstr(scope->child_category()))
3440- {
3441- res["dont_use"] = "true";
3442- }
3443- else if (scope->using_child_category_max_results())
3444- {
3445- scope->inc_result_idx(); // allows constraining num results to child_category_max_results declaration
3446- if (scope->result_idx() > scope->child_category_max_results() )
3447- {
3448- res["dont_use"] = "true";
3449- }
3450- }
3451- }
3452-
3453- // set the category tiile
3454- if (scope->using_category_title_incoming() )
3455- {
3456- scope->set_category_title(inc_res_cat_title);
3457- }
3458- else if (scope->using_category_title_display_name() )
3459- {
3460- scope->set_category_title(scope->metadata()->display_name());
3461- }
3462-
3463- cat_title = scope->category_title();
3464-
3465- // dont search of incoming renderer when searching
3466- if (!query().query_string().empty())
3467- {
3468- set_declared_category = true;
3469- if (!scope->search_template().empty())
3470- {
3471- qDebug() << "==== DECLARED FIRST RESULT: SEARCH using search renderer";
3472- cat_id = scope->local_id() + ":declared:search-rdr:searching:" + cat_title;
3473- rdr = scope->search_template();
3474- }
3475- else
3476- {
3477- qDebug() << "==== DECLARED FIRST RESULT: SEARCH using incoming result renderer";
3478- cat_id = scope->local_id() + ":declared:incomiing_rdr:searching:" + cat_title;
3479- rdr = res.category()->renderer_template().data();
3480- }
3481- scope->set_category_id(cat_id);
3482- }
3483- else if (scope->is_first_result())
3484- {
3485- qDebug() << "==== DECLARED FIRST RESULT for scope: " << client.qstr(scope->local_id());
3486- scope->set_is_first_result(false);
3487- scope->set_is_second_result(true);
3488- set_declared_category = true;
3489-
3490- cat_id = scope->id() + ":declared_default" + cat_title;
3491- if (first_result_owner.empty() || first_result_owner == scope->id())
3492- {
3493- first_result_owner = scope->id();
3494- if (!scope->first_result_template().empty())
3495- {
3496- qDebug() << "==== DECLARED FIRST RESULT: using first result template";
3497- cat_id = scope->local_id() + ":declared:first-result:first-result-rdr:surfacing:" + cat_title;
3498- rdr = scope->first_result_template();
3499- }
3500- else if (!scope->surface_template().empty())
3501- {
3502- cat_id = scope->local_id() + ":declared:first-result:surface-rdr:surfacing:" + cat_title;
3503- rdr = scope->surface_template();
3504- qDebug() << "==== DECLARED FIRST RESULT: using surface template";
3505- }
3506- else
3507- {
3508- cat_id = scope->local_id() + ":declared:first-result:incoming-rdr:surfacing:" + cat_title;
3509- rdr = res.category()->renderer_template().data();
3510- qDebug() << "==== DECLARED FIRST RESULT: using incoming template";
3511- }
3512- }
3513- else if (!scope->surface_template().empty())
3514- {
3515- cat_id = scope->local_id() + ":declared:first-result:first-result-owner:surface-rdr:surfacing:" + cat_title;
3516- rdr = scope->surface_template();
3517- qDebug() << "==== DECLARED FIRST RESULT: first-result-ownwer. using surface template";
3518- }
3519- else
3520- {
3521- cat_id = scope->local_id() + ":declared:first-result:first-result-owner:incoming-rdr:surfacing:" + cat_title;
3522- rdr = res.category()->renderer_template().data();
3523- }
3524- scope->set_category_id(cat_id);
3525- }
3526- else if( //second result but not using different renders for first result vs rest of the results
3527- scope->is_second_result()
3528- && scope->first_result_template().empty()
3529- )
3530- {
3531- qDebug() << "==== DECLARED SECOND RESULT (not using first/rest renderers) for scope: " << client.qstr(scope->local_id());
3532- scope->set_is_first_result(false);
3533- scope->set_is_second_result(false);
3534- set_declared_category = false;
3535- }
3536- else if( //second result and using different renderers for first result vs rest of the results
3537- scope->is_second_result()
3538- && !scope->first_result_template().empty()
3539- )
3540- {
3541- qDebug() << "==== DECLARED SECOND RESULT (using first/rest renderers) for scope: " << client.qstr(scope->local_id());
3542- scope->set_is_first_result(false);
3543- scope->set_is_second_result(false);
3544- set_declared_category = true;
3545-
3546- if (scope->using_category_title_incoming() )
3547- {
3548- scope->set_category_title(inc_res_cat_title);
3549- }
3550- else if (scope->using_category_title_display_name() )
3551- {
3552- scope->set_category_title(scope->metadata()->display_name());
3553- }
3554-
3555- cat_title = scope->category_title();
3556-
3557- if (scope->category_link_to_child())
3558- {
3559- if (query().query_string().empty()) // surfacing
3560- {
3561- if (scope->override_surface_template()) // use declared renderer
3562- {
3563- cat_id = scope->local_id() + ":declared:not-first-result:link:surface-rdr:surfacing:" + cat_title;
3564- rdr = scope->surface_template();
3565- qDebug() << "=== rdr: " << QString::fromStdString(rdr);
3566- }
3567- else // use incoming renderer
3568- {
3569- cat_id = scope->local_id() + ":declared:not-first-result:link:incoming-rdr:surfacing:" + cat_title;
3570- rdr = res.category()->renderer_template().data();
3571- }
3572- }
3573- else // user is searching
3574- {
3575- if (scope->override_search_template())
3576- {
3577- cat_id = scope->local_id() + ":declared:not-first-result:link:search-rdr:searching:" + cat_title;
3578- rdr = scope->search_template().data();
3579- }
3580- else
3581- {
3582- cat_id = scope->local_id() + ":declared:not-first-result:link:incoming-rdr:searching:" + cat_title;
3583- rdr = res.category()->renderer_template().data();
3584- }
3585- }
3586- }
3587- else // no category link to child
3588- {
3589- if (query().query_string().empty())
3590- {
3591- if (scope->override_surface_template())
3592- {
3593- cat_id = scope->local_id() + ":declared:nolink:surface-rdr:surfacing:" + cat_title;
3594- rdr = scope->surface_template().data();
3595- qDebug() << QString("=== rdr: %1").arg(client.qstr(rdr));
3596- }
3597- else if (!scope->first_result_template().empty())
3598- {
3599- cat_id = scope->local_id() + ":declared:nolink:incoming-rdr:surfacing:" + cat_title;
3600- rdr = res.category()->renderer_template().data();
3601- }
3602- }
3603- else
3604- {
3605- if (scope->override_search_template())
3606- {
3607- cat_id = scope->local_id() + "declared:nolink:search-rdr:searching:" + cat_title;
3608- rdr = scope->search_template().data();
3609- }
3610- else
3611- {
3612- cat_id = scope->local_id() + "declared:nolink:incoming-rdr:searching:" + cat_title;
3613- rdr = res.category()->renderer_template().data();
3614- }
3615- }
3616- }
3617- }
3618- if (set_declared_category)
3619- { //category needs to be registered
3620- qDebug() << "==== DECLARED. set_declared_category";
3621- // if needed, get renderer from result_category_to_common_template
3622- if (common_templates.find(child_scopes_m[scope->local_id()]->result_category_id_to_common_template[res.category()->id()]) != common_templates.end())
3623- {
3624- qDebug() << "==== DECLARED. current category:" << client.qstr(res.category()->id());
3625- std::string common_template_id = child_scopes_m[scope->local_id()]->result_category_id_to_common_template[res.category()->id()];
3626- qDebug() << "==== DECLARED. using common_template_id: " << client.qstr(common_template_id);
3627- std::string cat_id = common_template_id + ":" + scope->local_id() + cat_title;
3628-
3629- // if the common category has not yet been registered, register it
3630- if (!upstream_reply->lookup_category(cat_id))
3631- {
3632- std::string template_to_use = common_templates[child_scopes_m[scope->local_id()]->result_category_id_to_common_template[res.category()->id()]];
3633- std::string res_str = us::Variant(res.serialize()).serialize_json();
3634- auto change = this->client.check_result_fallbacks(res_str,
3635- scope->surface_common_template_id(),
3636- this->get_common_templates_fallbacks());
3637- if (!std::get<0>(change).empty())
3638- {
3639- res[std::get<0>(change)] = std::get<1>(change);
3640- }
3641- if (scope->category_link_to_child())
3642- {
3643- if (!upstream_reply->lookup_category(cat_id))
3644- {
3645- registered_common_categories[cat_id] = upstream_reply->register_category(cat_id, scope->category_title(), "",
3646- us::CannedQuery(scope->id(), query().query_string(), scope->child_department()),
3647- us::CategoryRenderer(template_to_use));
3648- }
3649- res.set_category(upstream_reply->lookup_category(cat_id));
3650- }
3651- else
3652- {
3653- if (!upstream_reply->lookup_category(cat_id))
3654- {
3655- registered_common_categories[cat_id] = upstream_reply->register_category(cat_id, scope->category_title(), "", us::CategoryRenderer(template_to_use));
3656- }
3657- res.set_category(upstream_reply->lookup_category(cat_id));
3658- }
3659- }
3660- }
3661- else
3662- {
3663- qDebug() << "=== DECLARED set_declared_category is TRUE. cat_id: " << client.qstr(cat_id);
3664- if (!rdr.empty()) // should not ever be empty!
3665- {
3666- if (!upstream_reply->lookup_category(cat_id))
3667- {
3668- qDebug() << "=== DECLARED set_declared_category is TRUE -- lookup cat not found: " << client.qstr(cat_id);
3669- if (scope->category_link_to_child())
3670- {
3671- qDebug() << "=== DECLARED set_declared_category is TRUE -- link to child ";
3672- auto cat = upstream_reply->register_category
3673- (
3674- cat_id,
3675- cat_title,
3676- "",
3677- us::CannedQuery(scope->id(), query().query_string(), scope->child_department()),
3678- us::CategoryRenderer(rdr)
3679- );
3680- scope->set_category(cat);
3681- scope->set_category_id(cat_id);
3682- }
3683- else
3684- {
3685- qDebug() << "=== DECLARED set_declared_category is TRUE -- NOT link to child . RDR:" << client.qstr(rdr);
3686- auto cat = upstream_reply->register_category
3687- (
3688- cat_id,
3689- cat_title,
3690- "",
3691- us::CategoryRenderer(rdr)
3692- );
3693- scope->set_category(cat);
3694- scope->set_category_id(cat_id);
3695- }
3696- }
3697- res.set_category(upstream_reply->lookup_category(cat_id));
3698- }
3699- }
3700- }
3701- else // category already registered, so get it and set remember its id
3702- {
3703- res.set_category(upstream_reply->lookup_category(scope->category_id()));
3704- scope->set_category_id(scope->category_id());
3705- }
3706-
3707- //for child scopes that declare attribute swaps, do them
3708- for (std::shared_ptr<std::pair<std::string,std::string>> swap : this->child_scopes_m[scope->local_id()]->swap_result_attributes)
3709- {
3710- qDebug() << "==== trying to swap attributes";
3711- try
3712- {
3713- if (!res[swap->first].get_string().empty())
3714- {
3715- qDebug() << "==== swap first: " << client.qstr(swap->first);
3716- res[swap->second] = res[swap->first];
3717- }
3718- }
3719- catch (unity::LogicException)
3720- {
3721- qDebug() << "==== failed to swap attributes";
3722- qWarning() << QString("Scope %1 result does not contain attribute 2").arg(client.qstr(scope->local_id()), client.qstr(swap->second));
3723- }
3724- }
3725-}

Subscribers

People subscribed via source and target branches