You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by jo...@apache.org on 2013/08/17 00:21:37 UTC

[01/16] git commit: [#6506] Added TracWikiImporter to requirements-sf.txt

Updated Branches:
  refs/heads/cj/6464 7474e6e55 -> d1ac784ee (forced update)


[#6506] Added TracWikiImporter to requirements-sf.txt


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/2b0a101f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/2b0a101f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/2b0a101f

Branch: refs/heads/cj/6464
Commit: 2b0a101f0a7a581fbde2fbc03441bcbcb3e616b1
Parents: 3e67aa6
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Fri Aug 16 14:46:34 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Fri Aug 16 14:46:34 2013 +0000

----------------------------------------------------------------------
 requirements-sf.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/2b0a101f/requirements-sf.txt
----------------------------------------------------------------------
diff --git a/requirements-sf.txt b/requirements-sf.txt
index 6d006ec..cc66c69 100644
--- a/requirements-sf.txt
+++ b/requirements-sf.txt
@@ -20,7 +20,7 @@ wsgipreload==1.2
 pyzmq==2.1.7
 html2text==3.200.3dev-20121112
 PyMollom==0.1
-TracWikiImporter=0.1.0
+TracWikiImporter==0.1.0
 
 # use version built from https://github.com/johnsca/GitPython/commits/tv/6000
 # for unmerged fixes for [#5411], [#6000], and [#6078]


[03/16] git commit: [#6464] Added tests for GC tracker attachments and comments

Posted by jo...@apache.org.
[#6464] Added tests for GC tracker attachments and comments

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/ea2fc78b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/ea2fc78b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/ea2fc78b

Branch: refs/heads/cj/6464
Commit: ea2fc78b88b4c52fa184daff35d367a07d33971a
Parents: 79d9493
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 9 16:25:01 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:20:59 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           | 10 +++-
 .../tests/data/google/test-issue.html           | 38 +++++++++++++
 .../tests/google/test_extractor.py              | 60 ++++++++++++++++++++
 .../forgeimporters/tests/google/test_tracker.py |  4 +-
 4 files changed, 107 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ea2fc78b/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index d06a500..88a1449 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -192,8 +192,12 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
         return self.page.find(id='hc0').find('span', 'date').get('title')
 
     def get_issue_mod_date(self):
-        last_update = Comment(self.page.findAll('div', 'issuecomment')[-1])
-        return last_update.created_date
+        comments = self.page.findAll('div', 'issuecomment')
+        if comments:
+            last_update = Comment(comments[-1])
+            return last_update.created_date
+        else:
+            return self.get_issue_created_date()
 
     def get_issue_creator(self):
         a = self.page.find(id='hc0').find('a', 'userlink')
@@ -237,7 +241,7 @@ class Comment(object):
     def __init__(self, tag):
         self.author = UserLink(tag.find('span', 'author').find('a', 'userlink'))
         self.created_date = tag.find('span', 'date').get('title')
-        self.body = _as_text(tag.find('pre'))
+        self.body = _as_text(tag.find('pre')).strip()
         self._get_updates(tag)
         self._get_attachments(tag)
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ea2fc78b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/test-issue.html b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
index 04667ac..dbef54e 100644
--- a/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
+++ b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
@@ -282,6 +282,44 @@ That's all
 
 
 </pre>
+<div class="attachments">
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at1.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=7&amp;aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000000&amp;name=at1.txt&amp;token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255">Download</a>
+</td>
+</tr>
+</table>
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at2.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=7&amp;aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=70000001&amp;name=at2.txt&amp;token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255">Download</a>
+</td>
+</tr>
+</table>
+</div>
 </div>
 <div class="cursor_off vt issuecomment" id="hc1">
 <div style="float:right; margin-right:.3em; text-align:right">

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ea2fc78b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 14333aa..bac1750 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -140,6 +140,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self.assertEqual(gpe.get_issue_status(), '')
         self.assertEqual(gpe.get_issue_attachments(), [])
         self.assertEqual(list(gpe.iter_comments()), [])
+        self.assertEqual(gpe.get_issue_mod_date(), 'Thu Aug  8 14:56:23 2013')
 
     def test_get_issue_basic_fields(self):
         test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
@@ -189,3 +190,62 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 'OpSys-Windows',
                 'OpSys-OSX',
             ])
+
+    def test_get_issue_attachments(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        attachments = gpe.get_issue_attachments()
+        self.assertEqual(len(attachments), 2)
+        self.assertEqual(attachments[0].filename, 'at1.txt')
+        self.assertEqual(attachments[0].url, 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000000&name=at1.txt&token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255')
+        self.assertIsNone(attachments[0].type)
+        self.assertEqual(attachments[1].filename, 'at2.txt')
+        self.assertEqual(attachments[1].url, 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000001&name=at2.txt&token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255')
+        self.assertIsNone(attachments[1].type)
+
+    def test_iter_comments(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        comments = list(gpe.iter_comments())
+        self.assertEqual(len(comments), 4)
+        expected = [
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:35:15 2013',
+                    'body': 'Test *comment* is a comment',
+                    'updates': {'Status:': 'Started', 'Labels:': '-OpSys-Linux OpSys-Windows'},
+                    'attachments': ['at2.txt'],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:35:34 2013',
+                    'body': 'Another comment',
+                    'updates': {},
+                    'attachments': [],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:36:39 2013',
+                    'body': 'Last comment',
+                    'updates': {},
+                    'attachments': ['at4.txt', 'at1.txt'],
+                },
+                {
+                    'author.name': 'john...@gmail.com',
+                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'created_date': 'Thu Aug  8 15:36:57 2013',
+                    'body': 'Oh, I forgot one',
+                    'updates': {'Labels:': 'OpSys-OSX'},
+                    'attachments': [],
+                },
+            ]
+        for actual, expected in zip(comments, expected):
+            self.assertEqual(actual.author.name, expected['author.name'])
+            self.assertEqual(actual.author.link, expected['author.link'])
+            self.assertEqual(actual.created_date, expected['created_date'])
+            self.assertEqual(actual.body, expected['body'])
+            self.assertEqual(actual.updates, expected['updates'])
+            self.assertEqual([a.filename for a in actual.attachments], expected['attachments'])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ea2fc78b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 62493bd..a1f0a28 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -116,7 +116,7 @@ class TestTrackerImporter(TestCase):
             return u
         issue = mock.Mock(
                 get_issue_summary=lambda:'summary',
-                get_issue_description=lambda:'description',
+                get_issue_description=lambda:'my *description* fool',
                 get_issue_status=lambda:'status',
                 get_issue_created_date=lambda:'created_date',
                 get_issue_mod_date=lambda:'mod_date',
@@ -128,7 +128,7 @@ class TestTrackerImporter(TestCase):
             dt.strptime.side_effect = lambda s,f: s
             importer.process_fields(ticket, issue)
             self.assertEqual(ticket.summary, 'summary')
-            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\ndescription')
+            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\nmy \*description\* fool')
             self.assertEqual(ticket.status, 'status')
             self.assertEqual(ticket.created_date, 'created_date')
             self.assertEqual(ticket.mod_date, 'mod_date')


[11/16] git commit: [#6464] Fixed issues from rebase

Posted by jo...@apache.org.
[#6464] Fixed issues from rebase

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/3c9bc576
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/3c9bc576
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/3c9bc576

Branch: refs/heads/cj/6464
Commit: 3c9bc576cfee85d43b73ef8d6b9892d010ec72f0
Parents: 38c8c8e
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 16 02:06:33 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:21:00 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/helpers.py                          |  3 +++
 Allura/allura/tests/test_helpers.py                   |  9 +++++----
 ForgeImporters/forgeimporters/google/__init__.py      |  4 ++--
 .../tests/google/functional/test_tracker.py           | 14 ++++++++------
 4 files changed, 18 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/3c9bc576/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 01586c2..39bf250 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -956,6 +956,9 @@ def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False
     if preserve_multiple_spaces:
         text = text.replace('\t', ' ' * 4)
         text = re_preserve_spaces.sub('&nbsp;', text)
+    else:
+        text = re_leading_spaces.sub('', text)
+    try:
         # try to use html2text for most of the escaping
         import html2text
         html2text.BODY_WIDTH = 0

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/3c9bc576/Allura/allura/tests/test_helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 03a530c..4a0b80d 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -292,17 +292,18 @@ M & Ms - doesn't get escaped
 http://blah.com/?x=y&a=b - not escaped either
 '''
 
-    assert_equals(h.plain2markdown(text), expected)
+    dd.assert_equal(h.plain2markdown(text), expected)
 
-    assert_equals(h.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
+    dd.assert_equal(h.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
                 'a foo&nbsp; bar\n\n&nbsp;&nbsp;&nbsp; code here?')
 
-    assert_equals(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
+    dd.assert_equal(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
                  '&nbsp;&nbsp;&nbsp; tab before \(stuff\)')
 
-    assert_equals(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
+    dd.assert_equal(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
                  'tab before \(stuff\)')
 
+
 @td.without_module('html2text')
 def test_plain2markdown():
     """Test plain2markdown using fallback regexp to escape markdown.

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/3c9bc576/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index f60bc58..7234dc8 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -156,7 +156,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
     @classmethod
     def _get_issue_ids_page(cls, project_name, start):
         url = cls.PAGE_MAP['issues_csv'].format(project_name=project_name, start=start)
-        with closing(urllib2.urlopen(url)) as fp:
+        with closing(cls.urlopen(url)) as fp:
             lines = fp.readlines()[1:]  # skip CSV header
             if not lines[-1].startswith('"'):
                 lines.pop()  # skip "next page here" info footer
@@ -285,6 +285,6 @@ class Attachment(object):
 
     @property
     def file(self):
-        fp_ish = urllib2.urlopen(self.url)
+        fp_ish = GoogleCodeProjectExtractor(None).urlopen(self.url)
         fp = StringIO(fp_ish.read())
         return fp

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/3c9bc576/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
index 7d67255..62c4453 100644
--- a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -29,15 +29,17 @@ from IPython.testing.decorators import module_not_available, skipif
 from alluratest.controller import setup_basic_test
 from allura.tests.decorators import without_module
 from allura import model as M
+from allura.lib import helpers as h
 from forgetracker import model as TM
-from .... import google
-from ....google import tracker
+from forgeimporters import base
+from forgeimporters import google
+from forgeimporters.google import tracker
 
 
 class TestGCTrackerImporter(TestCase):
     def _make_extractor(self, html):
-        with mock.patch.object(google, 'urllib2') as urllib2:
-            urllib2.urlopen.return_value = ''
+        with mock.patch.object(base.h, 'urlopen') as urlopen:
+            urlopen.return_value = ''
             extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
         extractor.page = BeautifulSoup(html)
         extractor.url = "http://test/issue/?id=1"
@@ -45,9 +47,9 @@ class TestGCTrackerImporter(TestCase):
 
     def _make_ticket(self, issue):
         self.assertIsNone(self.project.app_instance('test-issue'))
-        with mock.patch.object(google, 'urllib2') as urllib2,\
+        with mock.patch.object(base.h, 'urlopen') as urlopen,\
              mock.patch.object(google.tracker, 'GoogleCodeProjectExtractor') as GPE:
-            urllib2.urlopen = lambda url: mock.Mock(read=lambda: url)
+            urlopen.side_effect = lambda req, **kw: mock.Mock(read=req.get_full_url)
             GPE.iter_issues.return_value = [issue]
             gti = google.tracker.GoogleCodeTrackerImporter()
             gti.import_tool(self.project, self.user, 'test-issue-project', mount_point='test-issue')


[09/16] git commit: [#6464] Fixed attribute error on missing / empty GC issue fields and added tests

Posted by jo...@apache.org.
[#6464] Fixed attribute error on missing / empty GC issue fields and added tests

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/79d94937
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/79d94937
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/79d94937

Branch: refs/heads/cj/6464
Commit: 79d94937c03eb7d68f847272f83f99054b716f31
Parents: 9222343
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 9 14:35:59 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:20:59 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           |  14 +-
 .../tests/data/google/empty-issue.html          | 306 ++++++++++++
 .../tests/data/google/test-issue.html           | 488 +++++++++++++++++++
 .../tests/google/test_extractor.py              |  58 +++
 4 files changed, 863 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79d94937/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index eaa0765..d06a500 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -186,7 +186,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
         return self.page.find(id='issueheader').findAll('td', limit=2)[1].span.string.strip()
 
     def get_issue_description(self):
-        return _as_text(self.page.find(id='hc0').pre)
+        return _as_text(self.page.find(id='hc0').pre).strip()
 
     def get_issue_created_date(self):
         return self.page.find(id='hc0').find('span', 'date').get('title')
@@ -200,10 +200,18 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
         return UserLink(a)
 
     def get_issue_status(self):
-        return self.page.find(id='issuemeta').find('th', text=re.compile('Status:')).findNext().span.string.strip()
+        tag = self.page.find(id='issuemeta').find('th', text=re.compile('Status:')).findNext().span
+        if tag:
+            return tag.string.strip()
+        else:
+            return ''
 
     def get_issue_owner(self):
-        return UserLink(self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().a)
+        tag = self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().a
+        if tag:
+            return UserLink(tag)
+        else:
+            return None
 
     def get_issue_labels(self):
         label_nodes = self.page.find(id='issuemeta').findAll('a', 'label')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79d94937/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html b/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
new file mode 100644
index 0000000..a8fb1d5
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
@@ -0,0 +1,306 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+<meta name="ROBOTS" content="NOARCHIVE" />
+<link rel="icon" type="image/vnd.microsoft.icon" href="http://www.gstatic.com/codesite/ph/images/phosting.ico" />
+<script type="text/javascript">
+
+
+
+
+ var codesite_token = null;
+
+
+ var CS_env = {"assetHostPath":"http://www.gstatic.com/codesite/ph","projectHomeUrl":"/p/allura-google-importer","relativeBaseUrl":"","domainName":null,"projectName":"allura-google-importer","loggedInUserEmail":null,"profileUrl":null,"token":null,"assetVersionPath":"http://www.gstatic.com/codesite/ph/3783617020303179221"};
+ var _gaq = _gaq || [];
+ _gaq.push(
+ ['siteTracker._setAccount', 'UA-18071-1'],
+ ['siteTracker._trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
+ })();
+
+ </script>
+<title>Issue 5 -
+ allura-google-importer -
+
+ Empty Issue -
+ Import Google Code projects to an Allura forge - Google Project Hosting
+ </title>
+<link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/core.css" />
+<link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/ph_detail.css" />
+<!--[if IE]>
+ <link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/d_ie.css" >
+<![endif]-->
+<style type="text/css">
+ .menuIcon.off { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 -42px }
+ .menuIcon.on { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 -28px }
+ .menuIcon.down { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 0; }
+
+
+ .attachments { width:33%; border-top:2px solid #999; padding-top: 3px; margin-left: .7em;}
+ .attachments table { margin-bottom: 0.75em; }
+ .attachments table tr td { padding: 0; margin: 0; font-size: 95%; }
+ .preview { border: 2px solid #c3d9ff; padding: 1px; }
+ .preview:hover { border: 2px solid blue; }
+ .label { white-space: nowrap; }
+ .derived { font-style:italic }
+ .cursor_on .author {
+ background: url(http://www.gstatic.com/codesite/ph/images/show-arrow.gif) no-repeat 2px;
+ }
+ .hiddenform {
+ display: none;
+ }
+
+
+ </style>
+</head>
+<body class="t3">
+<script type="text/javascript">
+ window.___gcfg = {lang: 'en'};
+ (function()
+ {var po = document.createElement("script");
+ po.type = "text/javascript"; po.async = true;po.src = "https://apis.google.com/js/plusone.js";
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(po, s);
+ })();
+</script>
+<div class="headbg">
+<div id="gaia">
+<span>
+<a href="#" id="projects-dropdown" onclick="return false;"><u>My favorites</u> <small>&#9660;</small></a>
+ | <a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5" onclick="_CS_click('/gb/ph/signin');"><u>Sign in</u></a>
+</span>
+</div>
+<div class="gbh" style="left: 0pt;"></div>
+<div class="gbh" style="right: 0pt;"></div>
+<div style="height: 1px"></div>
+<!--[if lte IE 7]>
+<div style="text-align:center;">
+Your version of Internet Explorer is not supported. Try a browser that
+contributes to open source, such as <a href="http://www.firefox.com">Firefox</a>,
+<a href="http://www.google.com/chrome">Google Chrome</a>, or
+<a href="http://code.google.com/chrome/chromeframe/">Google Chrome Frame</a>.
+</div>
+<![endif]-->
+<table style="padding:0px; margin: 0px 0px 10px 0px; width:100%" cellpadding="0" cellspacing="0" itemscope="itemscope" itemtype="http://schema.org/CreativeWork">
+<tr style="height: 58px;">
+<td id="plogo">
+<link itemprop="url" href="/p/allura-google-importer" />
+<a href="/p/allura-google-importer/">
+<img src="/p/allura-google-importer/logo?cct=1374769571" alt="Logo" itemprop="image" />
+</a>
+</td>
+<td style="padding-left: 0.5em">
+<div id="pname">
+<a href="/p/allura-google-importer/"><span itemprop="name">allura-google-importer</span></a>
+</div>
+<div id="psum">
+<a id="project_summary_link" href="/p/allura-google-importer/"><span itemprop="description">Import Google Code projects to an Allura forge</span></a>
+</div>
+</td>
+<td style="white-space:nowrap;text-align:right; vertical-align:bottom;">
+<form action="/hosting/search">
+<input size="30" name="q" value="" type="text" />
+<input type="submit" name="projectsearch" value="Search projects" />
+</form>
+</td></tr>
+</table>
+</div>
+<div id="mt" class="gtb">
+<a href="/p/allura-google-importer/" class="tab ">Project&nbsp;Home</a>
+<a href="/p/allura-google-importer/wiki/TestPage?tm=6" class="tab ">Wiki</a>
+<a href="/p/allura-google-importer/issues/list" class="tab active">Issues</a>
+<a href="/p/allura-google-importer/source/checkout" class="tab ">Source</a>
+<div class="gtbc"></div>
+</div>
+<table cellspacing="0" cellpadding="0" width="100%" align="center" border="0" class="st">
+<tr>
+<td class="subt">
+<div class="issueDetail">
+<div class="isf">
+<span class="inIssueEntry">
+<a class="buttonify" href="entry" onclick="return _newIssuePrompt();">New issue</a>
+</span> &nbsp;
+
+ <span class="inIssueList">
+<span>Search</span>
+</span><form action="list" method="GET" style="display:inline">
+<select id="can" name="can">
+<option disabled="disabled">Search within:</option>
+<option value="1">&nbsp;All issues</option>
+<option value="2" selected="selected">&nbsp;Open issues</option>
+<option value="6">&nbsp;New issues</option>
+<option value="7">&nbsp;Issues to verify</option>
+</select>
+<span>for</span>
+<span id="qq"><input type="text" size="38" id="searchq" name="q" value="" autocomplete="off" onkeydown="_blurOnEsc(event)" /></span>
+<span id="search_colspec"><input type="hidden" name="colspec" value="ID Type Status Priority Milestone Owner Summary" /></span>
+<input type="hidden" name="cells" value="tiles" />
+<input type="submit" value="Search" />
+</form>
+ &nbsp;
+ <span class="inIssueAdvSearch">
+<a href="advsearch">Advanced search</a>
+</span> &nbsp;
+ <span class="inIssueSearchTips">
+<a href="searchtips">Search tips</a>
+</span> &nbsp;
+ <span class="inIssueSubscriptions">
+<a href="/p/allura-google-importer/issues/subscriptions">Subscriptions</a>
+</span>
+</div>
+</div>
+</td>
+<td align="right" valign="top" class="bevel-right"></td>
+</tr>
+</table>
+<script type="text/javascript">
+ var cancelBubble = false;
+ function _go(url) { document.location = url; }
+</script>
+<div id="maincol">
+<div id="color_control" class="">
+<div id="issueheader">
+<table cellpadding="0" cellspacing="0" width="100%"><tbody>
+<tr>
+<td class="vt h3" nowrap="nowrap" style="padding:0 5px">
+
+
+ Issue <a href="detail?id=5">5</a>:
+ </td>
+<td width="90%" class="vt">
+<span class="h3">Empty Issue</span>
+</td>
+<td>
+<div class="pagination">
+<a href="../../allura-google-importer/issues/detail?id=4" title="Prev">&lsaquo; Prev</a>
+ 5 of 5
+
+ </div>
+</td>
+</tr>
+<tr>
+<td></td>
+<td nowrap="nowrap">
+
+
+ 1 person starred this issue and may be notified of changes.
+
+
+
+ </td>
+<td align="center" nowrap="nowrap">
+<a href="http://code.google.com/p/allura-google-importer/issues/list?cursor=allura-google-importer%3A5">Back to list</a>
+</td>
+</tr>
+</tbody></table>
+</div>
+<table width="100%" cellpadding="0" cellspacing="0" border="0" class="issuepage" id="meta-container">
+<tbody class="collapse">
+<tr>
+<td id="issuemeta">
+<div id="meta-float">
+<table cellspacing="0" cellpadding="0">
+<tr><th align="left">Status:&nbsp;</th>
+<td width="100%">
+
+
+ ----
+
+
+ </td>
+</tr>
+<tr><th align="left">Owner:&nbsp;</th><td>
+
+
+ ----
+
+
+ </td>
+</tr>
+<tr><td colspan="2">
+</td></tr>
+</table>
+<div class="rel_issues">
+</div>
+<br /><br />
+<div style="white-space:nowrap"><a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5">Sign in</a> to add a comment</div>
+</div>&nbsp;
+ </td>
+<td class="vt issuedescription" width="100%" id="cursorarea">
+<div class="cursor_off vt issuedescription" id="hc0">
+<div class="author">
+<span class="role_label">Project Member</span>
+ Reported by
+
+
+ <a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a>,
+ <span class="date" title="Thu Aug  8 14:56:23 2013">Today (15 minutes ago)</span>
+</div>
+<pre>
+Empty
+</pre>
+</div>
+</td>
+</tr>
+<tr>
+<td></td>
+<td class="vt issuecomment">
+<span class="indicator">&#9658;</span> <a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D5">Sign in</a> to add a comment
+ </td>
+</tr>
+</tbody>
+</table>
+<br />
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/dit_scripts.js"></script>
+</div>
+<form name="delcom" action="delComment.do?q=&amp;can=2&amp;groupby=&amp;sort=&amp;colspec=ID+Type+Status+Priority+Milestone+Owner+Summary" method="POST">
+<input type="hidden" name="sequence_num" value="" />
+<input type="hidden" name="mode" value="" />
+<input type="hidden" name="id" value="5" />
+<input type="hidden" name="token" value="" />
+</form>
+<div id="helparea"></div>
+<script type="text/javascript">
+ _onload();
+ function delComment(sequence_num, delete_mode) {
+ var f = document.forms["delcom"];
+ f.sequence_num.value = sequence_num;
+ f.mode.value = delete_mode;
+
+ f.submit();
+ return false;
+ }
+
+ _floatMetadata();
+</script>
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/kibbles.js"></script>
+<script type="text/javascript">
+ _setupKibblesOnDetailPage(
+ 'http://code.google.com/p/allura-google-importer/issues/list?cursor\x3dallura-google-importer%3A5',
+ '/p/allura-google-importer/issues/entry',
+ '../../allura-google-importer/issues/detail?id\x3d4',
+ '',
+ '', 'allura-google-importer', 5,
+ false, false, codesite_token);
+</script>
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/ph_core.js"></script>
+</div>
+<div id="footer" dir="ltr">
+<div class="text">
+<a href="/projecthosting/terms.html">Terms</a> -
+ <a href="http://www.google.com/privacy.html">Privacy</a> -
+ <a href="/p/support/">Project Hosting Help</a>
+</div>
+</div>
+<div class="hostedBy" style="margin-top: -20px;">
+<span style="vertical-align: top;">Powered by <a href="http://code.google.com/projecthosting/">Google Project Hosting</a></span>
+</div>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79d94937/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/test-issue.html b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
new file mode 100644
index 0000000..04667ac
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/data/google/test-issue.html
@@ -0,0 +1,488 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+<meta name="ROBOTS" content="NOARCHIVE" />
+<link rel="icon" type="image/vnd.microsoft.icon" href="http://www.gstatic.com/codesite/ph/images/phosting.ico" />
+<script type="text/javascript">
+
+
+
+
+ var codesite_token = null;
+
+
+ var CS_env = {"loggedInUserEmail":null,"relativeBaseUrl":"","projectHomeUrl":"/p/allura-google-importer","assetVersionPath":"http://www.gstatic.com/codesite/ph/3783617020303179221","assetHostPath":"http://www.gstatic.com/codesite/ph","domainName":null,"projectName":"allura-google-importer","token":null,"profileUrl":null};
+ var _gaq = _gaq || [];
+ _gaq.push(
+ ['siteTracker._setAccount', 'UA-18071-1'],
+ ['siteTracker._trackPageview']);
+
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
+ })();
+
+ </script>
+<title>Issue 6 -
+ allura-google-importer -
+
+ Test Issue -
+ Import Google Code projects to an Allura forge - Google Project Hosting
+ </title>
+<link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/core.css" />
+<link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/ph_detail.css" />
+<!--[if IE]>
+ <link type="text/css" rel="stylesheet" href="http://www.gstatic.com/codesite/ph/3783617020303179221/css/d_ie.css" >
+<![endif]-->
+<style type="text/css">
+ .menuIcon.off { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 -42px }
+ .menuIcon.on { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 -28px }
+ .menuIcon.down { background: no-repeat url(http://www.gstatic.com/codesite/ph/images/dropdown_sprite.gif) 0 0; }
+
+
+ .attachments { width:33%; border-top:2px solid #999; padding-top: 3px; margin-left: .7em;}
+ .attachments table { margin-bottom: 0.75em; }
+ .attachments table tr td { padding: 0; margin: 0; font-size: 95%; }
+ .preview { border: 2px solid #c3d9ff; padding: 1px; }
+ .preview:hover { border: 2px solid blue; }
+ .label { white-space: nowrap; }
+ .derived { font-style:italic }
+ .cursor_on .author {
+ background: url(http://www.gstatic.com/codesite/ph/images/show-arrow.gif) no-repeat 2px;
+ }
+ .hiddenform {
+ display: none;
+ }
+
+
+ </style>
+</head>
+<body class="t3">
+<script type="text/javascript">
+ window.___gcfg = {lang: 'en'};
+ (function()
+ {var po = document.createElement("script");
+ po.type = "text/javascript"; po.async = true;po.src = "https://apis.google.com/js/plusone.js";
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(po, s);
+ })();
+</script>
+<div class="headbg">
+<div id="gaia">
+<span>
+<a href="#" id="projects-dropdown" onclick="return false;"><u>My favorites</u> <small>&#9660;</small></a>
+ | <a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6" onclick="_CS_click('/gb/ph/signin');"><u>Sign in</u></a>
+</span>
+</div>
+<div class="gbh" style="left: 0pt;"></div>
+<div class="gbh" style="right: 0pt;"></div>
+<div style="height: 1px"></div>
+<!--[if lte IE 7]>
+<div style="text-align:center;">
+Your version of Internet Explorer is not supported. Try a browser that
+contributes to open source, such as <a href="http://www.firefox.com">Firefox</a>,
+<a href="http://www.google.com/chrome">Google Chrome</a>, or
+<a href="http://code.google.com/chrome/chromeframe/">Google Chrome Frame</a>.
+</div>
+<![endif]-->
+<table style="padding:0px; margin: 0px 0px 10px 0px; width:100%" cellpadding="0" cellspacing="0" itemscope="itemscope" itemtype="http://schema.org/CreativeWork">
+<tr style="height: 58px;">
+<td id="plogo">
+<link itemprop="url" href="/p/allura-google-importer" />
+<a href="/p/allura-google-importer/">
+<img src="/p/allura-google-importer/logo?cct=1374769571" alt="Logo" itemprop="image" />
+</a>
+</td>
+<td style="padding-left: 0.5em">
+<div id="pname">
+<a href="/p/allura-google-importer/"><span itemprop="name">allura-google-importer</span></a>
+</div>
+<div id="psum">
+<a id="project_summary_link" href="/p/allura-google-importer/"><span itemprop="description">Import Google Code projects to an Allura forge</span></a>
+</div>
+</td>
+<td style="white-space:nowrap;text-align:right; vertical-align:bottom;">
+<form action="/hosting/search">
+<input size="30" name="q" value="" type="text" />
+<input type="submit" name="projectsearch" value="Search projects" />
+</form>
+</td></tr>
+</table>
+</div>
+<div id="mt" class="gtb">
+<a href="/p/allura-google-importer/" class="tab ">Project&nbsp;Home</a>
+<a href="/p/allura-google-importer/wiki/TestPage?tm=6" class="tab ">Wiki</a>
+<a href="/p/allura-google-importer/issues/list" class="tab active">Issues</a>
+<a href="/p/allura-google-importer/source/checkout" class="tab ">Source</a>
+<div class="gtbc"></div>
+</div>
+<table cellspacing="0" cellpadding="0" width="100%" align="center" border="0" class="st">
+<tr>
+<td class="subt">
+<div class="issueDetail">
+<div class="isf">
+<span class="inIssueEntry">
+<a class="buttonify" href="entry" onclick="return _newIssuePrompt();">New issue</a>
+</span> &nbsp;
+
+ <span class="inIssueList">
+<span>Search</span>
+</span><form action="list" method="GET" style="display:inline">
+<select id="can" name="can">
+<option disabled="disabled">Search within:</option>
+<option value="1">&nbsp;All issues</option>
+<option value="2" selected="selected">&nbsp;Open issues</option>
+<option value="6">&nbsp;New issues</option>
+<option value="7">&nbsp;Issues to verify</option>
+</select>
+<span>for</span>
+<span id="qq"><input type="text" size="38" id="searchq" name="q" value="" autocomplete="off" onkeydown="_blurOnEsc(event)" /></span>
+<span id="search_colspec"><input type="hidden" name="colspec" value="ID Type Status Priority Milestone Owner Summary" /></span>
+<input type="hidden" name="cells" value="tiles" />
+<input type="submit" value="Search" />
+</form>
+ &nbsp;
+ <span class="inIssueAdvSearch">
+<a href="advsearch">Advanced search</a>
+</span> &nbsp;
+ <span class="inIssueSearchTips">
+<a href="searchtips">Search tips</a>
+</span> &nbsp;
+ <span class="inIssueSubscriptions">
+<a href="/p/allura-google-importer/issues/subscriptions">Subscriptions</a>
+</span>
+</div>
+</div>
+</td>
+<td align="right" valign="top" class="bevel-right"></td>
+</tr>
+</table>
+<script type="text/javascript">
+ var cancelBubble = false;
+ function _go(url) { document.location = url; }
+</script>
+<div id="maincol">
+<div id="color_control" class="">
+<div id="issueheader">
+<table cellpadding="0" cellspacing="0" width="100%"><tbody>
+<tr>
+<td class="vt h3" nowrap="nowrap" style="padding:0 5px">
+
+
+ Issue <a href="detail?id=6">6</a>:
+ </td>
+<td width="90%" class="vt">
+<span class="h3">Test Issue</span>
+</td>
+<td>
+<div class="pagination">
+<a href="../../allura-google-importer/issues/detail?id=5" title="Prev">&lsaquo; Prev</a>
+ 6 of 6
+
+ </div>
+</td>
+</tr>
+<tr>
+<td></td>
+<td nowrap="nowrap">
+
+
+ 1 person starred this issue and may be notified of changes.
+
+
+
+ </td>
+<td align="center" nowrap="nowrap">
+<a href="http://code.google.com/p/allura-google-importer/issues/list?cursor=allura-google-importer%3A6">Back to list</a>
+</td>
+</tr>
+</tbody></table>
+</div>
+<table width="100%" cellpadding="0" cellspacing="0" border="0" class="issuepage" id="meta-container">
+<tbody class="collapse">
+<tr>
+<td id="issuemeta">
+<div id="meta-float">
+<table cellspacing="0" cellpadding="0">
+<tr><th align="left">Status:&nbsp;</th>
+<td width="100%">
+<span title="Work on this issue has begun">Started</span>
+</td>
+</tr>
+<tr><th align="left">Owner:&nbsp;</th><td>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a>
+</td>
+</tr>
+<tr><td colspan="2">
+<div style="padding-top:2px">
+<a href="list?q=label:Type-Defect" title="Report of a software defect" class="label"><b>Type-</b>Defect</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Priority-Medium" title="Normal priority" class="label"><b>Priority-</b>Medium</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Milestone-Release1.0" title="All essential functionality working" class="label"><b>Milestone-</b>Release1.0</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:OpSys-All" title="Affects all operating systems" class="label"><b>OpSys-</b>All</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Component-Logic" title="Issue relates to application logic" class="label"><b>Component-</b>Logic</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Performance" title="Performance issue" class="label">Performance</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:Security" title="Security risk to users" class="label">Security</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:OpSys-Windows" title="Affects Windows users" class="label"><b>OpSys-</b>Windows</a>
+</div>
+<div style="padding-top:2px">
+<a href="list?q=label:OpSys-OSX" title="Affects Mac OS X users" class="label"><b>OpSys-</b>OSX</a>
+</div>
+</td></tr>
+</table>
+<div class="rel_issues">
+</div>
+<br /><br />
+<div style="white-space:nowrap"><a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6">Sign in</a> to add a comment</div>
+</div>&nbsp;
+ </td>
+<td class="vt issuedescription" width="100%" id="cursorarea">
+<div class="cursor_off vt issuedescription" id="hc0">
+<div class="author">
+<span class="role_label">Project Member</span>
+ Reported by
+
+
+ <a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a>,
+ <span class="date" title="Thu Aug  8 15:33:52 2013">Today (3 minutes ago)</span>
+</div>
+<pre>
+Test *Issue* for testing
+
+  1. Test List
+  2. Item
+
+**Testing**
+
+ * Test list 2
+ * Item
+
+# Test Section
+
+    p = source.test_issue.post()
+    p.count = p.count *5 #* 6
+
+That's all
+
+
+</pre>
+</div>
+<div class="cursor_off vt issuecomment" id="hc1">
+<div style="float:right; margin-right:.3em; text-align:right">
+<span class="date" title="Thu Aug  8 15:35:15 2013">
+ Today (2 minutes ago)
+ </span>
+</div>
+<span class="author">
+<span class="role_label">Project Member</span>
+<a name="c1" href="/p/allura-google-importer/issues/detail?id=6#c1">#1</a>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a></span>
+<pre>
+Test *comment* is a comment
+</pre>
+<div class="attachments">
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60001000&amp;name=at2.txt&amp;token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at2.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=6&amp;aid=60001000&amp;name=at2.txt&amp;token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60001000&amp;name=at2.txt&amp;token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667">Download</a>
+</td>
+</tr>
+</table>
+</div>
+<div class="updates">
+<div class="round4"></div>
+<div class="round2"></div>
+<div class="round1"></div>
+<div class="box-inner">
+<b>Status:</b>
+ Started
+
+ <br />
+<b>Labels:</b>
+ -OpSys-Linux OpSys-Windows
+
+ <br />
+</div>
+<div class="round1"></div>
+<div class="round2"></div>
+<div class="round4"></div>
+</div>
+</div>
+<div class="cursor_off vt issuecomment" id="hc2">
+<div style="float:right; margin-right:.3em; text-align:right">
+<span class="date" title="Thu Aug  8 15:35:34 2013">
+ Today (1 minute ago)
+ </span>
+</div>
+<span class="author">
+<span class="role_label">Project Member</span>
+<a name="c2" href="/p/allura-google-importer/issues/detail?id=6#c2">#2</a>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a></span>
+<pre>
+Another comment
+</pre>
+</div>
+<div class="cursor_off vt issuecomment" id="hc3">
+<div style="float:right; margin-right:.3em; text-align:right">
+<span class="date" title="Thu Aug  8 15:36:39 2013">
+ Today (moments ago)
+ </span>
+</div>
+<span class="author">
+<span class="role_label">Project Member</span>
+<a name="c3" href="/p/allura-google-importer/issues/detail?id=6#c3">#3</a>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a></span>
+<pre>
+Last comment
+</pre>
+<div class="attachments">
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60003000&amp;name=at4.txt&amp;token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at4.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=6&amp;aid=60003000&amp;name=at4.txt&amp;token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60003000&amp;name=at4.txt&amp;token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667">Download</a>
+</td>
+</tr>
+</table>
+<table cellspacing="3" cellpadding="2" border="0">
+<tr><td width="20">
+<a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60003001&amp;name=at1.txt&amp;token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667">
+<img width="15" height="15" src="http://www.gstatic.com/codesite/ph/images/paperclip.gif" border="0" />
+</a>
+</td>
+<td style="min-width:16em" valign="top">
+<b>at1.txt</b>
+<br />
+ 13 bytes
+
+
+ &nbsp; <a href="../../allura-google-importer/issues/attachmentText?id=6&amp;aid=60003001&amp;name=at1.txt&amp;token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667" target="_blank">View</a>
+
+ &nbsp; <a href="//allura-google-importer.googlecode.com/issues/attachment?aid=60003001&amp;name=at1.txt&amp;token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667">Download</a>
+</td>
+</tr>
+</table>
+</div>
+</div>
+<div class="cursor_off vt issuecomment" id="hc4">
+<div style="float:right; margin-right:.3em; text-align:right">
+<span class="date" title="Thu Aug  8 15:36:57 2013">
+ Today (moments ago)
+ </span>
+</div>
+<span class="author">
+<span class="role_label">Project Member</span>
+<a name="c4" href="/p/allura-google-importer/issues/detail?id=6#c4">#4</a>
+<a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a></span>
+<pre>
+Oh, I forgot one
+</pre>
+<div class="updates">
+<div class="round4"></div>
+<div class="round2"></div>
+<div class="round1"></div>
+<div class="box-inner">
+<b>Labels:</b>
+ OpSys-OSX
+
+ <br />
+</div>
+<div class="round1"></div>
+<div class="round2"></div>
+<div class="round4"></div>
+</div>
+</div>
+</td>
+</tr>
+<tr>
+<td></td>
+<td class="vt issuecomment">
+<span class="indicator">&#9658;</span> <a href="https://www.google.com/accounts/ServiceLogin?service=code&amp;ltmpl=phosting&amp;continue=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6&amp;followup=http%3A%2F%2Fcode.google.com%2Fp%2Fallura-google-importer%2Fissues%2Fdetail%3Fid%3D6">Sign in</a> to add a comment
+ </td>
+</tr>
+</tbody>
+</table>
+<br />
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/dit_scripts.js"></script>
+</div>
+<form name="delcom" action="delComment.do?q=&amp;can=2&amp;groupby=&amp;sort=&amp;colspec=ID+Type+Status+Priority+Milestone+Owner+Summary" method="POST">
+<input type="hidden" name="sequence_num" value="" />
+<input type="hidden" name="mode" value="" />
+<input type="hidden" name="id" value="6" />
+<input type="hidden" name="token" value="" />
+</form>
+<div id="helparea"></div>
+<script type="text/javascript">
+ _onload();
+ function delComment(sequence_num, delete_mode) {
+ var f = document.forms["delcom"];
+ f.sequence_num.value = sequence_num;
+ f.mode.value = delete_mode;
+
+ f.submit();
+ return false;
+ }
+
+ _floatMetadata();
+</script>
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/kibbles.js"></script>
+<script type="text/javascript">
+ _setupKibblesOnDetailPage(
+ 'http://code.google.com/p/allura-google-importer/issues/list?cursor\x3dallura-google-importer%3A6',
+ '/p/allura-google-importer/issues/entry',
+ '../../allura-google-importer/issues/detail?id\x3d5',
+ '',
+ '', 'allura-google-importer', 6,
+ false, false, codesite_token);
+</script>
+<script type="text/javascript" src="http://www.gstatic.com/codesite/ph/3783617020303179221/js/ph_core.js"></script>
+</div>
+<div id="footer" dir="ltr">
+<div class="text">
+<a href="/projecthosting/terms.html">Terms</a> -
+ <a href="http://www.google.com/privacy.html">Privacy</a> -
+ <a href="/p/support/">Project Hosting Help</a>
+</div>
+</div>
+<div class="hostedBy" style="margin-top: -20px;">
+<span style="vertical-align: top;">Powered by <a href="http://code.google.com/projecthosting/">Google Project Hosting</a></span>
+</div>
+</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/79d94937/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 89aac5a..14333aa 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 from unittest import TestCase
+import pkg_resources
 
 import mock
 
@@ -131,3 +132,60 @@ class TestGoogleCodeProjectExtractor(TestCase):
         with self.assertRaises(Exception) as cm:
             extractor.get_repo_type()
         self.assertEqual(str(cm.exception), "Unknown repo type: cvs")
+
+    def test_empty_issue(self):
+        empty_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/empty-issue.html')).read()
+        gpe = self._make_extractor(empty_issue)
+        self.assertIsNone(gpe.get_issue_owner())
+        self.assertEqual(gpe.get_issue_status(), '')
+        self.assertEqual(gpe.get_issue_attachments(), [])
+        self.assertEqual(list(gpe.iter_comments()), [])
+
+    def test_get_issue_basic_fields(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        self.assertEqual(gpe.get_issue_creator().name, 'john...@gmail.com')
+        self.assertEqual(gpe.get_issue_creator().link, 'http://code.google.com/u/101557263855536553789/')
+        self.assertEqual(gpe.get_issue_owner().name, 'john...@gmail.com')
+        self.assertEqual(gpe.get_issue_owner().link, 'http://code.google.com/u/101557263855536553789/')
+        self.assertEqual(gpe.get_issue_status(), 'Started')
+        self.assertEqual(gpe.get_issue_summary(), 'Test Issue')
+        self.assertEqual(gpe.get_issue_description(),
+                'Test *Issue* for testing\n'
+                '\n'
+                '  1. Test List\n'
+                '  2. Item\n'
+                '\n'
+                '**Testing**\n'
+                '\n'
+                ' * Test list 2\n'
+                ' * Item\n'
+                '\n'
+                '# Test Section\n'
+                '\n'
+                '    p = source.test_issue.post()\n'
+                '    p.count = p.count *5 #* 6\n'
+                '\n'
+                'That\'s all'
+            )
+        self.assertEqual(gpe.get_issue_created_date(), 'Thu Aug  8 15:33:52 2013')
+
+    def test_get_issue_mod_date(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        self.assertEqual(gpe.get_issue_mod_date(), 'Thu Aug  8 15:36:57 2013')
+
+    def test_get_issue_labels(self):
+        test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
+        gpe = self._make_extractor(test_issue)
+        self.assertEqual(gpe.get_issue_labels(), [
+                'Type-Defect',
+                'Priority-Medium',
+                'Milestone-Release1.0',
+                'OpSys-All',
+                'Component-Logic',
+                'Performance',
+                'Security',
+                'OpSys-Windows',
+                'OpSys-OSX',
+            ])


[10/16] git commit: [#6464] Fixed issue with custom fields not persisting and added tests

Posted by jo...@apache.org.
[#6464] Fixed issue with custom fields not persisting and added tests

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/36cf1a28
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/36cf1a28
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/36cf1a28

Branch: refs/heads/cj/6464
Commit: 36cf1a281b1e0d88e384a18922e0edbb5a27d7b0
Parents: ea2fc78
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Mon Aug 12 22:25:23 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:21:00 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/helpers.py                    |   4 +-
 .../forgeimporters/google/__init__.py           |  19 ++
 ForgeImporters/forgeimporters/google/tracker.py |  65 ++---
 .../tests/google/functional/__init__.py         |  17 ++
 .../tests/google/functional/test_tracker.py     | 260 +++++++++++++++++++
 .../forgeimporters/tests/google/test_tracker.py |  25 +-
 6 files changed, 341 insertions(+), 49 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/36cf1a28/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 99a5d30..01586c2 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -98,7 +98,7 @@ re_preserve_spaces = re.compile(r'''
     ''', re.VERBOSE)
 re_angle_bracket_open = re.compile('<')
 re_angle_bracket_close = re.compile('>')
-md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!\\.+-])")
+md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!\\\.+-])")
 
 def make_safe_path_portion(ustr, relaxed=True):
     """Return an ascii representation of ``ustr`` that conforms to mount point
@@ -962,7 +962,7 @@ def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False
         text = html2text.escape_md_section(text, snob=True)
     except ImportError:
         # fall back to just escaping any MD-special chars
-        text = md_chars_matcher.sub(r"\\\\1", text)
+        text = md_chars_matcher_all.sub(r"\\\1", text)
     # prevent < and > from becoming tags
     text = re_angle_bracket_open.sub('&lt;', text)
     text = re_angle_bracket_close.sub('&gt;', text)

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/36cf1a28/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 88a1449..660853f 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -28,6 +28,7 @@ import logging
 
 from BeautifulSoup import BeautifulSoup
 
+from allura.lib import helpers as h
 from allura import model as M
 from forgeimporters.base import ProjectExtractor
 
@@ -261,6 +262,24 @@ class Comment(object):
         else:
             self.attachments = []
 
+    @property
+    def annotated_text(self):
+        text = (
+                u'*Originally posted by:* [{author.name}]({author.link})\n'
+                u'\n'
+                u'{body}\n'
+                u'\n'
+                u'{updates}'
+            ).format(
+                author=self.author,
+                body=h.plain2markdown(self.body, True),
+                updates='\n'.join(
+                        '**%s** %s' % (k,v)
+                        for k,v in self.updates.items()
+                    ),
+            )
+        return text
+
 class Attachment(object):
     def __init__(self, tag):
         self.filename = _as_text(tag).strip().split()[0]

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/36cf1a28/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index bd3a57f..fefd695 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -19,6 +19,7 @@ from collections import defaultdict
 from datetime import datetime
 
 from pylons import tmpl_context as c
+from pylons import app_globals as g
 from ming.orm import session, ThreadLocalORMSession
 
 from allura import model as M
@@ -44,22 +45,27 @@ class GoogleCodeTrackerImporter(ToolImporter):
 
     def import_tool(self, project, user, project_name, mount_point=None,
             mount_label=None, **kw):
-        c.app = project.install_app('tickets', mount_point, mount_label)
+        app = project.install_app('tickets', mount_point, mount_label)
+        app.globals.open_status_names = 'New Accepted Started'
+        app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         ThreadLocalORMSession.flush_all()
-        c.app.globals.open_status_names = 'New Accepted Started'
-        c.app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         self.custom_fields = {}
         try:
             M.session.artifact_orm_session._get().skip_mod_date = True
-            for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
-                ticket = TM.Ticket.new()
-                self.process_fields(ticket, issue)
-                self.process_labels(ticket, issue)
-                self.process_comments(ticket, issue)
-                session(ticket).flush(ticket)
-                session(ticket).expunge(ticket)
-            self.postprocess_custom_fields()
-            ThreadLocalORMSession.flush_all()
+            with h.push_config(c, user=M.User.anonymous(), app=app):
+                for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
+                    ticket = TM.Ticket.new()
+                    self.process_fields(ticket, issue)
+                    self.process_labels(ticket, issue)
+                    self.process_comments(ticket, issue)
+                    session(ticket).flush(ticket)
+                    session(ticket).expunge(ticket)
+                # app.globals gets expunged every time Ticket.new() is called :-(
+                app.globals = TM.Globals.query.get(app_config_id=app.config._id)
+                app.globals.custom_fields = self.postprocess_custom_fields()
+                ThreadLocalORMSession.flush_all()
+            g.post_event('project_updated')
+            return app
         finally:
             M.session.artifact_orm_session._get().skip_mod_date = False
 
@@ -78,13 +84,18 @@ class GoogleCodeTrackerImporter(ToolImporter):
         ticket.status = issue.get_issue_status()
         ticket.created_date = datetime.strptime(issue.get_issue_created_date(), '%c')
         ticket.mod_date = datetime.strptime(issue.get_issue_mod_date(), '%c')
+        owner = issue.get_issue_owner()
+        if owner:
+            owner_line = '*Originally owned by:* [{owner.name}]({owner.link})\n'.format(owner=owner)
+        else:
+            owner_line = ''
         ticket.description = (
                 u'*Originally created by:* [{creator.name}]({creator.link})\n'
-                '*Originally owned by:* [{owner.name}]({owner.link})\n'
-                '\n'
-                '{body}').format(
+                u'{owner}'
+                u'\n'
+                u'{body}').format(
                     creator=issue.get_issue_creator(),
-                    owner=issue.get_issue_owner(),
+                    owner=owner_line,
                     body=h.plain2markdown(issue.get_issue_description(), True),
                 )
         ticket.add_multiple_attachments(issue.get_issue_attachments())
@@ -106,25 +117,14 @@ class GoogleCodeTrackerImporter(ToolImporter):
     def process_comments(self, ticket, issue):
         for comment in issue.iter_comments():
             p = ticket.discussion_thread.add_post(
-                    text = (
-                        u'*Originally posted by:* [{author.name}]({author.link})\n'
-                        '\n'
-                        '{body}\n'
-                        '\n'
-                        '{updates}').format(
-                            author=comment.author,
-                            body=h.plain2markdown(comment.body, True),
-                            updates='\n'.join(
-                                '**%s** %s' % (k,v)
-                                for k,v in comment.updates.items()
-                            ),
-                    )
+                    text = comment.annotated_text,
+                    ignore_security = True,
+                    timestamp = datetime.strptime(comment.created_date, '%c'),
                 )
-            p.created_date = p.timestamp = datetime.strptime(comment.created_date, '%c')
             p.add_multiple_attachments(comment.attachments)
 
     def postprocess_custom_fields(self):
-        c.app.globals.custom_fields = []
+        custom_fields = []
         for name, field in self.custom_fields.iteritems():
             if field['name'] == '_milestone':
                 field['milestones'] = [{
@@ -137,4 +137,5 @@ class GoogleCodeTrackerImporter(ToolImporter):
                 field['options'] = ' '.join(field['options'])
             else:
                 field['options'] = ''
-            c.app.globals.custom_fields.append(field)
+            custom_fields.append(field)
+        return custom_fields

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/36cf1a28/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/__init__.py b/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
new file mode 100644
index 0000000..77505f1
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/google/functional/__init__.py
@@ -0,0 +1,17 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/36cf1a28/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
new file mode 100644
index 0000000..206ac7c
--- /dev/null
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -0,0 +1,260 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
+from unittest import TestCase
+import pkg_resources
+from functools import wraps
+from datetime import datetime
+
+from BeautifulSoup import BeautifulSoup
+import mock
+from ming.orm import ThreadLocalORMSession
+from pylons import tmpl_context as c
+from IPython.testing.decorators import module_not_available, skipif
+
+from alluratest.controller import setup_basic_test
+from allura import model as M
+from forgetracker import model as TM
+from .... import google
+from ....google import tracker
+
+
+def without_html2text(func):
+    @wraps(func)
+    def wrapped(*args, **kw):
+        try:
+            import html2text
+        except ImportError:
+            return func(*args, **kw)
+        else:
+            with mock.patch.object(html2text, 'escape_md_section') as ems:
+                ems.side_effect = ImportError
+                return func(*args, **kw)
+    return wrapped
+
+class TestGCTrackerImporter(TestCase):
+    def _make_extractor(self, html):
+        with mock.patch.object(google, 'urllib2') as urllib2:
+            urllib2.urlopen.return_value = ''
+            extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
+        extractor.page = BeautifulSoup(html)
+        extractor.url = "http://test/issue/?id=1"
+        return extractor
+
+    def _make_ticket(self, issue):
+        self.assertIsNone(self.project.app_instance('test-issue'))
+        with mock.patch.object(google, 'urllib2') as urllib2,\
+             mock.patch.object(google.tracker, 'GoogleCodeProjectExtractor') as GPE:
+            urllib2.urlopen = lambda url: mock.Mock(read=lambda: url)
+            GPE.iter_issues.return_value = [issue]
+            gti = google.tracker.GoogleCodeTrackerImporter()
+            gti.import_tool(self.project, self.user, 'test-issue-project', mount_point='test-issue')
+        c.app = self.project.app_instance('test-issue')
+        query = TM.Ticket.query.find({'app_config_id': c.app.config._id})
+        self.assertEqual(query.count(), 1)
+        ticket = query.all()[0]
+        return ticket
+
+    def setUp(self, *a, **kw):
+        super(TestGCTrackerImporter, self).setUp(*a, **kw)
+        setup_basic_test()
+        self.empty_issue = self._make_extractor(open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/empty-issue.html')).read())
+        self.test_issue = self._make_extractor(open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read())
+        c.project = self.project = M.Project.query.get(shortname='test')
+        c.user = self.user = M.User.query.get(username='test-admin')
+
+    def test_empty_issue(self):
+        ticket = self._make_ticket(self.empty_issue)
+        self.assertEqual(ticket.summary, 'Empty Issue')
+        self.assertEqual(ticket.description, '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n\nEmpty')
+        self.assertEqual(ticket.status, '')
+        self.assertEqual(ticket.milestone, '')
+        self.assertEqual(ticket.custom_fields, {})
+
+    @without_html2text
+    def test_issue_basic_fields(self):
+        anon = M.User.anonymous()
+        ticket = self._make_ticket(self.test_issue)
+        self.assertEqual(ticket.reported_by, anon)
+        self.assertIsNone(ticket.assigned_to_id)
+        self.assertEqual(ticket.summary, 'Test Issue')
+        self.assertEqual(ticket.description,
+                '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '*Originally owned by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '\n'
+                'Test \\*Issue\\* for testing\n'
+                '\n'
+                '&nbsp; 1\\. Test List\n'
+                '&nbsp; 2\\. Item\n'
+                '\n'
+                '\\*\\*Testing\\*\\*\n'
+                '\n'
+                ' \\* Test list 2\n'
+                ' \\* Item\n'
+                '\n'
+                '\\# Test Section\n'
+                '\n'
+                '&nbsp;&nbsp;&nbsp; p = source\\.test\\_issue\\.post\\(\\)\n'
+                '&nbsp;&nbsp;&nbsp; p\\.count = p\\.count \\*5 \\#\\* 6\n'
+                '\n'
+                'That\'s all'
+            )
+        self.assertEqual(ticket.status, 'Started')
+        self.assertEqual(ticket.created_date, datetime(2013, 8, 8, 15, 33, 52))
+        self.assertEqual(ticket.mod_date, datetime(2013, 8, 8, 15, 36, 57))
+        self.assertEqual(ticket.custom_fields, {
+                '_priority': 'Medium',
+                '_opsys': 'All, OSX, Windows',
+                '_component': 'Logic',
+                '_type': 'Defect',
+                '_milestone': 'Release1.0'
+            })
+        self.assertEqual(ticket.labels, ['Performance', 'Security'])
+
+    @skipif(module_not_available('html2text'))
+    def test_html2text_escaping(self):
+        ticket = self._make_ticket(self.test_issue)
+        self.assertEqual(ticket.description,
+                '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '*Originally owned by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                '\n'
+                'Test \\*Issue\\* for testing\n'
+                '\n'
+                '&nbsp; 1. Test List\n'
+                '&nbsp; 2. Item\n'
+                '\n'
+                '\\*\\*Testing\\*\\*\n'
+                '\n'
+                ' \\* Test list 2\n'
+                ' \\* Item\n'
+                '\n'
+                '\\# Test Section\n'
+                '\n'
+                '&nbsp;&nbsp;&nbsp; p = source.test\\_issue.post\\(\\)\n'
+                '&nbsp;&nbsp;&nbsp; p.count = p.count \\*5 \\#\\* 6\n'
+                '\n'
+                'That\'s all'
+            )
+
+    def _assert_attachments(self, actual, *expected):
+        self.assertEqual(actual.count(), len(expected))
+        atts = set((a.filename, a.content_type, a.rfile().read()) for a in actual)
+        self.assertEqual(atts, set(expected))
+
+    def test_attachements(self):
+        ticket = self._make_ticket(self.test_issue)
+        self._assert_attachments(ticket.attachments,
+                ('at1.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000000&name=at1.txt&token=3REU1M3JUUMt0rJUg7ldcELt6LA%3A1376059941255'),
+                ('at2.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000001&name=at2.txt&token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255'),
+            )
+
+    @without_html2text
+    def test_comments(self):
+        anon = M.User.anonymous()
+        ticket = self._make_ticket(self.test_issue)
+        actual_comments = ticket.discussion_thread.find_posts()
+        expected_comments = [
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 35, 15),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Test \\*comment\\* is a comment\n'
+                            '\n'
+                            '**Labels:** -OpSys-Linux OpSys-Windows\n'
+                            '**Status:** Started'
+                        ),
+                    'attachments': [
+                            ('at2.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60001000&name=at2.txt&token=JOSo4duwaN2FCKZrwYOQ-nx9r7U%3A1376001446667'),
+                        ],
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 35, 34),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Another comment\n\n'
+                        ),
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 36, 39),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Last comment\n\n'
+                        ),
+                    'attachments': [
+                            ('at4.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60003000&name=at4.txt&token=6Ny2zYHmV6b82dqxyoiH6HUYoC4%3A1376001446667'),
+                            ('at1.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=60003001&name=at1.txt&token=NS8aMvWsKzTAPuY2kniJG5aLzPg%3A1376001446667'),
+                        ],
+                },
+                {
+                    'timestamp': datetime(2013, 8, 8, 15, 36, 57),
+                    'text': (
+                            '*Originally posted by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n'
+                            '\n'
+                            'Oh, I forgot one\n'
+                            '\n'
+                            '**Labels:** OpSys-OSX'
+                        ),
+                },
+            ]
+        self.assertEqual(len(actual_comments), len(expected_comments))
+        for actual, expected in zip(actual_comments, expected_comments):
+            self.assertEqual(actual.author(), anon)
+            self.assertEqual(actual.timestamp, expected['timestamp'])
+            self.assertEqual(actual.text, expected['text'])
+            if 'attachments' in expected:
+                self._assert_attachments(actual.attachments, *expected['attachments'])
+
+    def test_globals(self):
+        globals = self._make_ticket(self.test_issue).globals
+        self.assertItemsEqual(globals.custom_fields, [
+                {
+                    'label': 'Milestone',
+                    'name': '_milestone',
+                    'type': 'milestone',
+                    'options': '',
+                    'milestones': [
+                            {'name': 'Release1.0', 'due_date': None, 'complete': False},
+                        ],
+                },
+                {
+                    'label': 'Type',
+                    'name': '_type',
+                    'type': 'select',
+                    'options': 'Defect',
+                },
+                {
+                    'label': 'Priority',
+                    'name': '_priority',
+                    'type': 'select',
+                    'options': 'Medium',
+                },
+                {
+                    'label': 'OpSys',
+                    'name': '_opsys',
+                    'type': 'string',
+                    'options': '',
+                },
+                {
+                    'label': 'Component',
+                    'name': '_component',
+                    'type': 'string',
+                    'options': '',
+                },
+            ])

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/36cf1a28/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index a1f0a28..3efd97d 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -27,9 +27,10 @@ class TestTrackerImporter(TestCase):
     @mock.patch.object(tracker, 'c')
     @mock.patch.object(tracker, 'ThreadLocalORMSession')
     @mock.patch.object(tracker, 'session')
+    @mock.patch.object(tracker, 'M')
     @mock.patch.object(tracker, 'TM')
     @mock.patch.object(tracker, 'GoogleCodeProjectExtractor')
-    def test_import_tool(self, gpe, TM, session, tlos, c):
+    def test_import_tool(self, gpe, TM, M, session, tlos, c):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_fields = mock.Mock()
         importer.process_labels = mock.Mock()
@@ -158,12 +159,14 @@ class TestTrackerImporter(TestCase):
                 mock.Mock(
                     author=_author(1),
                     body='text1',
+                    annotated_text='annotated1',
                     attachments='attachments1',
                     created_date='Mon Jul 15 00:00:00 2013',
                 ),
                 mock.Mock(
                     author=_author(2),
                     body='text2',
+                    annotated_text='annotated2',
                     attachments='attachments2',
                     created_date='Mon Jul 16 00:00:00 2013',
                 ),
@@ -177,24 +180,16 @@ class TestTrackerImporter(TestCase):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_comments(ticket, issue)
         self.assertEqual(ticket.discussion_thread.add_post.call_args_list[0], mock.call(
-                text='*Originally posted by:* [author1](author1_link)\n'
-                '\n'
-                'text1\n'
-                '\n'
-                '**Foo:** Bar\n'
-                '**Baz:** Qux'
+                text='annotated1',
+                timestamp=datetime(2013, 7, 15),
+                ignore_security=True,
             ))
-        self.assertEqual(posts[0].created_date, datetime(2013, 7, 15))
-        self.assertEqual(posts[0].timestamp, datetime(2013, 7, 15))
         posts[0].add_multiple_attachments.assert_called_once_with('attachments1')
         self.assertEqual(ticket.discussion_thread.add_post.call_args_list[1], mock.call(
-                text='*Originally posted by:* [author2](author2_link)\n'
-                '\n'
-                'text2\n'
-                '\n'
+                text='annotated2',
+                timestamp=datetime(2013, 7, 16),
+                ignore_security=True,
             ))
-        self.assertEqual(posts[1].created_date, datetime(2013, 7, 16))
-        self.assertEqual(posts[1].timestamp, datetime(2013, 7, 16))
         posts[1].add_multiple_attachments.assert_called_once_with('attachments2')
 
     @mock.patch.object(tracker, 'c')


[13/16] git commit: [#6464] Fixed handling of attachment links in GC Tracker importer

Posted by jo...@apache.org.
[#6464] Fixed handling of attachment links in GC Tracker importer

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/d1ac784e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/d1ac784e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/d1ac784e

Branch: refs/heads/cj/6464
Commit: d1ac784eeb2665a3d016b5cef592b887e6af6774
Parents: 8facbc5
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 16 22:04:00 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:21:00 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/__init__.py    | 16 ++++++++--------
 .../forgeimporters/tests/google/test_extractor.py   |  6 +++---
 2 files changed, 11 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d1ac784e/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 22be7c2..fbf3eb0 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -17,7 +17,7 @@
 
 import re
 import urllib
-from urlparse import urlparse, urljoin
+from urlparse import urlparse, urljoin, parse_qs
 from collections import defaultdict
 from contextlib import closing
 try:
@@ -123,7 +123,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
 
     def get_icon(self, project):
         page = self.get_page('project_info')
-        icon_url = urljoin(self.url, page.find(itemprop='image').attrMap['src'])
+        icon_url = urljoin(self.url, page.find(itemprop='image').get('src'))
         if icon_url == self.DEFAULT_ICON:
             return
         icon_name = urllib.unquote(urlparse(icon_url).path).split('/')[-1]
@@ -222,7 +222,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
     def get_issue_attachments(self):
         attachments = self.page.find(id='hc0').find('div', 'attachments')
         if attachments:
-            return map(Attachment, attachments.findAll('tr'))
+            return [Attachment(a.parent) for a in attachments.findAll('a', text='Download')]
         else:
             return []
 
@@ -233,8 +233,8 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
 class UserLink(object):
     def __init__(self, tag):
         self.name = tag.string.strip()
-        if 'href' in tag.attrMap:
-            self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.attrMap['href'])
+        if tag.get('href'):
+            self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.get('href'))
         else:
             self.url = None
 
@@ -264,7 +264,7 @@ class Comment(object):
     def _get_attachments(self, tag):
         attachments = tag.find('div', 'attachments')
         if attachments:
-            self.attachments = map(Attachment, attachments.findAll('tr'))
+            self.attachments = [Attachment(a.parent) for a in attachments.findAll('a', text='Download')]
         else:
             self.attachments = []
 
@@ -288,8 +288,8 @@ class Comment(object):
 
 class Attachment(object):
     def __init__(self, tag):
-        self.filename = _as_text(tag).strip().split()[0]
-        self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.a.get('href'))
+        self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.get('href'))
+        self.filename = parse_qs(urlparse(self.url).query)['name'][0]
         self.type = None
 
     @property

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/d1ac784e/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 4157c98..e208e8d 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -76,7 +76,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
     def test_get_icon(self, M, StringIO):
         self.urlopen.return_value.info.return_value = {'content-type': 'image/png'}
         extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
-        extractor.page.find.return_value.attrMap = {'src': 'http://example.com/foo/bar/my-logo.png'}
+        extractor.page.find.return_value.get.return_value = 'http://example.com/foo/bar/my-logo.png'
         self.urlopen.reset_mock()
 
         extractor.get_icon(self.project)
@@ -260,13 +260,13 @@ class TestUserLink(TestCase):
     def test_plain(self):
         tag = mock.Mock()
         tag.string.strip.return_value = 'name'
-        tag.attrMap = {}
+        tag.get.return_value = None
         link = google.UserLink(tag)
         self.assertEqual(str(link), 'name')
 
     def test_linked(self):
         tag = mock.Mock()
         tag.string.strip.return_value = 'name'
-        tag.attrMap = {'href': '/p/project'}
+        tag.get.return_value = '/p/project'
         link = google.UserLink(tag)
         self.assertEqual(str(link), '[name](http://code.google.com/p/project)')


[02/16] git commit: [#6480] add Apache License header to Allura/allura/tests/test_decorators.py

Posted by jo...@apache.org.
[#6480] add Apache License header to Allura/allura/tests/test_decorators.py


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/1c62317d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/1c62317d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/1c62317d

Branch: refs/heads/cj/6464
Commit: 1c62317dbbb01bfd413b83aa7e6906f9a1505249
Parents: 2b0a101
Author: Dave Brondsema <db...@slashdotmedia.com>
Authored: Fri Aug 16 15:17:14 2013 +0000
Committer: Dave Brondsema <db...@slashdotmedia.com>
Committed: Fri Aug 16 15:17:14 2013 +0000

----------------------------------------------------------------------
 Allura/allura/tests/test_decorators.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/1c62317d/Allura/allura/tests/test_decorators.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_decorators.py b/Allura/allura/tests/test_decorators.py
index 41ba631..d4b70a5 100644
--- a/Allura/allura/tests/test_decorators.py
+++ b/Allura/allura/tests/test_decorators.py
@@ -1,3 +1,20 @@
+#       Licensed to the Apache Software Foundation (ASF) under one
+#       or more contributor license agreements.  See the NOTICE file
+#       distributed with this work for additional information
+#       regarding copyright ownership.  The ASF licenses this file
+#       to you under the Apache License, Version 2.0 (the
+#       "License"); you may not use this file except in compliance
+#       with the License.  You may obtain a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#       Unless required by applicable law or agreed to in writing,
+#       software distributed under the License is distributed on an
+#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#       KIND, either express or implied.  See the License for the
+#       specific language governing permissions and limitations
+#       under the License.
+
 from unittest import TestCase
 
 from mock import patch


[14/16] git commit: [#6464] Refactored plain2markdown tests

Posted by jo...@apache.org.
[#6464] Refactored plain2markdown tests

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/38c8c8ea
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/38c8c8ea
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/38c8c8ea

Branch: refs/heads/cj/6464
Commit: 38c8c8eafa93419e84d0a64fb6495d4472f5f5a1
Parents: 123cb15
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed Aug 14 18:47:04 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:21:00 2013 +0000

----------------------------------------------------------------------
 Allura/allura/tests/decorators.py               | 11 +++
 Allura/allura/tests/test_helpers.py             | 85 ++++++++++++++++++++
 ForgeBlog/forgeblog/tests/test_commands.py      | 42 ----------
 .../forgeimporters/google/__init__.py           |  2 +-
 ForgeImporters/forgeimporters/google/tracker.py |  2 +-
 .../tests/google/functional/test_tracker.py     | 18 +----
 6 files changed, 101 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/38c8c8ea/Allura/allura/tests/decorators.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/decorators.py b/Allura/allura/tests/decorators.py
index 745b008..8c9e4c9 100644
--- a/Allura/allura/tests/decorators.py
+++ b/Allura/allura/tests/decorators.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+import sys
 from functools import wraps
 import contextlib
 
@@ -104,3 +105,13 @@ class raises(object):
                 return False
         else:
             raise AssertionError('Did not raise %s' % self.ExcType)
+
+
+def without_module(*module_names):
+    def _without_module(func):
+        @wraps(func)
+        def wrapped(*a, **kw):
+            with patch.dict(sys.modules, {m: None for m in module_names}):
+                return func(*a, **kw)
+        return wrapped
+    return _without_module

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/38c8c8ea/Allura/allura/tests/test_helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 4ec91df..03a530c 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -23,6 +23,8 @@ from mock import Mock, patch
 
 from pylons import tmpl_context as c
 from nose.tools import eq_, assert_equals
+from IPython.testing.decorators import skipif, module_not_available
+from datadiff import tools as dd
 
 from allura import model as M
 from allura.lib import helpers as h
@@ -263,6 +265,89 @@ def test_notifications_disabled():
     assert_equals(project.notifications_disabled, False)
 
 
+@skipif(module_not_available('html2text'))
+def test_plain2markdown_with_html2text():
+    """Test plain2markdown using html2text to escape markdown, if available."""
+    text = '''paragraph
+
+    4 spaces before this
+
+    *blah*
+
+here's a <tag> that should be <b>preserved</b>
+Literal &gt; &Ograve; &frac14; &amp; &#38; &#x123F;
+M & Ms - doesn't get escaped
+http://blah.com/?x=y&a=b - not escaped either
+'''
+
+    expected = '''paragraph
+
+4 spaces before this
+
+\*blah\*
+
+here's a &lt;tag&gt; that should be &lt;b&gt;preserved&lt;/b&gt;
+Literal &amp;gt; &amp;Ograve; &amp;frac14; &amp;amp; &amp;\#38; &amp;\#x123F;
+M & Ms - doesn't get escaped
+http://blah.com/?x=y&a=b - not escaped either
+'''
+
+    assert_equals(h.plain2markdown(text), expected)
+
+    assert_equals(h.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
+                'a foo&nbsp; bar\n\n&nbsp;&nbsp;&nbsp; code here?')
+
+    assert_equals(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
+                 '&nbsp;&nbsp;&nbsp; tab before \(stuff\)')
+
+    assert_equals(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
+                 'tab before \(stuff\)')
+
+@td.without_module('html2text')
+def test_plain2markdown():
+    """Test plain2markdown using fallback regexp to escape markdown.
+
+    Potentially MD-special characters are aggresively escaped, as without
+    knowledge of the MD parsing rules it's better to be excessive but safe.
+    """
+    text = '''paragraph
+
+    4 spaces before this
+
+    *blah*
+
+here's a <tag> that should be <b>preserved</b>
+Literal &gt; &Ograve; &frac14; &amp; &#38; &#x123F;
+M & Ms - amp doesn't get escaped
+http://blah.com/?x=y&a=b - not escaped either
+back\\-slash escaped
+'''
+
+    expected = '''paragraph
+
+4 spaces before this
+
+\*blah\*
+
+here's a &lt;tag&gt; that should be &lt;b&gt;preserved&lt;/b&gt;
+Literal &amp;gt; &amp;Ograve; &amp;frac14; &amp;amp; &amp;\#38; &amp;\#x123F;
+M & Ms \- amp doesn't get escaped
+http://blah\.com/?x=y&a=b \- not escaped either
+back\\\\\-slash escaped
+'''
+
+    dd.assert_equal(h.plain2markdown(text), expected)
+
+    dd.assert_equal(h.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
+                'a foo&nbsp; bar\n\n&nbsp;&nbsp;&nbsp; code here?')
+
+    dd.assert_equal(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
+                 '&nbsp;&nbsp;&nbsp; tab before \(stuff\)')
+
+    dd.assert_equal(h.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
+                 'tab before \(stuff\)')
+
+
 class TestUrlOpen(TestCase):
     @patch('allura.lib.helpers.urllib2')
     def test_no_error(self, urllib2):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/38c8c8ea/ForgeBlog/forgeblog/tests/test_commands.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/tests/test_commands.py b/ForgeBlog/forgeblog/tests/test_commands.py
index 472efda..0b2804a 100644
--- a/ForgeBlog/forgeblog/tests/test_commands.py
+++ b/ForgeBlog/forgeblog/tests/test_commands.py
@@ -168,45 +168,3 @@ def test_plaintext_preprocessor_wrapped():
         '<p>#foo bar <a class="" href="../baz">baz</a> foo bar </p>\n'
         '<p>#foo bar <a class="" href="../baz"> baz </a></p></div>'
     )
-
-@skipif(module_not_available('html2text'))
-def test_plain2markdown():
-    text = '''paragraph
-
-    4 spaces before this
-
-    *blah*
-
-here's a <tag> that should be <b>preserved</b>
-Literal &gt; &Ograve; &frac14; &amp; &#38; &#x123F;
-M & Ms - doesn't get escaped
-http://blah.com/?x=y&a=b - not escaped either
-'''
-
-    expected = '''paragraph
-
-4 spaces before this
-
-\*blah\*
-
-here's a &lt;tag&gt; that should be &lt;b&gt;preserved&lt;/b&gt;
-Literal &amp;gt; &amp;Ograve; &amp;frac14; &amp;amp; &amp;\#38; &amp;\#x123F;
-M & Ms - doesn't get escaped
-http://blah.com/?x=y&a=b - not escaped either
-'''
-    # note: the \# isn't necessary it could be just # but that's the way
-    # html2text escapes all #s currently.  The extra escaping of \# ends up
-    # being ok though when rendered
-
-    from forgeblog.command import rssfeeds
-
-    assert_equal(rssfeeds.plain2markdown(text), expected)
-
-    assert_equal(rssfeeds.plain2markdown('a foo  bar\n\n    code here?', preserve_multiple_spaces=True),
-                'a foo&nbsp; bar\n\n&nbsp;&nbsp;&nbsp; code here?')
-
-    assert_equal(rssfeeds.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=True),
-                 '&nbsp;&nbsp;&nbsp; tab before \(stuff\)')
-
-    assert_equal(rssfeeds.plain2markdown('\ttab before (stuff)', preserve_multiple_spaces=False),
-                 'tab before \(stuff\)')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/38c8c8ea/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 90319cd..f60bc58 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -269,7 +269,7 @@ class Comment(object):
                 u'{updates}'
             ).format(
                 author=self.author,
-                body=h.plain2markdown(self.body, True),
+                body=h.plain2markdown(self.body, preserve_multiple_spaces=True),
                 updates='\n'.join(
                         '**%s** %s' % (k,v)
                         for k,v in self.updates.items()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/38c8c8ea/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index fefd695..3257c5a 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -96,7 +96,7 @@ class GoogleCodeTrackerImporter(ToolImporter):
                 u'{body}').format(
                     creator=issue.get_issue_creator(),
                     owner=owner_line,
-                    body=h.plain2markdown(issue.get_issue_description(), True),
+                    body=h.plain2markdown(issue.get_issue_description(), preserve_multiple_spaces=True),
                 )
         ticket.add_multiple_attachments(issue.get_issue_attachments())
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/38c8c8ea/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
index 206ac7c..7d67255 100644
--- a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -27,25 +27,13 @@ from pylons import tmpl_context as c
 from IPython.testing.decorators import module_not_available, skipif
 
 from alluratest.controller import setup_basic_test
+from allura.tests.decorators import without_module
 from allura import model as M
 from forgetracker import model as TM
 from .... import google
 from ....google import tracker
 
 
-def without_html2text(func):
-    @wraps(func)
-    def wrapped(*args, **kw):
-        try:
-            import html2text
-        except ImportError:
-            return func(*args, **kw)
-        else:
-            with mock.patch.object(html2text, 'escape_md_section') as ems:
-                ems.side_effect = ImportError
-                return func(*args, **kw)
-    return wrapped
-
 class TestGCTrackerImporter(TestCase):
     def _make_extractor(self, html):
         with mock.patch.object(google, 'urllib2') as urllib2:
@@ -85,7 +73,7 @@ class TestGCTrackerImporter(TestCase):
         self.assertEqual(ticket.milestone, '')
         self.assertEqual(ticket.custom_fields, {})
 
-    @without_html2text
+    @without_module('html2text')
     def test_issue_basic_fields(self):
         anon = M.User.anonymous()
         ticket = self._make_ticket(self.test_issue)
@@ -162,7 +150,7 @@ class TestGCTrackerImporter(TestCase):
                 ('at2.txt', 'text/plain', 'http://allura-google-importer.googlecode.com/issues/attachment?aid=70000001&name=at2.txt&token=C9Hn4s1-g38hlSggRGo65VZM1ys%3A1376059941255'),
             )
 
-    @without_html2text
+    @without_module('html2text')
     def test_comments(self):
         anon = M.User.anonymous()
         ticket = self._make_ticket(self.test_issue)


[04/16] git commit: [#6464] Refactored plain2markdown to Allura helpers

Posted by jo...@apache.org.
[#6464] Refactored plain2markdown to Allura helpers

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/67852feb
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/67852feb
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/67852feb

Branch: refs/heads/cj/6464
Commit: 67852feb0b9fe82ab6eeaf1783fbeb5d0810a5ee
Parents: 547cad2
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu Aug 8 18:49:51 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:20:59 2013 +0000

----------------------------------------------------------------------
 Allura/allura/lib/helpers.py            | 41 ++++++++++++++++++++++++++++
 ForgeBlog/forgeblog/command/rssfeeds.py | 36 +-----------------------
 2 files changed, 42 insertions(+), 35 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/67852feb/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 0511dc7..99a5d30 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -80,6 +80,26 @@ re_clean_vardec_key = re.compile(r'''\A
 )+
 \Z''', re.VERBOSE)
 
+# markdown escaping regexps
+re_amp = re.compile(r'''
+    [&]          # amp
+    (?=          # look ahead for:
+      ([a-zA-Z0-9]+;)  # named HTML entity
+      |
+      (\#[0-9]+;)      # decimal entity
+      |
+      (\#x[0-9A-F]+;)  # hex entity
+    )
+    ''', re.VERBOSE)
+re_leading_spaces = re.compile(r'^[\t ]+', re.MULTILINE)
+re_preserve_spaces = re.compile(r'''
+    [ ]           # space
+    (?=[ ])       # lookahead for a space
+    ''', re.VERBOSE)
+re_angle_bracket_open = re.compile('<')
+re_angle_bracket_close = re.compile('>')
+md_chars_matcher_all = re.compile(r"([`\*_{}\[\]\(\)#!\\.+-])")
+
 def make_safe_path_portion(ustr, relaxed=True):
     """Return an ascii representation of ``ustr`` that conforms to mount point
     naming :attr:`rules <re_tool_mount_point_fragment>`.
@@ -926,3 +946,24 @@ def urlopen(url, retries=3, codes=(408,)):
             else:
                 log.exception('Failed after %s retries: %s', retries, e)
                 raise
+
+
+def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False):
+    if not has_html_entities:
+        # prevent &foo; and &#123; from becoming HTML entities
+        text = re_amp.sub('&amp;', text)
+    # avoid accidental 4-space indentations creating code blocks
+    if preserve_multiple_spaces:
+        text = text.replace('\t', ' ' * 4)
+        text = re_preserve_spaces.sub('&nbsp;', text)
+        # try to use html2text for most of the escaping
+        import html2text
+        html2text.BODY_WIDTH = 0
+        text = html2text.escape_md_section(text, snob=True)
+    except ImportError:
+        # fall back to just escaping any MD-special chars
+        text = md_chars_matcher.sub(r"\\\\1", text)
+    # prevent < and > from becoming tags
+    text = re_angle_bracket_open.sub('&lt;', text)
+    text = re_angle_bracket_close.sub('&gt;', text)
+    return text

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/67852feb/ForgeBlog/forgeblog/command/rssfeeds.py
----------------------------------------------------------------------
diff --git a/ForgeBlog/forgeblog/command/rssfeeds.py b/ForgeBlog/forgeblog/command/rssfeeds.py
index 305bc5a..f4b5c75 100644
--- a/ForgeBlog/forgeblog/command/rssfeeds.py
+++ b/ForgeBlog/forgeblog/command/rssfeeds.py
@@ -34,6 +34,7 @@ from forgeblog import version
 from forgeblog.main import ForgeBlogApp
 from allura.lib import exceptions
 from allura.lib.helpers import exceptionless
+from allura.lib.helpers import plain2markdown
 
 ## Everything in this file depends on html2text,
 ## so import attempt is placed in global scope.
@@ -45,41 +46,6 @@ except ImportError:
 
 html2text.BODY_WIDTH = 0
 
-re_amp = re.compile(r'''
-    [&]          # amp
-    (?=          # look ahead for:
-      ([a-zA-Z0-9]+;)  # named HTML entity
-      |
-      (\#[0-9]+;)      # decimal entity
-      |
-      (\#x[0-9A-F]+;)  # hex entity
-    )
-    ''', re.VERBOSE)
-re_leading_spaces = re.compile(r'^[\t ]+', re.MULTILINE)
-re_preserve_spaces = re.compile(r'''
-    [ ]           # space
-    (?=[ ])       # lookahead for a space
-    ''', re.VERBOSE)
-re_angle_bracket_open = re.compile('<')
-re_angle_bracket_close = re.compile('>')
-def plain2markdown(text, preserve_multiple_spaces=False, has_html_entities=False):
-    if not has_html_entities:
-        # prevent &foo; and &#123; from becoming HTML entities
-        text = re_amp.sub('&amp;', text)
-    # avoid accidental 4-space indentations creating code blocks
-    if preserve_multiple_spaces:
-        text = text.replace('\t', ' ' * 4)
-        text = re_preserve_spaces.sub('&nbsp;', text)
-    else:
-        text = re_leading_spaces.sub('', text)
-    # use html2text for most of the escaping
-    text = html2text.escape_md_section(text, snob=True)
-    # prevent < and > from becoming tags
-    text = re_angle_bracket_open.sub('&lt;', text)
-    text = re_angle_bracket_close.sub('&gt;', text)
-    return text
-
-
 class RssFeedsCommand(base.BlogCommand):
     summary = 'Rss feed client'
     parser = base.BlogCommand.standard_parser(verbose=True)


[15/16] git commit: [#6464] Fixed issues from rebase

Posted by jo...@apache.org.
[#6464] Fixed issues from rebase

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/123cb154
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/123cb154
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/123cb154

Branch: refs/heads/cj/6464
Commit: 123cb15447f9159f8bf3db38d13785e8f031049d
Parents: 3eab85b
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed Aug 14 16:26:53 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:21:00 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/__init__.py        |  7 ++-----
 ForgeImporters/forgeimporters/google/tests/test_code.py |  4 +++-
 .../forgeimporters/tests/google/test_extractor.py       | 12 +++++++++---
 .../forgeimporters/tests/google/test_tracker.py         |  4 +++-
 4 files changed, 17 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/123cb154/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 660853f..90319cd 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -79,7 +79,6 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
     DEFAULT_ICON = 'http://www.gstatic.com/codesite/ph/images/defaultlogo.png'
 
     def __init__(self, project_name, page_name=None, **kw):
-        self.url = self.PAGE_MAP[page_name].format(
         self.project_name = project_name
         self._page_cache = {}
         self.url = None
@@ -105,7 +104,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
         if self.url in self._page_cache:
             self.page = self._page_cache[self.url]
         else:
-            self.page = self._page_cache[page_name_or_url] = \
+            self.page = self._page_cache[self.url] = \
                     BeautifulSoup(self.urlopen(self.url))
         return self.page
 
@@ -116,9 +115,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
 
         """
         return self.PAGE_MAP[page_name].format(
-            project_name = urllib.quote(self.project_name),
-            **kw,
-        )
+            project_name = urllib.quote(self.project_name), **kw)
 
     def get_short_description(self, project):
         page = self.get_page('project_info')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/123cb154/ForgeImporters/forgeimporters/google/tests/test_code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tests/test_code.py b/ForgeImporters/forgeimporters/google/tests/test_code.py
index 13ec0c3..950246a 100644
--- a/ForgeImporters/forgeimporters/google/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/google/tests/test_code.py
@@ -56,9 +56,10 @@ class TestGoogleRepoImporter(TestCase):
         project.get_tool_data.side_effect = lambda *args: gc_proj_name
         return project
 
+    @patch('forgeimporters.google.code.g')
     @patch('forgeimporters.google.code.GoogleCodeProjectExtractor')
     @patch('forgeimporters.google.code.get_repo_url')
-    def test_import_tool_happy_path(self, get_repo_url, gcpe):
+    def test_import_tool_happy_path(self, get_repo_url, gcpe, g):
         gcpe.return_value.get_repo_type.return_value = 'git'
         get_repo_url.return_value = 'http://remote/clone/url/'
         p = self._make_project(gc_proj_name='myproject')
@@ -70,6 +71,7 @@ class TestGoogleRepoImporter(TestCase):
                 mount_label='Code',
                 init_from_url='http://remote/clone/url/',
                 )
+        g.post_event.assert_called_once_with('project_updated')
 
 
 class TestGoogleRepoImportController(TestController, TestCase):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/123cb154/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index bac1750..7a485d6 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -45,14 +45,20 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self.assertEqual(extractor.page, self.soup.return_value)
 
     def test_get_page(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
+        self.assertEqual(1, self.urlopen.call_count)
+        page = extractor.get_page('project_info')
         self.assertEqual(1, self.urlopen.call_count)
+        self.assertEqual(page, extractor._page_cache['http://code.google.com/p/my-project/'])
         page = extractor.get_page('project_info')
         self.assertEqual(1, self.urlopen.call_count)
-        self.assertEqual(page, extractor._page_cache['project_info'])
+        self.assertEqual(page, extractor._page_cache['http://code.google.com/p/my-project/'])
+        page = extractor.get_page('source_browse')
+        self.assertEqual(2, self.urlopen.call_count)
+        self.assertEqual(page, extractor._page_cache['http://code.google.com/p/my-project/source/browse/'])
 
     def test_get_page_url(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project')
+        extractor = google.GoogleCodeProjectExtractor('my-project')
         self.assertEqual(extractor.get_page_url('project_info'),
                 'http://code.google.com/p/my-project/')
 

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/123cb154/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 70ea3ad..15b8f33 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -24,13 +24,14 @@ from ...google import tracker
 
 
 class TestTrackerImporter(TestCase):
+    @mock.patch.object(tracker, 'g')
     @mock.patch.object(tracker, 'c')
     @mock.patch.object(tracker, 'ThreadLocalORMSession')
     @mock.patch.object(tracker, 'session')
     @mock.patch.object(tracker, 'M')
     @mock.patch.object(tracker, 'TM')
     @mock.patch.object(tracker, 'GoogleCodeProjectExtractor')
-    def test_import_tool(self, gpe, TM, M, session, tlos, c):
+    def test_import_tool(self, gpe, TM, M, session, tlos, c, g):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_fields = mock.Mock()
         importer.process_labels = mock.Mock()
@@ -70,6 +71,7 @@ class TestTrackerImporter(TestCase):
                 mock.call(tickets[0]),
                 mock.call(tickets[1]),
             ])
+        g.post_event.assert_called_once_with('project_updated')
 
     def test_custom_fields(self):
         importer = tracker.GoogleCodeTrackerImporter()


[06/16] git commit: [#6464] Changed tool_label on GC code importer

Posted by jo...@apache.org.
[#6464] Changed tool_label on GC code importer

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/547cad28
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/547cad28
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/547cad28

Branch: refs/heads/cj/6464
Commit: 547cad28efff4ee0d63c00c63e76facd93132d2d
Parents: ef8ecc8
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Wed Aug 7 01:14:26 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:20:59 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/code.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/547cad28/ForgeImporters/forgeimporters/google/code.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/code.py b/ForgeImporters/forgeimporters/google/code.py
index 2f8d19f..11dd7a6 100644
--- a/ForgeImporters/forgeimporters/google/code.py
+++ b/ForgeImporters/forgeimporters/google/code.py
@@ -105,7 +105,7 @@ class GoogleRepoImporter(ToolImporter):
         """ Import a Google Code repo into a new SVN, Git, or Hg Allura tool.
 
         """
-        extractor = GoogleCodeProjectExtractor(project, project_name, 'source_browse')
+        extractor = GoogleCodeProjectExtractor(project_name, 'source_browse')
         repo_type = extractor.get_repo_type()
         repo_url = get_repo_url(project_name, repo_type)
         app = project.install_app(


[08/16] git commit: [#6464] Use plain2markdown to escape ticket description and comments in GC Issues importer

Posted by jo...@apache.org.
[#6464] Use plain2markdown to escape ticket description and comments in GC Issues importer

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/9e4d5073
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/9e4d5073
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/9e4d5073

Branch: refs/heads/cj/6464
Commit: 9e4d50737ec8be750bda481d6a10b5bf415b62ab
Parents: 67852fe
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu Aug 8 20:44:54 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:20:59 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/tracker.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/9e4d5073/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 297f65a..a997429 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -80,7 +80,7 @@ class GoogleCodeTrackerImporter(ToolImporter):
                 '{body}').format(
                     creator=issue.get_issue_creator(),
                     owner=issue.get_issue_owner(),
-                    body=issue.get_issue_description(),
+                    body=h.plain2markdown(issue.get_issue_description(), True),
                 )
         ticket.add_multiple_attachments(issue.get_issue_attachments())
 
@@ -108,7 +108,7 @@ class GoogleCodeTrackerImporter(ToolImporter):
                         '\n'
                         '{updates}').format(
                             author=comment.author,
-                            body=comment.body,
+                            body=h.plain2markdown(comment.body, True),
                             updates='\n'.join(
                                 '**%s** %s' % (k,v)
                                 for k,v in comment.updates.items()


[05/16] git commit: [#6464] Google Code Tracker Importer via web scraping

Posted by jo...@apache.org.
[#6464] Google Code Tracker Importer via web scraping

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/ef8ecc85
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/ef8ecc85
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/ef8ecc85

Branch: refs/heads/cj/6464
Commit: ef8ecc85986996049d91603989747d3f6f14c0c7
Parents: 1c62317
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Aug 6 23:47:14 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:20:59 2013 +0000

----------------------------------------------------------------------
 Allura/allura/controllers/site_admin.py         |  15 +-
 .../allura/templates/site_admin_task_view.html  |   8 +
 .../forgeimporters/google/__init__.py           | 176 ++++++++++++++---
 ForgeImporters/forgeimporters/google/tasks.py   |   8 +-
 ForgeImporters/forgeimporters/google/tracker.py | 191 ++++---------------
 .../tests/google/test_extractor.py              |  19 +-
 .../forgeimporters/tests/google/test_tasks.py   |   8 +-
 .../forgeimporters/tests/google/test_tracker.py |  98 +++++-----
 8 files changed, 277 insertions(+), 246 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ef8ecc85/Allura/allura/controllers/site_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index 4f8f835..50b0e38 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -30,6 +30,7 @@ import tg
 from pylons import tmpl_context as c, app_globals as g
 from pylons import request
 from formencode import validators, Invalid
+from webob.exc import HTTPNotFound
 
 from allura.lib import helpers as h
 from allura.lib import validators as v
@@ -321,7 +322,19 @@ class TaskManagerController(object):
             config_dict['user'] = user
         with h.push_config(c, **config_dict):
             task = task.post(*args, **kw)
-        redirect('view/%s' % task._id)
+        redirect('../view/%s' % task._id)
+
+    @expose()
+    @require_post()
+    def resubmit(self, task_id):
+        try:
+            task = M.monq_model.MonQTask.query.get(_id=bson.ObjectId(task_id))
+        except bson.errors.InvalidId as e:
+            task = None
+        if task is None:
+            raise HTTPNotFound()
+        task.state = 'ready'
+        redirect('../view/%s' % task._id)
 
     @expose('json:')
     def task_doc(self, task_name):

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ef8ecc85/Allura/allura/templates/site_admin_task_view.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/site_admin_task_view.html b/Allura/allura/templates/site_admin_task_view.html
index c107382..e363b8d 100644
--- a/Allura/allura/templates/site_admin_task_view.html
+++ b/Allura/allura/templates/site_admin_task_view.html
@@ -66,6 +66,9 @@
     #task_details td.second-column {
         border: 0;
     }
+    #resubmit-task-form {
+        float: right;
+    }
 </style>
 {% endblock %}
 
@@ -73,6 +76,11 @@
 {% if not task %}
     Task not found
 {% else %}
+    {% if task.state in ['error', 'complete'] %}
+    <form id="resubmit-task-form" action="../resubmit/{{task._id}}" method="POST">
+        <input type="submit" value="Re-Submit Task" />
+    </form>
+    {% endif %}
     <h2>Task Details</h2>
     <table id="task_details">
         <tr>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ef8ecc85/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index a12389b..eaa0765 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -19,6 +19,7 @@ import re
 import urllib
 from urlparse import urlparse, urljoin
 from collections import defaultdict
+from contextlib import closing
 try:
     from cStringIO import StringIO
 except ImportError:
@@ -33,13 +34,32 @@ from forgeimporters.base import ProjectExtractor
 
 log = logging.getLogger(__name__)
 
+def _as_text(node, chunks=None):
+    """
+    Similar to node.text, but preserves whitespace around tags,
+    and converts <br/>s to \n.
+    """
+    if chunks is None:
+        chunks = []
+    for n in node:
+        if isinstance(n, basestring):
+            chunks.append(n)
+        elif n.name == 'br':
+            chunks.append('\n')
+        else:
+            _as_text(n, chunks)
+    return ''.join(chunks)
+
+
 class GoogleCodeProjectExtractor(ProjectExtractor):
     BASE_URL = 'http://code.google.com'
     RE_REPO_TYPE = re.compile(r'(svn|hg|git)')
 
     PAGE_MAP = {
-            'project_info': BASE_URL + '/p/%s/',
-            'source_browse': BASE_URL + '/p/%s/source/browse/',
+            'project_info': BASE_URL + '/p/{project_name}/',
+            'source_browse': BASE_URL + '/p/{project_name}/source/browse/',
+            'issues_csv': BASE_URL + '/p/{project_name}/issues/csv?can=1&colspec=ID&start={start}',
+            'issue': BASE_URL + '/p/{project_name}/issues/detail?id={issue_id}',
         }
 
     LICENSE_MAP = defaultdict(lambda:'Other/Proprietary License', {
@@ -57,16 +77,16 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
 
     DEFAULT_ICON = 'http://www.gstatic.com/codesite/ph/images/defaultlogo.png'
 
-    def __init__(self, allura_project, gc_project_name, page=None):
-        self.project = allura_project
-        self.gc_project_name = gc_project_name
+    def __init__(self, project_name, page_name=None, **kw):
+        self.url = self.PAGE_MAP[page_name].format(
+        self.project_name = project_name
         self._page_cache = {}
         self.url = None
         self.page = None
-        if page:
-            self.get_page(page)
+        if page_name:
+            self.get_page(page_name, **kw)
 
-    def get_page(self, page_name_or_url):
+    def get_page(self, page_name_or_url, **kw):
         """Return a Beautiful soup object for the given page name or url.
 
         If a page name is provided, the associated url is looked up in
@@ -77,27 +97,33 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
         request.
 
         """
-        if page_name_or_url in self._page_cache:
-            return self._page_cache[page_name_or_url]
-        self.url = (self.get_page_url(page_name_or_url) if page_name_or_url in
-                self.PAGE_MAP else page_name_or_url)
-        self.page = self._page_cache[page_name_or_url] = \
-                BeautifulSoup(self.urlopen(self.url))
+        if page_name_or_url in self.PAGE_MAP:
+            self.url = self.get_page_url(page_name_or_url, **kw)
+        else:
+            self.url = page_name_or_url
+        if self.url in self._page_cache:
+            self.page = self._page_cache[self.url]
+        else:
+            self.page = self._page_cache[page_name_or_url] = \
+                    BeautifulSoup(self.urlopen(self.url))
         return self.page
 
-    def get_page_url(self, page_name):
+    def get_page_url(self, page_name, **kw):
         """Return the url associated with ``page_name``.
 
         Raises KeyError if ``page_name`` is not in :attr:`PAGE_MAP`.
 
         """
-        return self.PAGE_MAP[page_name] % urllib.quote(self.gc_project_name)
+        return self.PAGE_MAP[page_name].format(
+            project_name = urllib.quote(self.project_name),
+            **kw,
+        )
 
-    def get_short_description(self):
+    def get_short_description(self, project):
         page = self.get_page('project_info')
-        self.project.short_description = page.find(itemprop='description').string.strip()
+        project.short_description = page.find(itemprop='description').string.strip()
 
-    def get_icon(self):
+    def get_icon(self, project):
         page = self.get_page('project_info')
         icon_url = urljoin(self.url, page.find(itemprop='image').attrMap['src'])
         if icon_url == self.DEFAULT_ICON:
@@ -109,13 +135,13 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
             icon_name, fp,
             fp_ish.info()['content-type'].split(';')[0],  # strip off charset=x extra param,
             square=True, thumbnail_size=(48,48),
-            thumbnail_meta={'project_id': self.project._id, 'category': 'icon'})
+            thumbnail_meta={'project_id': project._id, 'category': 'icon'})
 
-    def get_license(self):
+    def get_license(self, project):
         page = self.get_page('project_info')
         license = page.find(text='Code license').findNext().find('a').string.strip()
         trove = M.TroveCategory.query.get(fullname=self.LICENSE_MAP[license])
-        self.project.trove_license.append(trove._id)
+        project.trove_license.append(trove._id)
 
     def get_repo_type(self):
         page = self.get_page('source_browse')
@@ -128,3 +154,109 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
             return re_match.group(0)
         else:
             raise Exception("Unknown repo type: {0}".format(repo_type.text))
+
+    @classmethod
+    def _get_issue_ids_page(cls, project_name, start):
+        url = cls.PAGE_MAP['issues_csv'].format(project_name=project_name, start=start)
+        with closing(urllib2.urlopen(url)) as fp:
+            lines = fp.readlines()[1:]  # skip CSV header
+            if not lines[-1].startswith('"'):
+                lines.pop()  # skip "next page here" info footer
+        issue_ids = [line.strip('",\n') for line in lines]
+        return issue_ids
+
+    @classmethod
+    def iter_issues(cls, project_name):
+        """
+        Iterate over all issues for a project,
+        using paging to keep the responses reasonable.
+        """
+        start = 0
+        limit = 100
+
+        while True:
+            issue_ids = cls._get_issue_ids_page(project_name, start)
+            if len(issue_ids) <= 0:
+                return
+            for issue_id in issue_ids:
+                yield cls(project_name, 'issue', issue_id=issue_id)
+            start += limit
+
+    def get_issue_summary(self):
+        return self.page.find(id='issueheader').findAll('td', limit=2)[1].span.string.strip()
+
+    def get_issue_description(self):
+        return _as_text(self.page.find(id='hc0').pre)
+
+    def get_issue_created_date(self):
+        return self.page.find(id='hc0').find('span', 'date').get('title')
+
+    def get_issue_mod_date(self):
+        last_update = Comment(self.page.findAll('div', 'issuecomment')[-1])
+        return last_update.created_date
+
+    def get_issue_creator(self):
+        a = self.page.find(id='hc0').find('a', 'userlink')
+        return UserLink(a)
+
+    def get_issue_status(self):
+        return self.page.find(id='issuemeta').find('th', text=re.compile('Status:')).findNext().span.string.strip()
+
+    def get_issue_owner(self):
+        return UserLink(self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().a)
+
+    def get_issue_labels(self):
+        label_nodes = self.page.find(id='issuemeta').findAll('a', 'label')
+        return [_as_text(l) for l in label_nodes]
+
+    def get_issue_attachments(self):
+        attachments = self.page.find(id='hc0').find('div', 'attachments')
+        if attachments:
+            return map(Attachment, attachments.findAll('tr'))
+        else:
+            return []
+
+    def iter_comments(self):
+        for comment in self.page.findAll('div', 'issuecomment'):
+            yield Comment(comment)
+
+class UserLink(object):
+    def __init__(self, tag):
+        self.name = tag.string.strip()
+        self.link = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.get('href'))
+
+class Comment(object):
+    def __init__(self, tag):
+        self.author = UserLink(tag.find('span', 'author').find('a', 'userlink'))
+        self.created_date = tag.find('span', 'date').get('title')
+        self.body = _as_text(tag.find('pre'))
+        self._get_updates(tag)
+        self._get_attachments(tag)
+
+    def _get_updates(self, tag):
+        _updates = tag.find('div', 'updates')
+        if _updates:
+            _strings = _updates.findAll(text=True)
+            updates = (s.strip() for s in _strings if s.strip())
+            self.updates = {field: updates.next() for field in updates}
+        else:
+            self.updates = {}
+
+    def _get_attachments(self, tag):
+        attachments = tag.find('div', 'attachments')
+        if attachments:
+            self.attachments = map(Attachment, attachments.findAll('tr'))
+        else:
+            self.attachments = []
+
+class Attachment(object):
+    def __init__(self, tag):
+        self.filename = _as_text(tag).strip().split()[0]
+        self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.a.get('href'))
+        self.type = None
+
+    @property
+    def file(self):
+        fp_ish = urllib2.urlopen(self.url)
+        fp = StringIO(fp_ish.read())
+        return fp

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ef8ecc85/ForgeImporters/forgeimporters/google/tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tasks.py b/ForgeImporters/forgeimporters/google/tasks.py
index 968d9a9..69e7556 100644
--- a/ForgeImporters/forgeimporters/google/tasks.py
+++ b/ForgeImporters/forgeimporters/google/tasks.py
@@ -27,9 +27,9 @@ from . import GoogleCodeProjectExtractor
 
 @task
 def import_project_info(project_name):
-    extractor = GoogleCodeProjectExtractor(c.project, project_name, 'project_info')
-    extractor.get_short_description()
-    extractor.get_icon()
-    extractor.get_license()
+    extractor = GoogleCodeProjectExtractor(project_name, 'project_info')
+    extractor.get_short_description(c.project)
+    extractor.get_icon(c.project)
+    extractor.get_license(c.project)
     ThreadLocalORMSession.flush_all()
     g.post_event('project_updated')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ef8ecc85/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 95f53e4..297f65a 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -19,15 +19,14 @@ from collections import defaultdict
 from datetime import datetime
 
 from pylons import tmpl_context as c
-#import gdata
-gdata = None
-from ming.orm import session
+from ming.orm import session, ThreadLocalORMSession
 
 from allura.lib import helpers as h
 
 from forgetracker.tracker_main import ForgeTrackerApp
 from forgetracker import model as TM
 from ..base import ToolImporter
+from . import GoogleCodeProjectExtractor
 
 
 class GoogleCodeTrackerImporter(ToolImporter):
@@ -42,23 +41,22 @@ class GoogleCodeTrackerImporter(ToolImporter):
             type='select',
         )
 
-    def import_tool(self, project, user, project_name=None, mount_point=None,
+    def import_tool(self, project, user, project_name, mount_point=None,
             mount_label=None, **kw):
-        c.app = project.install_app('tracker', mount_point, mount_label)
-        c.app.globals.open_status_names = ['New', 'Accepted', 'Started']
-        c.app.globals.closed_status_names = ['Fixed', 'Verified', 'Invalid', 'Duplicate', 'WontFix', 'Done']
+        c.app = project.install_app('tickets', mount_point, mount_label)
+        ThreadLocalORMSession.flush_all()
+        c.app.globals.open_status_names = 'New Accepted Started'
+        c.app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         self.custom_fields = {}
-        extractor = GDataAPIExtractor(project_name)
-        for issue in extractor.iter_issues():
+        for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
             ticket = TM.Ticket.new()
             self.process_fields(ticket, issue)
             self.process_labels(ticket, issue)
-            self.process_comments(ticket, extractor.iter_comments(issue))
+            self.process_comments(ticket, issue)
             session(ticket).flush(ticket)
             session(ticket).expunge(ticket)
         self.postprocess_custom_fields()
-        session(c.app).flush(c.app)
-        session(c.app.globals).flush(c.app.globals)
+        ThreadLocalORMSession.flush_all()
 
     def custom_field(self, name):
         if name not in self.custom_fields:
@@ -71,16 +69,25 @@ class GoogleCodeTrackerImporter(ToolImporter):
         return self.custom_fields[name]
 
     def process_fields(self, ticket, issue):
-        ticket.summary = issue.summary
-        ticket.description = issue.description
-        ticket.status = issue.status
-        ticket.created_date = datetime.strptime(issue.created_date, '')
-        ticket.mod_date = datetime.strptime(issue.mod_date, '')
+        ticket.summary = issue.get_issue_summary()
+        ticket.status = issue.get_issue_status()
+        ticket.created_date = datetime.strptime(issue.get_issue_created_date(), '%c')
+        ticket.mod_date = datetime.strptime(issue.get_issue_mod_date(), '%c')
+        ticket.description = (
+                u'*Originally created by:* [{creator.name}]({creator.link})\n'
+                '*Originally owned by:* [{owner.name}]({owner.link})\n'
+                '\n'
+                '{body}').format(
+                    creator=issue.get_issue_creator(),
+                    owner=issue.get_issue_owner(),
+                    body=issue.get_issue_description(),
+                )
+        ticket.add_multiple_attachments(issue.get_issue_attachments())
 
     def process_labels(self, ticket, issue):
         labels = set()
         custom_fields = defaultdict(set)
-        for label in issue.labels:
+        for label in issue.get_issue_labels():
             if u'-' in label:
                 name, value = label.split(u'-', 1)
                 cf = self.custom_field(name)
@@ -91,23 +98,24 @@ class GoogleCodeTrackerImporter(ToolImporter):
         ticket.labels = list(labels)
         ticket.custom_fields = {n: u', '.join(sorted(v)) for n,v in custom_fields.iteritems()}
 
-    def process_comments(self, ticket, comments):
-        for comment in comments:
-            p = ticket.thread.add_post(
+    def process_comments(self, ticket, issue):
+        for comment in issue.iter_comments():
+            p = ticket.discussion_thread.add_post(
                     text = (
-                        u'Originally posted by: [{author.name}]({author.link})\n'
+                        u'*Originally posted by:* [{author.name}]({author.link})\n'
                         '\n'
                         '{body}\n'
                         '\n'
                         '{updates}').format(
                             author=comment.author,
-                            body=comment.text,
+                            body=comment.body,
                             updates='\n'.join(
-                                '*%s*: %s' % (k,v)
+                                '**%s** %s' % (k,v)
                                 for k,v in comment.updates.items()
                             ),
                     )
                 )
+            p.created_date = p.timestamp = datetime.strptime(comment.created_date, '%c')
             p.add_multiple_attachments(comment.attachments)
 
     def postprocess_custom_fields(self):
@@ -125,138 +133,3 @@ class GoogleCodeTrackerImporter(ToolImporter):
             else:
                 field['options'] = ''
             c.app.globals.custom_fields.append(field)
-
-
-class GDataAPIExtractor(object):
-    def __init__(self, project_name):
-        self.project_name = project_name
-
-    def iter_issues(self, limit=50):
-        """
-        Iterate over all issues for a project,
-        using paging to keep the responses reasonable.
-        """
-        start = 1
-
-        client = gdata.projecthosting.client.ProjectHostingClient()
-        while True:
-            query = gdata.projecthosting.client.Query(start_index=start, max_results=limit)
-            issues = client.get_issues(self.project_name, query=query).entry
-            if len(issues) <= 0:
-                return
-            for issue in issues:
-                yield GDataAPIIssue(issue)
-            start += limit
-
-    def iter_comments(self, issue, limit=50):
-        """
-        Iterate over all comments for a given issue,
-        using paging to keep the responses reasonable.
-        """
-        start = 1
-
-        client = gdata.projecthosting.client.ProjectHostingClient()
-        while True:
-            query = gdata.projecthosting.client.Query(start_index=start, max_results=limit)
-            comments = client.get_comments(self.project_name, query=query).entry
-            if len(comments) <= 0:
-                return
-            for comment in comments:
-                yield GDataAPIComment(comment)
-            start += limit
-
-
-class GDataAPIUser(object):
-    def __init__(self, user):
-        self.user = user
-
-    @property
-    def name(self):
-        return h.really_unicode(self.user.name.text)
-
-    @property
-    def link(self):
-        return u'http://code.google.com/u/%s' % self.name
-
-
-class GDataAPIIssue(object):
-    def __init__(self, issue):
-        self.issue = issue
-
-    @property
-    def summary(self):
-        return h.really_unicode(self.issue.title.text)
-
-    @property
-    def description(self):
-        return h.really_unicode(self.issue.content.text)
-
-    @property
-    def created_date(self):
-        return self.to_date(self.issue.published.text)
-
-    @property
-    def mod_date(self):
-        return self.to_date(self.issue.updated.text)
-
-    @property
-    def creator(self):
-        return h.really_unicode(self.issue.author[0].name.text)
-
-    @property
-    def status(self):
-        if getattr(self.issue, 'status', None) is not None:
-            return h.really_unicode(self.issue.status.text)
-        return u''
-
-    @property
-    def owner(self):
-        if getattr(self.issue, 'owner', None) is not None:
-            return h.really_unicode(self.issue.owner.username.text)
-        return u''
-
-    @property
-    def labels(self):
-        return [h.really_unicode(l.text) for l in self.issue.labels]
-
-
-class GDataAPIComment(object):
-    def __init__(self, comment):
-        self.comment = comment
-
-    @property
-    def author(self):
-        return GDataAPIUser(self.comment.author[0])
-
-    @property
-    def created_date(self):
-        return h.really_unicode(self.comment.published.text)
-
-    @property
-    def body(self):
-        return h.really_unicode(self.comment.content.text)
-
-    @property
-    def updates(self):
-        return {}
-
-    @property
-    def attachments(self):
-        return []
-
-
-class GDataAPIAttachment(object):
-    def __init__(self, attachment):
-        self.attachment = attachment
-
-    @property
-    def filename(self):
-        pass
-
-    @property
-    def type(self):
-        pass
-
-    @property
-    def file(self):
-        pass

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ef8ecc85/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 9b6db45..89aac5a 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -37,10 +37,9 @@ class TestGoogleCodeProjectExtractor(TestCase):
         self._p_soup.stop()
 
     def test_init(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
 
         self.urlopen.assert_called_once_with('http://code.google.com/p/my-project/')
-        self.assertEqual(extractor.project, self.project)
         self.soup.assert_called_once_with(self.urlopen.return_value)
         self.assertEqual(extractor.page, self.soup.return_value)
 
@@ -57,10 +56,10 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 'http://code.google.com/p/my-project/')
 
     def test_get_short_description(self):
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
         extractor.page.find.return_value.string = 'My Super Project'
 
-        extractor.get_short_description()
+        extractor.get_short_description(self.project)
 
         extractor.page.find.assert_called_once_with(itemprop='description')
         self.assertEqual(self.project.short_description, 'My Super Project')
@@ -69,11 +68,11 @@ class TestGoogleCodeProjectExtractor(TestCase):
     @mock.patch.object(google, 'M')
     def test_get_icon(self, M, StringIO):
         self.urlopen.return_value.info.return_value = {'content-type': 'image/png'}
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
         extractor.page.find.return_value.attrMap = {'src': 'http://example.com/foo/bar/my-logo.png'}
         self.urlopen.reset_mock()
 
-        extractor.get_icon()
+        extractor.get_icon(self.project)
 
         extractor.page.find.assert_called_once_with(itemprop='image')
         self.urlopen.assert_called_once_with('http://example.com/foo/bar/my-logo.png')
@@ -87,11 +86,11 @@ class TestGoogleCodeProjectExtractor(TestCase):
     @mock.patch.object(google, 'M')
     def test_get_license(self, M):
         self.project.trove_license = []
-        extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project', 'project_info')
+        extractor = google.GoogleCodeProjectExtractor('my-project', 'project_info')
         extractor.page.find.return_value.findNext.return_value.find.return_value.string = '  New BSD License  '
         trove = M.TroveCategory.query.get.return_value
 
-        extractor.get_license()
+        extractor.get_license(self.project)
 
         extractor.page.find.assert_called_once_with(text='Code license')
         extractor.page.find.return_value.findNext.assert_called_once_with()
@@ -101,13 +100,13 @@ class TestGoogleCodeProjectExtractor(TestCase):
 
         M.TroveCategory.query.get.reset_mock()
         extractor.page.find.return_value.findNext.return_value.find.return_value.string = 'non-existant license'
-        extractor.get_license()
+        extractor.get_license(self.project)
         M.TroveCategory.query.get.assert_called_once_with(fullname='Other/Proprietary License')
 
     def _make_extractor(self, html):
         from BeautifulSoup import BeautifulSoup
         with mock.patch.object(base.ProjectExtractor, 'urlopen'):
-            extractor = google.GoogleCodeProjectExtractor(self.project, 'my-project')
+            extractor = google.GoogleCodeProjectExtractor('my-project')
         extractor.page = BeautifulSoup(html)
         extractor.get_page = lambda pagename: extractor.page
         extractor.url="http://test/source/browse"

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ef8ecc85/ForgeImporters/forgeimporters/tests/google/test_tasks.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tasks.py b/ForgeImporters/forgeimporters/tests/google/test_tasks.py
index dc7d936..01bab68 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tasks.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tasks.py
@@ -26,8 +26,8 @@ from ...google import tasks
 def test_import_project_info(c, session, gpe):
     c.project = mock.Mock(name='project')
     tasks.import_project_info('my-project')
-    gpe.assert_called_once_with(c.project, 'my-project', 'project_info')
-    gpe.return_value.get_short_description.assert_called_once_with()
-    gpe.return_value.get_icon.assert_called_once_with()
-    gpe.return_value.get_license.assert_called_once_with()
+    gpe.assert_called_once_with('my-project', 'project_info')
+    gpe.return_value.get_short_description.assert_called_once_with(c.project)
+    gpe.return_value.get_icon.assert_called_once_with(c.project)
+    gpe.return_value.get_license.assert_called_once_with(c.project)
     session.flush_all.assert_called_once_with()

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/ef8ecc85/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index e49f279..62493bd 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from datetime import datetime
 from operator import itemgetter
 from unittest import TestCase
 import mock
@@ -24,10 +25,11 @@ from ...google import tracker
 
 class TestTrackerImporter(TestCase):
     @mock.patch.object(tracker, 'c')
+    @mock.patch.object(tracker, 'ThreadLocalORMSession')
     @mock.patch.object(tracker, 'session')
     @mock.patch.object(tracker, 'TM')
-    @mock.patch.object(tracker, 'GDataAPIExtractor')
-    def test_import_tool(self, gdata, TM, session, c):
+    @mock.patch.object(tracker, 'GoogleCodeProjectExtractor')
+    def test_import_tool(self, gpe, TM, session, tlos, c):
         importer = tracker.GoogleCodeTrackerImporter()
         importer.process_fields = mock.Mock()
         importer.process_labels = mock.Mock()
@@ -35,16 +37,14 @@ class TestTrackerImporter(TestCase):
         importer.postprocess_custom_fields = mock.Mock()
         project, user = mock.Mock(), mock.Mock()
         app = project.install_app.return_value
-        extractor = gdata.return_value
-        issues = extractor.iter_issues.return_value = [mock.Mock(), mock.Mock()]
+        issues = gpe.iter_issues.return_value = [mock.Mock(), mock.Mock()]
         tickets = TM.Ticket.new.side_effect = [mock.Mock(), mock.Mock()]
-        comments = extractor.iter_comments.side_effect = [mock.Mock(), mock.Mock()]
 
         importer.import_tool(project, user, project_name='project_name',
                 mount_point='mount_point', mount_label='mount_label')
 
-        project.install_app.assert_called_once_with('tracker', 'mount_point', 'mount_label')
-        gdata.assert_called_once_with('project_name')
+        project.install_app.assert_called_once_with('tickets', 'mount_point', 'mount_label')
+        gpe.iter_issues.assert_called_once_with('project_name')
         self.assertEqual(importer.process_fields.call_args_list, [
                 mock.call(tickets[0], issues[0]),
                 mock.call(tickets[1], issues[1]),
@@ -54,26 +54,16 @@ class TestTrackerImporter(TestCase):
                 mock.call(tickets[1], issues[1]),
             ])
         self.assertEqual(importer.process_comments.call_args_list, [
-                mock.call(tickets[0], comments[0]),
-                mock.call(tickets[1], comments[1]),
+                mock.call(tickets[0], issues[0]),
+                mock.call(tickets[1], issues[1]),
             ])
-        self.assertEqual(extractor.iter_comments.call_args_list, [
-                mock.call(issues[0]),
-                mock.call(issues[1]),
-            ])
-        self.assertEqual(session.call_args_list, [
-                mock.call(tickets[0]),
-                mock.call(tickets[0]),
-                mock.call(tickets[1]),
-                mock.call(tickets[1]),
-                mock.call(app),
-                mock.call(app.globals),
+        self.assertEqual(tlos.flush_all.call_args_list, [
+                mock.call(),
+                mock.call(),
             ])
         self.assertEqual(session.return_value.flush.call_args_list, [
                 mock.call(tickets[0]),
                 mock.call(tickets[1]),
-                mock.call(app),
-                mock.call(app.globals),
             ])
         self.assertEqual(session.return_value.expunge.call_args_list, [
                 mock.call(tickets[0]),
@@ -119,30 +109,37 @@ class TestTrackerImporter(TestCase):
 
     def test_process_fields(self):
         ticket = mock.Mock()
+        def _user(l):
+            u = mock.Mock()
+            u.name = '%sname' % l
+            u.link = '%slink' % l
+            return u
         issue = mock.Mock(
-                summary='summary',
-                description='description',
-                status='status',
-                created_date='created_date',
-                mod_date='mod_date',
+                get_issue_summary=lambda:'summary',
+                get_issue_description=lambda:'description',
+                get_issue_status=lambda:'status',
+                get_issue_created_date=lambda:'created_date',
+                get_issue_mod_date=lambda:'mod_date',
+                get_issue_creator=lambda:_user('c'),
+                get_issue_owner=lambda:_user('o'),
             )
         importer = tracker.GoogleCodeTrackerImporter()
         with mock.patch.object(tracker, 'datetime') as dt:
             dt.strptime.side_effect = lambda s,f: s
             importer.process_fields(ticket, issue)
             self.assertEqual(ticket.summary, 'summary')
-            self.assertEqual(ticket.description, 'description')
+            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\ndescription')
             self.assertEqual(ticket.status, 'status')
             self.assertEqual(ticket.created_date, 'created_date')
             self.assertEqual(ticket.mod_date, 'mod_date')
             self.assertEqual(dt.strptime.call_args_list, [
-                    mock.call('created_date', ''),
-                    mock.call('mod_date', ''),
+                    mock.call('created_date', '%c'),
+                    mock.call('mod_date', '%c'),
                 ])
 
     def test_process_labels(self):
         ticket = mock.Mock(custom_fields={}, labels=[])
-        issue = mock.Mock(labels=['Foo-Bar', 'Baz', 'Foo-Qux'])
+        issue = mock.Mock(get_issue_labels=lambda:['Foo-Bar', 'Baz', 'Foo-Qux'])
         importer = tracker.GoogleCodeTrackerImporter()
         importer.custom_field = mock.Mock(side_effect=lambda n: {'name': '_%s' % n.lower(), 'options': set()})
         importer.process_labels(ticket, issue)
@@ -156,40 +153,49 @@ class TestTrackerImporter(TestCase):
             a.link = 'author%s_link' % n
             return a
         ticket = mock.Mock()
-        comments = [
+        issue = mock.Mock()
+        comments = issue.iter_comments.return_value = [
                 mock.Mock(
                     author=_author(1),
-                    text='text1',
+                    body='text1',
                     attachments='attachments1',
+                    created_date='Mon Jul 15 00:00:00 2013',
                 ),
                 mock.Mock(
                     author=_author(2),
-                    text='text2',
+                    body='text2',
                     attachments='attachments2',
+                    created_date='Mon Jul 16 00:00:00 2013',
                 ),
             ]
-        comments[0].updates.items.return_value = [('Foo', 'Bar'), ('Baz', 'Qux')]
+        comments[0].updates.items.return_value = [('Foo:', 'Bar'), ('Baz:', 'Qux')]
         comments[1].updates.items.return_value = []
+        posts = ticket.discussion_thread.add_post.side_effect = [
+                mock.Mock(),
+                mock.Mock(),
+            ]
         importer = tracker.GoogleCodeTrackerImporter()
-        importer.process_comments(ticket, comments)
-        self.assertEqual(ticket.thread.add_post.call_args_list[0], mock.call(
-                text='Originally posted by: [author1](author1_link)\n'
+        importer.process_comments(ticket, issue)
+        self.assertEqual(ticket.discussion_thread.add_post.call_args_list[0], mock.call(
+                text='*Originally posted by:* [author1](author1_link)\n'
                 '\n'
                 'text1\n'
                 '\n'
-                '*Foo*: Bar\n'
-                '*Baz*: Qux'
+                '**Foo:** Bar\n'
+                '**Baz:** Qux'
             ))
-        self.assertEqual(ticket.thread.add_post.call_args_list[1], mock.call(
-                text='Originally posted by: [author2](author2_link)\n'
+        self.assertEqual(posts[0].created_date, datetime(2013, 7, 15))
+        self.assertEqual(posts[0].timestamp, datetime(2013, 7, 15))
+        posts[0].add_multiple_attachments.assert_called_once_with('attachments1')
+        self.assertEqual(ticket.discussion_thread.add_post.call_args_list[1], mock.call(
+                text='*Originally posted by:* [author2](author2_link)\n'
                 '\n'
                 'text2\n'
                 '\n'
             ))
-        self.assertEqual(ticket.thread.add_post.return_value.add_multiple_attachments.call_args_list, [
-                mock.call('attachments1'),
-                mock.call('attachments2'),
-            ])
+        self.assertEqual(posts[1].created_date, datetime(2013, 7, 16))
+        self.assertEqual(posts[1].timestamp, datetime(2013, 7, 16))
+        posts[1].add_multiple_attachments.assert_called_once_with('attachments2')
 
     @mock.patch.object(tracker, 'c')
     def test_postprocess_custom_fields(self, c):


[07/16] git commit: [#6464] Added skip_mod_date to GC tracker importer

Posted by jo...@apache.org.
[#6464] Added skip_mod_date to GC tracker importer

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/92223433
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/92223433
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/92223433

Branch: refs/heads/cj/6464
Commit: 9222343379c48fac96a52bb886dee762f2c54870
Parents: 9e4d507
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Thu Aug 8 21:10:24 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:20:59 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/google/tracker.py | 23 ++++++++++++--------
 1 file changed, 14 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/92223433/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index a997429..bd3a57f 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -21,6 +21,7 @@ from datetime import datetime
 from pylons import tmpl_context as c
 from ming.orm import session, ThreadLocalORMSession
 
+from allura import model as M
 from allura.lib import helpers as h
 
 from forgetracker.tracker_main import ForgeTrackerApp
@@ -48,15 +49,19 @@ class GoogleCodeTrackerImporter(ToolImporter):
         c.app.globals.open_status_names = 'New Accepted Started'
         c.app.globals.closed_status_names = 'Fixed Verified Invalid Duplicate WontFix Done'
         self.custom_fields = {}
-        for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
-            ticket = TM.Ticket.new()
-            self.process_fields(ticket, issue)
-            self.process_labels(ticket, issue)
-            self.process_comments(ticket, issue)
-            session(ticket).flush(ticket)
-            session(ticket).expunge(ticket)
-        self.postprocess_custom_fields()
-        ThreadLocalORMSession.flush_all()
+        try:
+            M.session.artifact_orm_session._get().skip_mod_date = True
+            for issue in GoogleCodeProjectExtractor.iter_issues(project_name):
+                ticket = TM.Ticket.new()
+                self.process_fields(ticket, issue)
+                self.process_labels(ticket, issue)
+                self.process_comments(ticket, issue)
+                session(ticket).flush(ticket)
+                session(ticket).expunge(ticket)
+            self.postprocess_custom_fields()
+            ThreadLocalORMSession.flush_all()
+        finally:
+            M.session.artifact_orm_session._get().skip_mod_date = False
 
     def custom_field(self, name):
         if name not in self.custom_fields:


[12/16] git commit: [#6464] Fixed test failing due to refactor

Posted by jo...@apache.org.
[#6464] Fixed test failing due to refactor

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/3eab85b7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/3eab85b7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/3eab85b7

Branch: refs/heads/cj/6464
Commit: 3eab85b71cc64ebe1686f7bfdd462327399b2c39
Parents: 36cf1a2
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Tue Aug 13 14:07:38 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:21:00 2013 +0000

----------------------------------------------------------------------
 ForgeImporters/forgeimporters/tests/google/test_tracker.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/3eab85b7/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 3efd97d..70ea3ad 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -212,8 +212,8 @@ class TestTrackerImporter(TestCase):
                     'options': set(['foo', 'bar']),
                 },
             }
-        importer.postprocess_custom_fields()
-        self.assertEqual(sorted(c.app.globals.custom_fields, key=itemgetter('name')), [
+        custom_fields = importer.postprocess_custom_fields()
+        self.assertItemsEqual(custom_fields, [
                 {
                     'name': '_foo',
                     'type': 'string',


[16/16] git commit: [#6464] Handle non-linked Google users in tracker import

Posted by jo...@apache.org.
[#6464] Handle non-linked Google users in tracker import

Signed-off-by: Cory Johns <cj...@slashdotmedia.com>


Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/8facbc5e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/8facbc5e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/8facbc5e

Branch: refs/heads/cj/6464
Commit: 8facbc5e2ada555bca0a92b0b322fcb05c6fb23d
Parents: 3c9bc57
Author: Cory Johns <cj...@slashdotmedia.com>
Authored: Fri Aug 16 20:02:17 2013 +0000
Committer: Cory Johns <cj...@slashdotmedia.com>
Committed: Fri Aug 16 22:21:00 2013 +0000

----------------------------------------------------------------------
 .../forgeimporters/google/__init__.py           | 19 +++++++++----
 ForgeImporters/forgeimporters/google/tracker.py |  4 +--
 .../tests/data/google/empty-issue.html          |  2 +-
 .../tests/google/functional/test_tracker.py     |  2 +-
 .../tests/google/test_extractor.py              | 29 +++++++++++++++-----
 .../forgeimporters/tests/google/test_tracker.py | 11 ++------
 6 files changed, 43 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8facbc5e/ForgeImporters/forgeimporters/google/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/__init__.py b/ForgeImporters/forgeimporters/google/__init__.py
index 7234dc8..22be7c2 100644
--- a/ForgeImporters/forgeimporters/google/__init__.py
+++ b/ForgeImporters/forgeimporters/google/__init__.py
@@ -198,7 +198,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
             return self.get_issue_created_date()
 
     def get_issue_creator(self):
-        a = self.page.find(id='hc0').find('a', 'userlink')
+        a = self.page.find(id='hc0').find(True, 'userlink')
         return UserLink(a)
 
     def get_issue_status(self):
@@ -209,7 +209,7 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
             return ''
 
     def get_issue_owner(self):
-        tag = self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().a
+        tag = self.page.find(id='issuemeta').find('th', text=re.compile('Owner:')).findNext().find(True, 'userlink')
         if tag:
             return UserLink(tag)
         else:
@@ -233,11 +233,20 @@ class GoogleCodeProjectExtractor(ProjectExtractor):
 class UserLink(object):
     def __init__(self, tag):
         self.name = tag.string.strip()
-        self.link = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.get('href'))
+        if 'href' in tag.attrMap:
+            self.url = urljoin(GoogleCodeProjectExtractor.BASE_URL, tag.attrMap['href'])
+        else:
+            self.url = None
+
+    def __str__(self):
+        if self.url:
+            return '[{name}]({url})'.format(name = self.name, url = self.url)
+        else:
+            return self.name
 
 class Comment(object):
     def __init__(self, tag):
-        self.author = UserLink(tag.find('span', 'author').find('a', 'userlink'))
+        self.author = UserLink(tag.find('span', 'author').find(True, 'userlink'))
         self.created_date = tag.find('span', 'date').get('title')
         self.body = _as_text(tag.find('pre')).strip()
         self._get_updates(tag)
@@ -262,7 +271,7 @@ class Comment(object):
     @property
     def annotated_text(self):
         text = (
-                u'*Originally posted by:* [{author.name}]({author.link})\n'
+                u'*Originally posted by:* {author}\n'
                 u'\n'
                 u'{body}\n'
                 u'\n'

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8facbc5e/ForgeImporters/forgeimporters/google/tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/google/tracker.py b/ForgeImporters/forgeimporters/google/tracker.py
index 3257c5a..3d9843c 100644
--- a/ForgeImporters/forgeimporters/google/tracker.py
+++ b/ForgeImporters/forgeimporters/google/tracker.py
@@ -86,11 +86,11 @@ class GoogleCodeTrackerImporter(ToolImporter):
         ticket.mod_date = datetime.strptime(issue.get_issue_mod_date(), '%c')
         owner = issue.get_issue_owner()
         if owner:
-            owner_line = '*Originally owned by:* [{owner.name}]({owner.link})\n'.format(owner=owner)
+            owner_line = '*Originally owned by:* {owner}\n'.format(owner=owner)
         else:
             owner_line = ''
         ticket.description = (
-                u'*Originally created by:* [{creator.name}]({creator.link})\n'
+                u'*Originally created by:* {creator}\n'
                 u'{owner}'
                 u'\n'
                 u'{body}').format(

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8facbc5e/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html b/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
index a8fb1d5..b2eef20 100644
--- a/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
+++ b/ForgeImporters/forgeimporters/tests/data/google/empty-issue.html
@@ -240,7 +240,7 @@ contributes to open source, such as <a href="http://www.firefox.com">Firefox</a>
  Reported by
 
 
- <a class="userlink" href="/u/101557263855536553789/">john...@gmail.com</a>,
+ <span class="userlink">john...@gmail.com</span>,
  <span class="date" title="Thu Aug  8 14:56:23 2013">Today (15 minutes ago)</span>
 </div>
 <pre>

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8facbc5e/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
index 62c4453..80a8747 100644
--- a/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/functional/test_tracker.py
@@ -70,7 +70,7 @@ class TestGCTrackerImporter(TestCase):
     def test_empty_issue(self):
         ticket = self._make_ticket(self.empty_issue)
         self.assertEqual(ticket.summary, 'Empty Issue')
-        self.assertEqual(ticket.description, '*Originally created by:* [john...@gmail.com](http://code.google.com/u/101557263855536553789/)\n\nEmpty')
+        self.assertEqual(ticket.description, '*Originally created by:* john...@gmail.com\n\nEmpty')
         self.assertEqual(ticket.status, '')
         self.assertEqual(ticket.milestone, '')
         self.assertEqual(ticket.custom_fields, {})

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8facbc5e/ForgeImporters/forgeimporters/tests/google/test_extractor.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_extractor.py b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
index 7a485d6..4157c98 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_extractor.py
@@ -152,9 +152,9 @@ class TestGoogleCodeProjectExtractor(TestCase):
         test_issue = open(pkg_resources.resource_filename('forgeimporters', 'tests/data/google/test-issue.html')).read()
         gpe = self._make_extractor(test_issue)
         self.assertEqual(gpe.get_issue_creator().name, 'john...@gmail.com')
-        self.assertEqual(gpe.get_issue_creator().link, 'http://code.google.com/u/101557263855536553789/')
+        self.assertEqual(gpe.get_issue_creator().url, 'http://code.google.com/u/101557263855536553789/')
         self.assertEqual(gpe.get_issue_owner().name, 'john...@gmail.com')
-        self.assertEqual(gpe.get_issue_owner().link, 'http://code.google.com/u/101557263855536553789/')
+        self.assertEqual(gpe.get_issue_owner().url, 'http://code.google.com/u/101557263855536553789/')
         self.assertEqual(gpe.get_issue_status(), 'Started')
         self.assertEqual(gpe.get_issue_summary(), 'Test Issue')
         self.assertEqual(gpe.get_issue_description(),
@@ -217,7 +217,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
         expected = [
                 {
                     'author.name': 'john...@gmail.com',
-                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'author.url': 'http://code.google.com/u/101557263855536553789/',
                     'created_date': 'Thu Aug  8 15:35:15 2013',
                     'body': 'Test *comment* is a comment',
                     'updates': {'Status:': 'Started', 'Labels:': '-OpSys-Linux OpSys-Windows'},
@@ -225,7 +225,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 },
                 {
                     'author.name': 'john...@gmail.com',
-                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'author.url': 'http://code.google.com/u/101557263855536553789/',
                     'created_date': 'Thu Aug  8 15:35:34 2013',
                     'body': 'Another comment',
                     'updates': {},
@@ -233,7 +233,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 },
                 {
                     'author.name': 'john...@gmail.com',
-                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'author.url': 'http://code.google.com/u/101557263855536553789/',
                     'created_date': 'Thu Aug  8 15:36:39 2013',
                     'body': 'Last comment',
                     'updates': {},
@@ -241,7 +241,7 @@ class TestGoogleCodeProjectExtractor(TestCase):
                 },
                 {
                     'author.name': 'john...@gmail.com',
-                    'author.link': 'http://code.google.com/u/101557263855536553789/',
+                    'author.url': 'http://code.google.com/u/101557263855536553789/',
                     'created_date': 'Thu Aug  8 15:36:57 2013',
                     'body': 'Oh, I forgot one',
                     'updates': {'Labels:': 'OpSys-OSX'},
@@ -250,8 +250,23 @@ class TestGoogleCodeProjectExtractor(TestCase):
             ]
         for actual, expected in zip(comments, expected):
             self.assertEqual(actual.author.name, expected['author.name'])
-            self.assertEqual(actual.author.link, expected['author.link'])
+            self.assertEqual(actual.author.url, expected['author.url'])
             self.assertEqual(actual.created_date, expected['created_date'])
             self.assertEqual(actual.body, expected['body'])
             self.assertEqual(actual.updates, expected['updates'])
             self.assertEqual([a.filename for a in actual.attachments], expected['attachments'])
+
+class TestUserLink(TestCase):
+    def test_plain(self):
+        tag = mock.Mock()
+        tag.string.strip.return_value = 'name'
+        tag.attrMap = {}
+        link = google.UserLink(tag)
+        self.assertEqual(str(link), 'name')
+
+    def test_linked(self):
+        tag = mock.Mock()
+        tag.string.strip.return_value = 'name'
+        tag.attrMap = {'href': '/p/project'}
+        link = google.UserLink(tag)
+        self.assertEqual(str(link), '[name](http://code.google.com/p/project)')

http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/8facbc5e/ForgeImporters/forgeimporters/tests/google/test_tracker.py
----------------------------------------------------------------------
diff --git a/ForgeImporters/forgeimporters/tests/google/test_tracker.py b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
index 15b8f33..01d9f8e 100644
--- a/ForgeImporters/forgeimporters/tests/google/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/google/test_tracker.py
@@ -112,26 +112,21 @@ class TestTrackerImporter(TestCase):
 
     def test_process_fields(self):
         ticket = mock.Mock()
-        def _user(l):
-            u = mock.Mock()
-            u.name = '%sname' % l
-            u.link = '%slink' % l
-            return u
         issue = mock.Mock(
                 get_issue_summary=lambda:'summary',
                 get_issue_description=lambda:'my *description* fool',
                 get_issue_status=lambda:'status',
                 get_issue_created_date=lambda:'created_date',
                 get_issue_mod_date=lambda:'mod_date',
-                get_issue_creator=lambda:_user('c'),
-                get_issue_owner=lambda:_user('o'),
+                get_issue_creator=lambda:'creator',
+                get_issue_owner=lambda:'owner',
             )
         importer = tracker.GoogleCodeTrackerImporter()
         with mock.patch.object(tracker, 'datetime') as dt:
             dt.strptime.side_effect = lambda s,f: s
             importer.process_fields(ticket, issue)
             self.assertEqual(ticket.summary, 'summary')
-            self.assertEqual(ticket.description, '*Originally created by:* [cname](clink)\n*Originally owned by:* [oname](olink)\n\nmy \*description\* fool')
+            self.assertEqual(ticket.description, '*Originally created by:* creator\n*Originally owned by:* owner\n\nmy \*description\* fool')
             self.assertEqual(ticket.status, 'status')
             self.assertEqual(ticket.created_date, 'created_date')
             self.assertEqual(ticket.mod_date, 'mod_date')