Merge lp:~stolowski/unity-scopes-shell/session-id into lp:unity-scopes-shell

Proposed by Paweł Stołowski
Status: Merged
Approved by: Pete Woods
Approved revision: 139
Merged at revision: 139
Proposed branch: lp:~stolowski/unity-scopes-shell/session-id
Merge into: lp:unity-scopes-shell
Prerequisite: lp:~stolowski/unity-scopes-shell/expandable-widget
Diff against target: 422 lines (+223/-4)
9 files modified
src/Unity/previewstack.cpp (+6/-1)
src/Unity/previewstack.h (+3/-1)
src/Unity/scope.cpp (+30/-1)
src/Unity/scope.h (+6/-0)
src/Unity/utils.cpp (+10/-0)
src/Unity/utils.h (+2/-0)
tests/data/mock-scope/mock-scope.cpp (+4/-1)
tests/previewtest.cpp (+1/-0)
tests/resultstest.cpp (+161/-0)
To merge this branch: bzr merge lp:~stolowski/unity-scopes-shell/session-id
Reviewer Review Type Date Requested Status
Marcus Tomlinson (community) Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
Review via email: mp+233214@code.launchpad.net

Commit message

Generate session_id (uuid) and pass it with search and preview request via SearchMetadata / ActionMetadata hints.

Description of the change

Generate session_id (uuid) and pass it with search and preview request via SearchMetadata / ActionMetadata hints. This requires https://code.launchpad.net/~stolowski/unity-scopes-api/session-id/+merge/233210 ; i'm assuming it lands together with other changes in scopes API, so it depends on scopes api 0.6.5.

The session id is re-created whenever a new search query is entered; appending or removing characters from existing search query keeps current session id.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
136. By Paweł Stołowski

Maintain and pass query_id.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

99 + // don't regenerate it if currenty query appends to previous query or removes

currenty -> currently

100 + // caharcters from previous query.

caharcters -> characters

101 + if (m_session_id.isNull() ||
102 + m_searchQuery.isEmpty() || search_query.isEmpty() ||
103 + !(m_searchQuery.startsWith(search_query) ||
104 + search_query.startsWith(m_searchQuery))) {

this is quite hard to read, and I think its wrong. Rather do something like this:

bool search_empty = m_searchQuery.isEmpty() || search_query.isEmpty();

bool updates_previous = m_searchQuery.startsWith(search_query) || search_query.startsWith(m_searchQuery);

if((m_session_id.isNull() || search_empty) && !updates_previous)

review: Needs Fixing
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Also could we add a simple test for this?

137. By Paweł Stołowski

Refactored the conditional for re-creating session_id to be more readable.

138. By Paweł Stołowski

Added tests for session id in search metadata.

139. By Paweł Stołowski

Added test for passing session id with preview request.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

> 99 + // don't regenerate it if currenty query appends to previous
> query or removes
>
> currenty -> currently
>
> 100 + // caharcters from previous query.
>
> caharcters -> characters
>
> 101 + if (m_session_id.isNull() ||
> 102 + m_searchQuery.isEmpty() || search_query.isEmpty() ||
> 103 + !(m_searchQuery.startsWith(search_query) ||
> 104 + search_query.startsWith(m_searchQuery))) {
>
> this is quite hard to read, and I think its wrong. Rather do something like
> this:
>
> bool search_empty = m_searchQuery.isEmpty() || search_query.isEmpty();
>
> bool updates_previous = m_searchQuery.startsWith(search_query) ||
> search_query.startsWith(m_searchQuery);
>
> if((m_session_id.isNull() || search_empty) && !updates_previous)

I think the logic in this conditional was correct though I agree it wasn't readable. The catch in it is that the startsWith check can only be made with non-empty strings (otherwise it yields true in cases where it shouldn't); I've refactored it a bit with helper variables.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

> Also could we add a simple test for this?

Added.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

Looks good, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Unity/previewstack.cpp'
2--- src/Unity/previewstack.cpp 2014-07-18 17:10:39 +0000
3+++ src/Unity/previewstack.cpp 2014-09-04 09:34:32 +0000
4@@ -31,6 +31,7 @@
5 #include <QJsonDocument>
6 #include <QJsonObject>
7 #include <QJsonValue>
8+#include <QUuid>
9
10 #include <unity/scopes/ActionMetadata.h>
11 #include <unity/scopes/Scope.h>
12@@ -76,9 +77,10 @@
13 return false;
14 }
15
16-void PreviewStack::setAssociatedScope(scopes_ng::Scope* scope)
17+void PreviewStack::setAssociatedScope(scopes_ng::Scope* scope, QUuid const& session_id)
18 {
19 m_associatedScope = scope;
20+ m_session_id = session_id;
21 }
22
23 void PreviewStack::loadForResult(scopes::Result::SPtr const& result)
24@@ -119,6 +121,9 @@
25 if (!extra_data.is_null()) {
26 metadata.set_scope_data(extra_data);
27 }
28+ if (!m_session_id.isNull()) {
29+ metadata["session-id"] = uuidToString(m_session_id).toStdString();
30+ }
31
32 std::shared_ptr<PreviewDataReceiver> listener(new PreviewDataReceiver(m_activePreview));
33 std::weak_ptr<ScopeDataReceiverBase> wl(listener);
34
35=== modified file 'src/Unity/previewstack.h'
36--- src/Unity/previewstack.h 2014-05-19 09:58:14 +0000
37+++ src/Unity/previewstack.h 2014-09-04 09:34:32 +0000
38@@ -28,6 +28,7 @@
39 #include <QSharedPointer>
40 #include <QMultiMap>
41 #include <QPointer>
42+#include <QUuid>
43
44 #include <unity/scopes/PreviewWidget.h>
45 #include <unity/scopes/Result.h>
46@@ -59,7 +60,7 @@
47
48 void setWidgetColumnCount(int columnCount) override;
49 int widgetColumnCount() const override;
50- void setAssociatedScope(scopes_ng::Scope*);
51+ void setAssociatedScope(scopes_ng::Scope*, QUuid const&);
52
53 private Q_SLOTS:
54 void widgetTriggered(QString const&, QString const&, QVariantMap const&);
55@@ -79,6 +80,7 @@
56 std::shared_ptr<ScopeDataReceiverBase> m_lastActivation;
57
58 unity::scopes::Result::SPtr m_previewedResult;
59+ QUuid m_session_id;
60 };
61
62 } // namespace scopes_ng
63
64=== modified file 'src/Unity/scope.cpp'
65--- src/Unity/scope.cpp 2014-08-25 15:35:21 +0000
66+++ src/Unity/scope.cpp 2014-09-04 09:34:32 +0000
67@@ -54,6 +54,7 @@
68 #include <unity/scopes/PreviewWidget.h>
69 #include <unity/scopes/SearchMetadata.h>
70 #include <unity/scopes/ActionMetadata.h>
71+#include <unity/scopes/Variant.h>
72
73 namespace scopes_ng
74 {
75@@ -67,6 +68,7 @@
76 const int RESULTS_TTL_LARGE = 3600000; // 1 hour
77
78 Scope::Scope(QObject *parent) : unity::shell::scopes::ScopeInterface(parent)
79+ , m_query_id(0)
80 , m_formFactor("phone")
81 , m_isActive(false)
82 , m_searchInProgress(false)
83@@ -625,6 +627,10 @@
84
85 if (m_proxy) {
86 scopes::SearchMetadata meta(QLocale::system().name().toStdString(), m_formFactor.toStdString());
87+ if (!m_session_id.isNull()) {
88+ meta["session-id"] = uuidToString(m_session_id).toStdString();
89+ }
90+ meta["query-id"] = unity::scopes::Variant(m_query_id);
91 if (m_settings) {
92 QVariant remoteSearch(m_settings->get("remote-content-search"));
93 if (remoteSearch.toString() == QString("none")) {
94@@ -940,6 +946,21 @@
95 */
96
97 if (m_searchQuery.isNull() || search_query != m_searchQuery) {
98+ // regenerate session id uuid if previous or current search string is empty or
99+ // if current and previous query have no common prefix;
100+ // don't regenerate it if current query appends to previous query or removes
101+ // characters from previous query.
102+ bool search_empty = m_searchQuery.isEmpty() || search_query.isEmpty();
103+
104+ // only check for common prefix if search is not empty
105+ bool common_prefix = (!search_empty) && (m_searchQuery.startsWith(search_query) || search_query.startsWith(m_searchQuery));
106+
107+ if (m_session_id.isNull() || search_empty || !common_prefix) {
108+ m_session_id = QUuid::createUuid();
109+ m_query_id = 0;
110+ } else {
111+ ++m_query_id;
112+ }
113 m_searchQuery = search_query;
114
115 // atm only empty query can have a filter state
116@@ -1049,7 +1070,7 @@
117 }
118
119 PreviewStack* stack = new PreviewStack(nullptr);
120- stack->setAssociatedScope(this);
121+ stack->setAssociatedScope(this, m_session_id);
122 stack->loadForResult(result);
123 return stack;
124 }
125@@ -1084,6 +1105,14 @@
126 return m_resultsDirty;
127 }
128
129+QString Scope::sessionId() const {
130+ return uuidToString(m_session_id);
131+}
132+
133+int Scope::queryId() const {
134+ return m_query_id;
135+}
136+
137 void Scope::activateUri(QString const& uri)
138 {
139 /*
140
141=== modified file 'src/Unity/scope.h'
142--- src/Unity/scope.h 2014-08-25 15:35:21 +0000
143+++ src/Unity/scope.h 2014-09-04 09:34:32 +0000
144@@ -31,6 +31,7 @@
145 #include <QMultiMap>
146 #include <QSet>
147 #include <QGSettings>
148+#include <QUuid>
149
150 // scopes
151 #include <unity/scopes/ActivationResponse.h>
152@@ -159,6 +160,9 @@
153 bool resultsDirty() const;
154 virtual unity::scopes::ScopeProxy proxy_for_result(unity::scopes::Result::SPtr const& result) const;
155
156+ QString sessionId() const;
157+ int queryId() const;
158+
159 public Q_SLOTS:
160 void invalidateResults();
161
162@@ -198,6 +202,8 @@
163 static unity::scopes::Department::SCPtr findDepartmentById(unity::scopes::Department::SCPtr const& root, std::string const& id);
164 static unity::scopes::Department::SCPtr findUpdateNode(DepartmentNode* node, unity::scopes::Department::SCPtr const& scopeNode);
165
166+ QUuid m_session_id;
167+ int m_query_id;
168 QString m_searchQuery;
169 QString m_noResultsHint;
170 QString m_formFactor;
171
172=== modified file 'src/Unity/utils.cpp'
173--- src/Unity/utils.cpp 2014-03-25 16:34:26 +0000
174+++ src/Unity/utils.cpp 2014-09-04 09:34:32 +0000
175@@ -123,4 +123,14 @@
176 }
177 }
178
179+Q_DECL_EXPORT QString uuidToString(QUuid const& uuid)
180+{
181+ // workaround: use mid to get rid of curly braces; see https://bugreports.qt-project.org/browse/QTBUG-885
182+ auto const uuid_str = uuid.toString();
183+ if (uuid_str.startsWith("{")) {
184+ return uuid_str.mid(1, 36);
185+ }
186+ return uuid_str;
187+}
188+
189 } // namespace scopes_ng
190
191=== modified file 'src/Unity/utils.h'
192--- src/Unity/utils.h 2014-03-25 16:34:26 +0000
193+++ src/Unity/utils.h 2014-09-04 09:34:32 +0000
194@@ -22,6 +22,7 @@
195
196 // Qt
197 #include <QVariant>
198+#include <QUuid>
199
200 #include <unity/scopes/Variant.h>
201
202@@ -31,6 +32,7 @@
203 Q_DECL_EXPORT QVariant scopeVariantToQVariant(unity::scopes::Variant const& variant);
204 Q_DECL_EXPORT unity::scopes::Variant qVariantToScopeVariant(QVariant const& variant);
205 Q_DECL_EXPORT QVariant backgroundUriToVariant(QString const& uri);
206+Q_DECL_EXPORT QString uuidToString(QUuid const& uuid);
207
208 } // namespace scopes_ng
209
210
211=== modified file 'tests/data/mock-scope/mock-scope.cpp'
212--- tests/data/mock-scope/mock-scope.cpp 2014-09-04 09:34:32 +0000
213+++ tests/data/mock-scope/mock-scope.cpp 2014-09-04 09:34:32 +0000
214@@ -233,6 +233,8 @@
215 res.set_title("result for: \"" + query_ + "\"");
216 res.set_art("art");
217 res.set_dnd_uri("test:dnd_uri");
218+ res["session-id"] = search_metadata()["session-id"].get_string();
219+ res["query-id"] = Variant(search_metadata()["query-id"].get_int());
220 res.set_intercept_activation();
221 reply->push(res);
222 }
223@@ -326,13 +328,14 @@
224 }
225
226 PreviewWidgetList widgets;
227- PreviewWidget w1(R"({"id": "hdr", "type": "header", "components": {"title": "title", "subtitle": "uri", "attribute-1": "extra-data"}})");
228+ PreviewWidget w1(R"({"id": "hdr", "type": "header", "components": {"title": "title", "subtitle": "uri", "attribute-1": "extra-data", "session-id": "session-id-val"}})");
229 PreviewWidget w2(R"({"id": "img", "type": "image", "components": {"source": "art"}, "zoomable": false})");
230 widgets.push_back(w1);
231 widgets.push_back(w2);
232 reply->push(widgets);
233
234 reply->push("extra-data", Variant("foo"));
235+ reply->push("session-id-val", Variant(action_metadata()["session-id"]));
236 }
237
238 private:
239
240=== modified file 'tests/previewtest.cpp'
241--- tests/previewtest.cpp 2014-09-04 09:34:32 +0000
242+++ tests/previewtest.cpp 2014-09-04 09:34:32 +0000
243@@ -115,6 +115,7 @@
244 QCOMPARE(props[QString("title")].toString(), QString::fromStdString(result->title()));
245 QCOMPARE(props[QString("subtitle")].toString(), QString::fromStdString(result->uri()));
246 QCOMPARE(props[QString("attribute-1")].toString(), QString("foo"));
247+ QCOMPARE(props[QString("session-id")].toString(), m_scope->sessionId());
248
249 idx = preview_widgets->index(1);
250 QCOMPARE(preview_widgets->data(idx, PreviewWidgetModel::RoleWidgetId).toString(), QString("img"));
251
252=== modified file 'tests/resultstest.cpp'
253--- tests/resultstest.cpp 2014-08-26 13:47:35 +0000
254+++ tests/resultstest.cpp 2014-09-04 09:34:32 +0000
255@@ -260,6 +260,167 @@
256 QCOMPARE(results->data(idx, ResultsModel::Roles::RoleCategoryId), categories->data(categories->index(0), Categories::Roles::RoleCategoryId));
257 }
258
259+ void testSessionId()
260+ {
261+ std::string lastSessionId;
262+
263+ performSearch(m_scope, QString(""));
264+
265+ QVERIFY(!m_scope->sessionId().isEmpty());
266+ QCOMPARE(m_scope->queryId(), 0);
267+
268+ {
269+ auto categories = m_scope->categories();
270+ QVERIFY(categories->rowCount() > 0);
271+ QVariant results_var = categories->data(categories->index(0), Categories::Roles::RoleResults);
272+ auto results = results_var.value<ResultsModel*>();
273+ QVERIFY(results->rowCount() > 0);
274+
275+ auto idx = results->index(0);
276+ auto result = results->data(idx, ResultsModel::Roles::RoleResult).value<std::shared_ptr<unity::scopes::Result>>();
277+
278+ auto sessionId = (*result)["session-id"].get_string();
279+ auto queryId = (*result)["query-id"].get_int();
280+
281+ // mock scope should send session-id and query-id it received back via custom result's values
282+ QCOMPARE(sessionId, m_scope->sessionId().toStdString());
283+ QCOMPARE(queryId, m_scope->queryId());
284+ QCOMPARE(queryId, 0);
285+
286+ lastSessionId = sessionId;
287+ }
288+
289+ // new search
290+ performSearch(m_scope, QString("m"));
291+ {
292+ auto categories = m_scope->categories();
293+ QVERIFY(categories->rowCount() > 0);
294+ QVariant results_var = categories->data(categories->index(0), Categories::Roles::RoleResults);
295+ auto results = results_var.value<ResultsModel*>();
296+ QVERIFY(results->rowCount() > 0);
297+
298+ auto idx = results->index(0);
299+ auto result = results->data(idx, ResultsModel::Roles::RoleResult).value<std::shared_ptr<unity::scopes::Result>>();
300+
301+ auto sessionId = (*result)["session-id"].get_string();
302+ auto queryId = (*result)["query-id"].get_int();
303+
304+ // mock scope should send session-id and query-id it received back via custom result's values
305+ QCOMPARE(sessionId, m_scope->sessionId().toStdString());
306+ QCOMPARE(queryId, m_scope->queryId());
307+ QCOMPARE(queryId, 0);
308+
309+ // new session id
310+ QVERIFY(sessionId != lastSessionId);
311+
312+ lastSessionId = sessionId;
313+ }
314+
315+ // appends to previous search
316+ performSearch(m_scope, QString("met"));
317+ {
318+ auto categories = m_scope->categories();
319+ QVERIFY(categories->rowCount() > 0);
320+ QVariant results_var = categories->data(categories->index(0), Categories::Roles::RoleResults);
321+ auto results = results_var.value<ResultsModel*>();
322+ QVERIFY(results->rowCount() > 0);
323+
324+ auto idx = results->index(0);
325+ auto result = results->data(idx, ResultsModel::Roles::RoleResult).value<std::shared_ptr<unity::scopes::Result>>();
326+
327+ auto sessionId = (*result)["session-id"].get_string();
328+ auto queryId = (*result)["query-id"].get_int();
329+
330+ // mock scope should send session-id and query-id it received back via custom result's values
331+ QCOMPARE(sessionId, m_scope->sessionId().toStdString());
332+ QCOMPARE(queryId, m_scope->queryId());
333+ QCOMPARE(queryId, 1);
334+
335+ // session id unchanged
336+ QVERIFY(sessionId == lastSessionId);
337+
338+ lastSessionId = sessionId;
339+ }
340+
341+ // removes characters from previous search
342+ performSearch(m_scope, QString("me"));
343+ {
344+ auto categories = m_scope->categories();
345+ QVERIFY(categories->rowCount() > 0);
346+ QVariant results_var = categories->data(categories->index(0), Categories::Roles::RoleResults);
347+ auto results = results_var.value<ResultsModel*>();
348+ QVERIFY(results->rowCount() > 0);
349+
350+ auto idx = results->index(0);
351+ auto result = results->data(idx, ResultsModel::Roles::RoleResult).value<std::shared_ptr<unity::scopes::Result>>();
352+
353+ auto sessionId = (*result)["session-id"].get_string();
354+ auto queryId = (*result)["query-id"].get_int();
355+
356+ // mock scope should send session-id and query-id it received back via custom result's values
357+ QCOMPARE(sessionId, m_scope->sessionId().toStdString());
358+ QCOMPARE(queryId, m_scope->queryId());
359+ QCOMPARE(queryId, 2);
360+
361+ // session id unchanged
362+ QVERIFY(sessionId == lastSessionId);
363+
364+ lastSessionId = sessionId;
365+ }
366+
367+ // new non-empty search again
368+ performSearch(m_scope, QString("iron"));
369+ {
370+ auto categories = m_scope->categories();
371+ QVERIFY(categories->rowCount() > 0);
372+ QVariant results_var = categories->data(categories->index(0), Categories::Roles::RoleResults);
373+ auto results = results_var.value<ResultsModel*>();
374+ QVERIFY(results->rowCount() > 0);
375+
376+ auto idx = results->index(0);
377+ auto result = results->data(idx, ResultsModel::Roles::RoleResult).value<std::shared_ptr<unity::scopes::Result>>();
378+
379+ auto sessionId = (*result)["session-id"].get_string();
380+ auto queryId = (*result)["query-id"].get_int();
381+
382+ // mock scope should send session-id and query-id it received back via custom result's values
383+ QCOMPARE(sessionId, m_scope->sessionId().toStdString());
384+ QCOMPARE(queryId, m_scope->queryId());
385+ QCOMPARE(queryId, 0);
386+
387+ // new session id
388+ QVERIFY(sessionId != lastSessionId);
389+
390+ lastSessionId = sessionId;
391+ }
392+
393+ // new empty search again
394+ performSearch(m_scope, QString(""));
395+ {
396+ auto categories = m_scope->categories();
397+ QVERIFY(categories->rowCount() > 0);
398+ QVariant results_var = categories->data(categories->index(0), Categories::Roles::RoleResults);
399+ auto results = results_var.value<ResultsModel*>();
400+ QVERIFY(results->rowCount() > 0);
401+
402+ auto idx = results->index(0);
403+ auto result = results->data(idx, ResultsModel::Roles::RoleResult).value<std::shared_ptr<unity::scopes::Result>>();
404+
405+ auto sessionId = (*result)["session-id"].get_string();
406+ auto queryId = (*result)["query-id"].get_int();
407+
408+ // mock scope should send session-id and query-id it received back via custom result's values
409+ QCOMPARE(sessionId, m_scope->sessionId().toStdString());
410+ QCOMPARE(queryId, m_scope->queryId());
411+ QCOMPARE(queryId, 0);
412+
413+ // new session id
414+ QVERIFY(sessionId != lastSessionId);
415+
416+ lastSessionId = sessionId;
417+ }
418+ }
419+
420 void testResultMetadata()
421 {
422 performSearch(m_scope, QString("metadata"));

Subscribers

People subscribed via source and target branches

to all changes: