Merge lp:~gary-lasker/software-center/recommendations-opt-out into lp:software-center

Proposed by Gary Lasker
Status: Merged
Merged at revision: 2880
Proposed branch: lp:~gary-lasker/software-center/recommendations-opt-out
Merge into: lp:software-center
Diff against target: 324 lines (+125/-25)
6 files modified
data/ui/gtk3/SoftwareCenter.ui (+16/-6)
softwarecenter/ui/gtk3/app.py (+36/-7)
softwarecenter/ui/gtk3/views/catview_gtk.py (+1/-1)
softwarecenter/ui/gtk3/widgets/containers.py (+4/-0)
softwarecenter/ui/gtk3/widgets/recommendations.py (+54/-9)
test/gtk3/test_recommendations_widgets.py (+14/-2)
To merge this branch: bzr merge lp:~gary-lasker/software-center/recommendations-opt-out
Reviewer Review Type Date Requested Status
software-store-developers Pending
Review via email: mp+97928@code.launchpad.net

Description of the change

This branch implements the recommendations opt-out feature as specified in the most recent Software Center spec from mpt (https://wiki.ubuntu.com/SoftwareCenter/Recommendations#Opting_in_and_out). It adds a new menu item to the "View" menu that reads "Turn Off Recommendations" for the case where the user has previously opted in.

In the case where the user has not yet opted in, this menu item reads "Turn On Recommendations...", and if activated it displays a dialog to show the disclaimer for uploading of data (that same text that is shown in the panel below).

Unfortunately, this branch is light on tests for the small amout of new stuff, but in actuality there is not much new code for which tests will be terribly important. The "heavy" code is already tested -- this is really just some small UI additions. Nevertheless, I apologize for that, and a follow-on branch is forthcoming that will improve this coverage. In the interest of getting this last remaining important piece of recommender functionality merged in time for it to be covered under our current outstanding string freeze/UI freeze exception bug 956779, I'm submitting the MP now.

Thanks!

To post a comment you must log in.
2858. By Gary Lasker

trunkify

2859. By Gary Lasker

make a new class RecommendationsOptInDialog in the recommendations module and use it

2860. By Gary Lasker

add new tests so that all of the recommendations widget panels are tested

Revision history for this message
Gary Lasker (gary-lasker) wrote :

Just a note that I factored the opt-in dialog code to its own class and added some new unit tests so that all of the recommendations panel widget types are tested now.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks for your branch. This is fine but I did some fixes in lp:~mvo/software-center/recommendations-opt-out/
 please double check them and merge back.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'data/ui/gtk3/SoftwareCenter.ui'
--- data/ui/gtk3/SoftwareCenter.ui 2011-11-29 18:52:13 +0000
+++ data/ui/gtk3/SoftwareCenter.ui 2012-03-19 01:17:20 +0000
@@ -158,12 +158,6 @@
158 </object>158 </object>
159 </child>159 </child>
160 <child>160 <child>
161 <object class="GtkSeparatorMenuItem" id="separator_login">
162 <property name="visible">True</property>
163 <property name="can_focus">False</property>
164 </object>
165 </child>
166 <child>
167 <object class="GtkMenuItem" id="menuitem_reinstall_purchases">161 <object class="GtkMenuItem" id="menuitem_reinstall_purchases">
168 <property name="visible">True</property>162 <property name="visible">True</property>
169 <property name="can_focus">False</property>163 <property name="can_focus">False</property>
@@ -454,6 +448,22 @@
454 <signal name="toggled" handler="on_menuitem_add_to_launcher_toggled" swapped="no"/>448 <signal name="toggled" handler="on_menuitem_add_to_launcher_toggled" swapped="no"/>
455 </object>449 </object>
456 </child>450 </child>
451 <child>
452 <object class="GtkSeparatorMenuItem" id="separator_recommendations_opt_in">
453 <property name="visible">True</property>
454 <property name="can_focus">False</property>
455 </object>
456 </child>
457 <child>
458 <object class="GtkMenuItem" id="menuitem_recommendations">
459 <property name="visible">True</property>
460 <property name="can_focus">False</property>
461 <property name="use_action_appearance">False</property>
462 <property name="label" translatable="yes">Turn On Recommendations...</property>
463 <property name="use_underline">True</property>
464 <signal name="activate" handler="on_menuitem_recommendations_activate" swapped="no"/>
465 </object>
466 </child>
457 </object>467 </object>
458 </child>468 </child>
459 </object>469 </object>
460470
=== modified file 'softwarecenter/ui/gtk3/app.py'
--- softwarecenter/ui/gtk3/app.py 2012-03-16 17:49:04 +0000
+++ softwarecenter/ui/gtk3/app.py 2012-03-19 01:17:20 +0000
@@ -89,6 +89,8 @@
89 get_appmanager)89 get_appmanager)
90from softwarecenter.ui.gtk3.session.viewmanager import (90from softwarecenter.ui.gtk3.session.viewmanager import (
91 ViewManager, get_viewmanager)91 ViewManager, get_viewmanager)
92from softwarecenter.ui.gtk3.widgets.recommendations import (
93 RecommendationsOptInDialog)
9294
93from softwarecenter.config import get_config95from softwarecenter.config import get_config
94from softwarecenter.backend import get_install_backend96from softwarecenter.backend import get_install_backend
@@ -370,14 +372,14 @@
370372
371 # Adapt menu entries373 # Adapt menu entries
372 supported_menuitem = self.builder.get_object(374 supported_menuitem = self.builder.get_object(
373 "menuitem_view_supported_only")375 "menuitem_view_supported_only")
374 supported_menuitem.set_label(self.distro.get_supported_filter_name())376 supported_menuitem.set_label(self.distro.get_supported_filter_name())
375 file_menu = self.builder.get_object("menu1")377 file_menu = self.builder.get_object("menu1")
376378
377 if not self.distro.DEVELOPER_URL:379 if not self.distro.DEVELOPER_URL:
378 help_menu = self.builder.get_object("menu_help")380 help_menu = self.builder.get_object("menu_help")
379 developer_separator = self.builder.get_object(381 developer_separator = self.builder.get_object(
380 "separator_developer")382 "separator_developer")
381 help_menu.remove(developer_separator)383 help_menu.remove(developer_separator)
382 developer_menuitem = self.builder.get_object("menuitem_developer")384 developer_menuitem = self.builder.get_object("menuitem_developer")
383 help_menu.remove(developer_menuitem)385 help_menu.remove(developer_menuitem)
@@ -495,6 +497,9 @@
495497
496 def on_available_pane_created(self, widget):498 def on_available_pane_created(self, widget):
497 self.available_pane.searchentry.grab_focus()499 self.available_pane.searchentry.grab_focus()
500 rec_panel = self.available_pane.cat_view.recommended_for_you_panel
501 self._update_recommendations_menuitem(
502 opted_in=rec_panel.recommender_agent.is_opted_in())
498 # connect a signal to monitor the recommendations opt-in state and503 # connect a signal to monitor the recommendations opt-in state and
499 # persist the recommendations uuid on an opt-in504 # persist the recommendations uuid on an opt-in
500 self.available_pane.cat_view.recommended_for_you_panel.connect(505 self.available_pane.cat_view.recommended_for_you_panel.connect(
@@ -507,13 +512,25 @@
507 #~ def on_installed_pane_created(self, widget):512 #~ def on_installed_pane_created(self, widget):
508 #~ pass513 #~ pass
509514
510 def _on_recommendations_opt_in(self, agent, recommender_uuid):515 def _on_recommendations_opt_in(self, rec_panel, recommender_uuid):
511 self.recommender_uuid = recommender_uuid516 self.recommender_uuid = recommender_uuid
517 self._update_recommendations_menuitem(opted_in=True)
512518
513 def _on_recommendations_opt_out(self):519 def _on_recommendations_opt_out(self, rec_panel):
514 # if the user opts back out of the recommender service, we520 # if the user opts back out of the recommender service, we
515 # reset the UUID to indicate it521 # reset the recommender UUID to indicate it
516 self.recommender_uuid = ""522 self.recommender_uuid = ""
523 self._update_recommendations_menuitem(opted_in=False)
524
525 def _update_recommendations_menuitem(self, opted_in):
526 recommendations_menuitem = self.builder.get_object(
527 "menuitem_recommendations")
528 if opted_in:
529 recommendations_menuitem.set_label(
530 _(u"Turn Off Recommendations"))
531 else:
532 recommendations_menuitem.set_label(
533 _(u"Turn On Recommendations..."))
517534
518 def _on_update_software_center_agent_finished(self, pid, condition):535 def _on_update_software_center_agent_finished(self, pid, condition):
519 LOG.info("software-center-agent finished with status %i" %536 LOG.info("software-center-agent finished with status %i" %
@@ -764,7 +781,19 @@
764 from softwarecenter.backend.scagent import SoftwareCenterAgent781 from softwarecenter.backend.scagent import SoftwareCenterAgent
765 self.scagent = SoftwareCenterAgent()782 self.scagent = SoftwareCenterAgent()
766 self.scagent.connect("available-for-me",783 self.scagent.connect("available-for-me",
767 self._available_for_me_result)784 self._available_for_me_result)
785
786 def on_menuitem_recommendations_activate(self, menu_item):
787 rec_panel = self.available_pane.cat_view.recommended_for_you_panel
788 if rec_panel.recommender_agent.is_opted_in():
789 rec_panel.opt_out_of_recommendations_service()
790 else:
791 # build and show the opt-in dialog
792 opt_in_dialog = RecommendationsOptInDialog(self.icons)
793 res = opt_in_dialog.run()
794 opt_in_dialog.destroy()
795 if res == Gtk.ResponseType.YES:
796 rec_panel.opt_in_to_recommendations_service()
768797
769 def on_menuitem_reinstall_purchases_activate(self, menuitem):798 def on_menuitem_reinstall_purchases_activate(self, menuitem):
770 self.view_manager.set_active_view(ViewPages.AVAILABLE)799 self.view_manager.set_active_view(ViewPages.AVAILABLE)
@@ -773,7 +802,7 @@
773 if self.available_for_me_query:802 if self.available_for_me_query:
774 # we already have the list of available items, so just show it803 # we already have the list of available items, so just show it
775 self.available_pane.on_previous_purchases_activated(804 self.available_pane.on_previous_purchases_activated(
776 self.available_for_me_query)805 self.available_for_me_query)
777 else:806 else:
778 # fetch the list of available items and show it807 # fetch the list of available items and show it
779 self._create_scagent_if_needed()808 self._create_scagent_if_needed()
780809
=== modified file 'softwarecenter/ui/gtk3/views/catview_gtk.py'
--- softwarecenter/ui/gtk3/views/catview_gtk.py 2012-03-15 21:19:02 +0000
+++ softwarecenter/ui/gtk3/views/catview_gtk.py 2012-03-19 01:17:20 +0000
@@ -579,7 +579,7 @@
579 self.vbox.remove(self.recommended_for_you_in_cat)579 self.vbox.remove(self.recommended_for_you_in_cat)
580 self.recommended_for_you_in_cat = RecommendationsPanelCategory(580 self.recommended_for_you_in_cat = RecommendationsPanelCategory(
581 self,581 self,
582 category)582 category)
583 # only show the panel in the categories view when the user583 # only show the panel in the categories view when the user
584 # is opted in to the recommender service584 # is opted in to the recommender service
585 # FIXME: this is needed vs. a simple hide() on the widget because585 # FIXME: this is needed vs. a simple hide() on the widget because
586586
=== modified file 'softwarecenter/ui/gtk3/widgets/containers.py'
--- softwarecenter/ui/gtk3/widgets/containers.py 2012-03-12 12:43:52 +0000
+++ softwarecenter/ui/gtk3/widgets/containers.py 2012-03-19 01:17:20 +0000
@@ -564,6 +564,10 @@
564 self.more = MoreLink()564 self.more = MoreLink()
565 self.header.pack_end(self.more, False, False, 0)565 self.header.pack_end(self.more, False, False, 0)
566566
567 def remove_more_button(self):
568 if hasattr(self, "more"):
569 self.header.remove(self.more)
570
567 def render_header(self, cr, a, border_radius, assets):571 def render_header(self, cr, a, border_radius, assets):
568572
569 if hasattr(self, "more"):573 if hasattr(self, "more"):
570574
=== modified file 'softwarecenter/ui/gtk3/widgets/recommendations.py'
--- softwarecenter/ui/gtk3/widgets/recommendations.py 2012-03-15 22:23:21 +0000
+++ softwarecenter/ui/gtk3/widgets/recommendations.py 2012-03-19 01:17:20 +0000
@@ -91,7 +91,7 @@
91 # add the new stuff91 # add the new stuff
92 self.recommended_for_you_content = FlowableGrid()92 self.recommended_for_you_content = FlowableGrid()
93 self.add(self.recommended_for_you_content)93 self.add(self.recommended_for_you_content)
94 self.spinner_notebook.show_spinner(_("Receiving recommendations…"))94 self.spinner_notebook.show_spinner(_(u"Receiving recommendations…"))
95 # get the recommendations from the recommender agent95 # get the recommendations from the recommender agent
96 self.recommended_for_you_cat = RecommendedForYouCategory(96 self.recommended_for_you_cat = RecommendedForYouCategory(
97 subcategory=self.subcategory)97 subcategory=self.subcategory)
@@ -136,6 +136,12 @@
136 ),136 ),
137 }137 }
138138
139 TURN_ON_RECOMMENDATIONS_TEXT = _(u"Turn On Recommendations")
140 RECOMMENDATIONS_OPT_IN_TEXT = _(u"To make recommendations, "
141 "Ubuntu Software Center "
142 "will occasionally send to Canonical an anonymous list "
143 "of software currently installed.")
144
139 def __init__(self, catview):145 def __init__(self, catview):
140 RecommendationsPanel.__init__(self, catview)146 RecommendationsPanel.__init__(self, catview)
141 self.subcategory = None147 self.subcategory = None
@@ -156,7 +162,7 @@
156 self.add(self.recommended_for_you_content)162 self.add(self.recommended_for_you_content)
157163
158 # opt in button164 # opt in button
159 button = Gtk.Button(_("Turn On Recommendations"))165 button = Gtk.Button(self.TURN_ON_RECOMMENDATIONS_TEXT)
160 button.connect("clicked", self._on_opt_in_button_clicked)166 button.connect("clicked", self._on_opt_in_button_clicked)
161 hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL)167 hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL)
162 hbox.pack_start(button, False, False, 0)168 hbox.pack_start(button, False, False, 0)
@@ -164,15 +170,16 @@
164 self.opt_in_button = button # for tests170 self.opt_in_button = button # for tests
165171
166 # opt in text172 # opt in text
167 text = _("To make recommendations, Ubuntu Software Center "173 text = self.RECOMMENDATIONS_OPT_IN_TEXT
168 "will occasionally send to Canonical an anonymous list "
169 "of software currently installed.")
170 label = Gtk.Label(text)174 label = Gtk.Label(text)
171 label.set_alignment(0, 0.5)175 label.set_alignment(0, 0.5)
172 label.set_line_wrap(True)176 label.set_line_wrap(True)
173 vbox.pack_start(label, False, False, 0)177 vbox.pack_start(label, False, False, 0)
174178
175 def _on_opt_in_button_clicked(self, button):179 def _on_opt_in_button_clicked(self, button):
180 self.opt_in_to_recommendations_service()
181
182 def opt_in_to_recommendations_service(self):
176 # we upload the user profile here, and only after this is finished183 # we upload the user profile here, and only after this is finished
177 # do we fire the request for recommendations and finally display184 # do we fire the request for recommendations and finally display
178 # them here -- a spinner is shown for this process (the spec185 # them here -- a spinner is shown for this process (the spec
@@ -180,6 +187,14 @@
180 # progress info)187 # progress info)
181 self._upload_user_profile_and_get_recommendations()188 self._upload_user_profile_and_get_recommendations()
182189
190 def opt_out_of_recommendations_service(self):
191 if self.recommended_for_you_content:
192 self.recommended_for_you_content.destroy()
193 self._show_opt_in_view()
194 self.remove_more_button()
195 self.show_all()
196 self.emit("recommendations-opt-out")
197
183 def _upload_user_profile_and_get_recommendations(self):198 def _upload_user_profile_and_get_recommendations(self):
184 # initiate upload of the user profile here199 # initiate upload of the user profile here
185 self._upload_user_profile()200 self._upload_user_profile()
@@ -256,18 +271,42 @@
256 self.hide()271 self.hide()
257272
258273
274class RecommendationsOptInDialog(Gtk.MessageDialog):
275 """
276 Dialog to display the recommendations opt-in message when opt-in is
277 initiated from the menu.
278 """
279 def __init__(self, icons):
280 Gtk.MessageDialog.__init__(self, flags=Gtk.DialogFlags.MODAL,
281 type=Gtk.MessageType.INFO)
282 self.set_title("")
283 icon_name = "softwarecenter"
284 if icons.has_icon(icon_name):
285 icon = Gtk.Image.new_from_icon_name(icon_name,
286 Gtk.IconSize.DIALOG)
287 self.set_image(icon)
288 icon.show()
289 self.format_secondary_text(
290 RecommendationsPanelLobby.RECOMMENDATIONS_OPT_IN_TEXT)
291 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
292 self.add_button(RecommendationsPanelLobby.TURN_ON_RECOMMENDATIONS_TEXT,
293 Gtk.ResponseType.YES)
294 self.set_default_response(Gtk.ResponseType.YES)
295
296
259# test helpers297# test helpers
260def get_test_window():298def get_test_window(panel_type="lobby"):
261 import softwarecenter.log299 import softwarecenter.log
262 softwarecenter.log.root.setLevel(level=logging.DEBUG)300 softwarecenter.log.root.setLevel(level=logging.DEBUG)
263 fmt = logging.Formatter("%(name)s - %(message)s", None)301 fmt = logging.Formatter("%(name)s - %(message)s", None)
264 softwarecenter.log.handler.setFormatter(fmt)302 softwarecenter.log.handler.setFormatter(fmt)
265303
266 # this is *way* to complicated we should *not* need a CatView304 # this is *way* too complicated we should *not* need a CatView
267 # here! see FIXME in RecommendationsPanel.__init__()305 # here! see FIXME in RecommendationsPanel.__init__()
268 from softwarecenter.ui.gtk3.views.catview_gtk import CategoriesViewGtk306 from softwarecenter.ui.gtk3.views.catview_gtk import CategoriesViewGtk
269 from softwarecenter.testutils import (307 from softwarecenter.testutils import (
270 get_test_db, get_test_pkg_info, get_test_gtk3_icon_cache)308 get_test_db, get_test_pkg_info, get_test_gtk3_icon_cache,
309 get_test_categories)
271 cache = get_test_pkg_info()310 cache = get_test_pkg_info()
272 db = get_test_db()311 db = get_test_db()
273 icons = get_test_gtk3_icon_cache()312 icons = get_test_gtk3_icon_cache()
@@ -277,7 +316,13 @@
277 db,316 db,
278 icons)317 icons)
279318
280 view = RecommendationsPanelLobby(catview)319 if panel_type is "lobby":
320 view = RecommendationsPanelLobby(catview)
321 elif panel_type is "category":
322 cats = get_test_categories(db)
323 view = RecommendationsPanelCategory(catview, cats[0])
324 else: # panel_type is "details":
325 view = RecommendationsPanelDetails(catview)
281326
282 win = Gtk.Window()327 win = Gtk.Window()
283 win.connect("destroy", lambda x: Gtk.main_quit())328 win.connect("destroy", lambda x: Gtk.main_quit())
284329
=== modified file 'test/gtk3/test_recommendations_widgets.py'
--- test/gtk3/test_recommendations_widgets.py 2012-03-01 11:29:55 +0000
+++ test/gtk3/test_recommendations_widgets.py 2012-03-19 01:17:20 +0000
@@ -16,8 +16,20 @@
1616
17class TestRecommendationsWidgets(unittest.TestCase):17class TestRecommendationsWidgets(unittest.TestCase):
1818
19 def test_recommendations_widgets(self):19 def test_recommendations_lobby(self):
20 win = get_test_window()20 win = get_test_window(panel_type="lobby")
21 win.show_all()
22 GObject.timeout_add(TIMEOUT, lambda: win.destroy())
23 Gtk.main()
24
25 def test_recommendations_category(self):
26 win = get_test_window(panel_type="category")
27 win.show_all()
28 GObject.timeout_add(TIMEOUT, lambda: win.destroy())
29 Gtk.main()
30
31 def test_recommendations_details(self):
32 win = get_test_window(panel_type="details")
21 win.show_all()33 win.show_all()
22 GObject.timeout_add(TIMEOUT, lambda: win.destroy())34 GObject.timeout_add(TIMEOUT, lambda: win.destroy())
23 Gtk.main()35 Gtk.main()

Subscribers

People subscribed via source and target branches