Repository: mozmeao/snippets-service

Issue:
Add OPEN_APPLICATIONS_MENU, HIGHLIGHT_FEATURE and OPEN_PREFERENCES_PAGE actions to links
Add additional special links to

 - OPEN_PREFERENCES_PAGE
 - HIGHLIGHT_FEATURE and
 - OPEN_APPLICATIONS_MENU

Note that OPEN_APPLICATIONS_MENU is already present but limited to `appMenu` page. After we deploy this we want convert all existing snippets to use the new link and delete the old code. 


Discussion:

Resolving patch:
diff --git a/snippets/base/admin/adminmodels.py b/snippets/base/admin/adminmodels.py
--- a/snippets/base/admin/adminmodels.py
+++ b/snippets/base/admin/adminmodels.py
@@ -583,10 +583,13 @@ class ASRSnippetAdmin(admin.ModelAdmin):
                 '''
                 <strong>Available deep links:</strong><br/>
                 <ol>
-                  <li><code>special:accounts</code> to open Firefox Accounts</li>
-                  <li><code>special:appMenu</code> to open the hamburger menu</li>
-                  <li><code>special:monitor</code> link User to Firefox Monitor and directly authenticate them. Works only in buttons. Works only after Firefox 69.</li>
-                  <li><code>special:about:ABOUT_PAGE</code> link to an About page. Get a list of About pages by typing <code>about:about</code> in your awesome bar. Example: <code>special:about:protections</code>.
+                  <li><code>special:accounts</code> opens Firefox Accounts</li>
+                  <li><code>special:appMenu</code> opens the hamburger menu</li>
+                  <li><code>special:monitor</code> links User to Firefox Monitor and directly authenticates them. Works only in buttons. Works only after Firefox 69.</li>
+                  <li><code>special:about:ABOUT_PAGE</code> links to an About page. Get a list of About pages by typing <code>about:about</code> in your awesome bar. Example: <code>special:about:protections</code>.
+                  <li><code>special:preferences</code> opens the Firefox Preferences tab. Example: <code>special:preferences</code>.
+                  <li><code>special:highlight:HIGHLIGHT</code> highlights a button in the browser chrome. Get a list of <a href="https://bedrock.readthedocs.io/en/latest/uitour.html#showhighlight-target-effect">available highlights</a>. Example: <code>special:highlight:logins</code>. Works only after Firefox 71.
+                  <li><code>special:menu:MENU</code> opens a targeted menu in the browser chrome. Get a list of <a href="https://bedrock.readthedocs.io/en/latest/uitour.html#showmenu-target-callback">available menus</a>. Example: <code>special:menu:appMenu</code>.
                 </ol><br/>
                 <strong>Content Variables:</strong><br/>
                 You can use <code>[[snippet_id]]</code> in any field and it
diff --git a/snippets/base/models.py b/snippets/base/models.py
--- a/snippets/base/models.py
+++ b/snippets/base/models.py
@@ -678,30 +678,12 @@ def _convert_special_buttons(self, data):
         to_append = {}
         for key, value in local_data.items():
             if key == 'button_url':
-                if value == 'special:appMenu':
-                    to_append['button_action'] = 'OPEN_APPLICATIONS_MENU'
-                    to_append['button_action_args'] = 'appMenu'
-                    to_delete.append(key)
-                elif value == 'special:accounts':
-                    to_append['button_action'] = 'SHOW_FIREFOX_ACCOUNTS'
-                    to_delete.append(key)
-                elif value.startswith('special:about'):
-                    to_append['button_action'] = 'OPEN_ABOUT_PAGE'
-                    to_append['button_action_args'] = value.rsplit(':', 1)[1]
-                elif value == 'special:monitor':
-                    to_append['button_action'] = 'ENABLE_FIREFOX_MONITOR'
-                    to_append['button_action_args'] = {
-                        'url': ('https://monitor.firefox.com/oauth/init?'
-                                'utm_source=desktop-snippet&utm_term=[[job_id]]&'
-                                'utm_content=[[channels]]&utm_campaign=[[campaign_slug]]&'
-                                'entrypoint=snippets&form_type=button'),
-                        'flowRequestParams': {
-                            'entrypoint': 'snippets',
-                            'utm_term': 'snippet-job-[[job_id]]',
-                            'form_type': 'button'
-                        }
-                    }
+                action, args = util.convert_special_link(value)
+                if action:
+                    to_append['button_action'] = action
                     to_delete.append(key)
+                    if args:
+                        to_append['button_action_args'] = args
 
         for key in to_delete:
             local_data.pop(key)
diff --git a/snippets/base/util.py b/snippets/base/util.py
--- a/snippets/base/util.py
+++ b/snippets/base/util.py
@@ -99,6 +99,40 @@ def urlparams(url_, fragment=None, query_dict=None, replace=True, **query):
     return new.geturl()
 
 
+def convert_special_link(url):
+    action = args = None
+    if url == 'special:appMenu':
+        action = 'OPEN_APPLICATIONS_MENU'
+        args = 'appMenu'
+    elif url.startswith('special:menu:'):
+        action = 'OPEN_APPLICATIONS_MENU'
+        args = url.rsplit(':', 1)[1]
+    elif url.startswith('special:about:'):
+        action = 'OPEN_ABOUT_PAGE'
+        args = url.rsplit(':', 1)[1]
+    elif url.startswith('special:highlight:'):
+        action = 'HIGHLIGHT_FEATURE'
+        args = url.rsplit(':', 1)[1]
+    elif url == 'special:preferences':
+        action = 'OPEN_PREFERENCES_PAGE'
+    elif url == 'special:accounts':
+        action = 'SHOW_FIREFOX_ACCOUNTS'
+    elif url == 'special:monitor':
+        action = 'ENABLE_FIREFOX_MONITOR'
+        args = {
+            'url': ('https://monitor.firefox.com/oauth/init?'
+                    'utm_source=desktop-snippet&utm_term=[[job_id]]&'
+                    'utm_content=[[channels]]&utm_campaign=[[campaign_slug]]&'
+                    'entrypoint=snippets&form_type=button'),
+            'flowRequestParams': {
+                'entrypoint': 'snippets',
+                'utm_term': 'snippet-job-[[job_id]]',
+                'form_type': 'button'
+            }
+        }
+    return action, args
+
+
 def fluent_link_extractor(data, variables):
     """Replaces all <a> elements with fluent.js link elements sequentially
     numbered.
@@ -123,35 +157,14 @@ def __call__(self, matchobj):
             if url_match:
                 url = url_match.group('url')
 
-            if url == 'special:appMenu':
-                self.links[keyname] = {
-                    'action': 'OPEN_APPLICATIONS_MENU',
-                    'args': 'appMenu',
-                }
-            elif url.startswith('special:about'):
-                self.links[keyname] = {
-                    'action': 'OPEN_ABOUT_PAGE',
-                    'args': url.rsplit(':', 1)[1],
-                }
-            elif url == 'special:accounts':
-                self.links[keyname] = {
-                    'action': 'SHOW_FIREFOX_ACCOUNTS',
-                }
-            elif url == 'special:monitor':
+            action, args = convert_special_link(url)
+
+            if action:
                 self.links[keyname] = {
-                    'action': 'ENABLE_FIREFOX_MONITOR',
-                    'args': {
-                        'url': ('https://monitor.firefox.com/oauth/init?'
-                                'utm_source=desktop-snippet&utm_term=[[job_id]]&'
-                                'utm_content=[[channels]]&utm_campaign=[[campaign_slug]]&'
-                                'entrypoint=snippets&form_type=button'),
-                        'flowRequestParams': {
-                            'entrypoint': 'snippets',
-                            'utm_term': 'snippet-job-[[job_id]]',
-                            'form_type': 'button'
-                        }
-                    }
+                    'action': action,
                 }
+                if args:
+                    self.links[keyname]['args'] = args
             else:
                 self.links[keyname] = {
                     'url': url,


Test patch:
diff --git a/snippets/base/tests/test_util.py b/snippets/base/tests/test_util.py
--- a/snippets/base/tests/test_util.py
+++ b/snippets/base/tests/test_util.py
@@ -2,8 +2,9 @@
 
 from snippets.base.models import Snippet
 from snippets.base.tests import SnippetFactory, TestCase
-from snippets.base.util import (deep_search_and_replace, first,
-                                fluent_link_extractor, get_object_or_none, urlparams)
+from snippets.base.util import (convert_special_link, deep_search_and_replace,
+                                first, fluent_link_extractor,
+                                get_object_or_none, urlparams)
 
 
 class TestGetObjectOrNone(TestCase):
@@ -37,7 +38,7 @@ def test_no_match(self):
         self.assertEqual(first(items, lambda x: x[0] == 17), None)
 
 
-class TestFluentLinkExtractor(TestCase):
+class TestFluentLinkExtractorTests(TestCase):
     def test_multiple_links_with_metrics(self):
         data = {
             'text': ('We have an <a href="http://example.com">example</a> and another'
@@ -99,6 +100,35 @@ def test_multiple_links_with_metrics(self):
         self.assertEqual(final_data['links'], generated_data['links'])
 
 
+class ConvertSpecialLinkTests(TestCase):
+    def test_base(self):
+        monitor_data = {
+            'url': ('https://monitor.firefox.com/oauth/init?'
+                    'utm_source=desktop-snippet&utm_term=[[job_id]]&'
+                    'utm_content=[[channels]]&utm_campaign=[[campaign_slug]]&'
+                    'entrypoint=snippets&form_type=button'),
+            'flowRequestParams': {
+                'entrypoint': 'snippets',
+                'utm_term': 'snippet-job-[[job_id]]',
+                'form_type': 'button'
+            }
+        }
+        inputs = [
+            ('https://example.com', (None, None)),
+            ('special:appMenu', ('OPEN_APPLICATIONS_MENU', 'appMenu')),
+            ('special:menu:foo', ('OPEN_APPLICATIONS_MENU', 'foo')),
+            ('special:about:login', ('OPEN_ABOUT_PAGE', 'login')),
+            ('special:highlight:foo', ('HIGHLIGHT_FEATURE', 'foo')),
+            ('special:preferences', ('OPEN_PREFERENCES_PAGE', None)),
+            ('special:accounts', ('SHOW_FIREFOX_ACCOUNTS', None)),
+            ('special:monitor', ('ENABLE_FIREFOX_MONITOR', monitor_data)),
+            # This is invalid link but test that the app doesn't choke.
+            ('special:highlight:', ('HIGHLIGHT_FEATURE', '')),
+        ]
+        for url, expected_tuple in inputs:
+            self.assertEqual(convert_special_link(url), expected_tuple)
+
+
 class DeepSearchAndReplaceTests(TestCase):
     def test_base(self):
         data = {