From 884f1f6d92872a80ca86a8e75e07cc2754f73d74 Mon Sep 17 00:00:00 2001
From: anar <anaradilovab@gmail.com>
Date: Nov 06 2017 08:57:44 +0000
Subject: UserIssues widget initial commit


---

diff --git a/hubs/default_config.py b/hubs/default_config.py
index b0e805d..d2cd3a4 100644
--- a/hubs/default_config.py
+++ b/hubs/default_config.py
@@ -73,4 +73,5 @@ WIDGETS = [
     'hubs.widgets.sticky:Sticky',
     'hubs.widgets.subscriptions:Subscriptions',
     'hubs.widgets.workflow.updates2stable:Updates2Stable',
+    'hubs.widgets.user_issues:UserIssues',
     ]
diff --git a/hubs/static/css/style.css b/hubs/static/css/style.css
index 9257a3b..96a4b8d 100644
--- a/hubs/static/css/style.css
+++ b/hubs/static/css/style.css
@@ -318,12 +318,14 @@ font-size: 32pt;
  * Issue widgets
  */
 .widget .widget-pagure_pr li,
+.widget .widget-user_issues li,
 .widget .widget-githubissues li,
 .widget .widget-bugzilla li {
   border-bottom: 1px solid rgba(0, 0, 0, .125);
 }
 
 .widget .widget-pagure_pr li:last-child,
+.widget .widget-user_issues li:last-child,
 .widget .widget-githubissues li:last-child,
 .widget .widget-bugzilla li:last-child {
 border: 0}
diff --git a/hubs/tests/test_widget_validators.py b/hubs/tests/test_widget_validators.py
index 77a2a54..e66ab45 100644
--- a/hubs/tests/test_widget_validators.py
+++ b/hubs/tests/test_widget_validators.py
@@ -60,3 +60,14 @@ class ValidatorsTest(APPTest):
             validators.PagureRepo("fedora-hubs"), "fedora-hubs")
         self.assertRaises(ValueError, validators.PagureRepo,
                           "something-that-does-not-exist")
+
+    def test_pagure_user(self):
+        self.assertEqual(validators.PagureUser("abompard"), "abompard")
+        self.assertRaises(ValueError, validators.PagureUser,
+                          "someone-that-does-not-exist")
+
+    def test_bugzilla_user(self):
+        self.assertEqual(validators.BugzillaUser("aurelien@bompard.org"),
+                         "aurelien@bompard.org")
+        self.assertRaises(ValueError, validators.BugzillaUser,
+                          "someone-that-does-not-exist")
diff --git a/hubs/tests/vcr-request-data/hubs.tests.test_widget_validators.ValidatorsTest.test_bugzilla_user b/hubs/tests/vcr-request-data/hubs.tests.test_widget_validators.ValidatorsTest.test_bugzilla_user
new file mode 100644
index 0000000..ee7d3c4
--- /dev/null
+++ b/hubs/tests/vcr-request-data/hubs.tests.test_widget_validators.ValidatorsTest.test_bugzilla_user
@@ -0,0 +1,61 @@
+interactions:
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.18.4]
+    method: GET
+    uri: https://bugzilla.redhat.com/jsonrpc.cgi?method=User.get&params=[%7B%22names%22:[%22aurelien@bompard.org%22]%7D]
+  response:
+    body:
+      string: !!binary |
+        H4sIAAAAAAAAAy3NQQrDIBCF4auUWQeFCF24anuNUsqkDokwahh1E/HuFZLt+/h5DUgkCdhYmSfw
+        DixspezZar3U9fDMqITchkX9UtAwgVCuXMA2qJkkg323sSF/IwYa+bMKsad4e6Wwo7iRXIKXPJZT
+        VJIVzlNj7vNs+qf3Py9QgsCSAAAA
+    headers:
+      connection: [close]
+      content-encoding: [gzip]
+      content-length: ['135']
+      content-type: [application/json; charset=UTF-8]
+      date: ['Sun, 05 Nov 2017 11:21:36 GMT']
+      server: [Apache]
+      set-cookie: [Bugzilla_login_request_cookie=QrHmDU8BpR; domain=bugzilla.redhat.com;
+          path=/; HttpOnly]
+      vary: ['Accept-Encoding,User-Agent']
+      x-content-type-options: [nosniff]
+      x-frame-options: [SAMEORIGIN]
+      x-xss-protection: [1; mode=block]
+    status: {code: 200, message: OK}
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.18.4]
+    method: GET
+    uri: https://bugzilla.redhat.com/jsonrpc.cgi?method=User.get&params=[%7B%22names%22:[%22someone-that-does-not-exist%22]%7D]
+  response:
+    body:
+      string: !!binary |
+        H4sIAAAAAAAAAy2Ouw7CMAxFf8XywkJaMbB0REJiRvxAaEwbKYlR7EiUin/HPDbLOufeuyLVyhWH
+        FTOJ+IlwwMtMlSAKFIYmVKH4TAE2wpm4kNPZqwtM4gqro0cU3XRwjGoeLNwgR3G63M2x19cGrvDR
+        fnmz/2QrLKRQaTLfCgPcDPJwtutk5KFNz5iSBz+O3Ip2uMWRgw3c715bjMGWzqp3Gfr++mc7i7GW
+        buTcG15JWlIcSkvp9QaAQ1aH6wAAAA==
+    headers:
+      connection: [close]
+      content-encoding: [gzip]
+      content-length: ['193']
+      content-type: [application/json; charset=UTF-8]
+      date: ['Sun, 05 Nov 2017 11:21:40 GMT']
+      server: [Apache]
+      set-cookie: [Bugzilla_login_request_cookie=TFTzxQO4s0; domain=bugzilla.redhat.com;
+          path=/; HttpOnly]
+      vary: ['Accept-Encoding,User-Agent']
+      x-content-type-options: [nosniff]
+      x-frame-options: [SAMEORIGIN]
+      x-xss-protection: [1; mode=block]
+    status: {code: 200, message: OK}
+version: 1
diff --git a/hubs/tests/vcr-request-data/hubs.tests.test_widget_validators.ValidatorsTest.test_pagure_user b/hubs/tests/vcr-request-data/hubs.tests.test_widget_validators.ValidatorsTest.test_pagure_user
new file mode 100644
index 0000000..169ce75
--- /dev/null
+++ b/hubs/tests/vcr-request-data/hubs.tests.test_widget_validators.ValidatorsTest.test_pagure_user
@@ -0,0 +1,272 @@
+interactions:
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.18.4]
+    method: GET
+    uri: https://pagure.io/api/0/user/abompard
+  response:
+    body: {string: !!python/unicode "{\n  \"forks\": [\n    {\n      \"access_groups\"\
+        : {\n        \"admin\": [],\n        \"commit\": [],\n        \"ticket\":\
+        \ []\n      },\n      \"access_users\": {\n        \"admin\": [],\n      \
+        \  \"commit\": [],\n        \"owner\": [\n          \"abompard\"\n       \
+        \ ],\n        \"ticket\": []\n      },\n      \"close_status\": [\n      \
+        \  \"Invalid\",\n        \"Insufficient data\",\n        \"Fixed\",\n    \
+        \    \"Duplicate\"\n      ],\n      \"custom_keys\": [],\n      \"date_created\"\
+        : \"1476178044\",\n      \"date_modified\": \"1476178044\",\n      \"description\"\
+        : \"Fedora Hubs\",\n      \"fullname\": \"forks/abompard/fedora-hubs\",\n\
+        \      \"id\": 1232,\n      \"milestones\": {},\n      \"name\": \"fedora-hubs\"\
+        ,\n      \"namespace\": null,\n      \"parent\": {\n        \"access_groups\"\
+        : {\n          \"admin\": [],\n          \"commit\": [],\n          \"ticket\"\
+        : []\n        },\n        \"access_users\": {\n          \"admin\": [\n  \
+        \          \"duffy\",\n            \"lmacken\",\n            \"nyazdani\"\
+        ,\n            \"bee2502\",\n            \"devyani7\",\n            \"dhrish20\"\
+        ,\n            \"jflory7\",\n            \"pingou\",\n            \"ryanlerch\"\
+        ,\n            \"skrzepto\",\n            \"sayanchowdhury\",\n          \
+        \  \"bkorren\",\n            \"atelic\",\n            \"wispfox\",\n     \
+        \       \"jcline\",\n            \"abompard\"\n          ],\n          \"\
+        commit\": [\n            \"etsang\"\n          ],\n          \"owner\": [\n\
+        \            \"decause\"\n          ],\n          \"ticket\": []\n       \
+        \ },\n        \"close_status\": [\n          \"Invalid\",\n          \"Insufficient\
+        \ data\",\n          \"Fixed\",\n          \"Duplicate\"\n        ],\n   \
+        \     \"custom_keys\": [],\n        \"date_created\": \"1433438868\",\n  \
+        \      \"date_modified\": \"1505214285\",\n        \"description\": \"Fedora\
+        \ Hubs\",\n        \"fullname\": \"fedora-hubs\",\n        \"id\": 50,\n \
+        \       \"milestones\": {\n          \"2017 Year End\": \"\",\n          \"\
+        Flock 2017\": \"\",\n          \"None\": \"\",\n          \"Post Flock 2017\"\
+        : \"\",\n          \"Production\": \"\"\n        },\n        \"name\": \"\
+        fedora-hubs\",\n        \"namespace\": null,\n        \"parent\": null,\n\
+        \        \"priorities\": {\n          \"1\": \"High\",\n          \"2\": \"\
+        Normal\",\n          \"3\": \"Low\"\n        },\n        \"settings\": {\n\
+        \          \"Enforce_signed-off_commits_in_pull-request\": false,\n      \
+        \    \"Minimum_score_to_merge_pull-request\": -1,\n          \"Only_assignee_can_merge_pull-request\"\
+        : false,\n          \"Web-hooks\": null,\n          \"always_merge\": false,\n\
+        \          \"fedmsg_notifications\": true,\n          \"issue_tracker\": true,\n\
+        \          \"issues_default_to_private\": false,\n          \"project_documentation\"\
+        : true,\n          \"pull_request_access_only\": false,\n          \"pull_requests\"\
+        : true\n        },\n        \"tags\": [\n          \"fedora-infra\",\n   \
+        \       \"hubs\"\n        ],\n        \"url_path\": \"fedora-hubs\",\n   \
+        \     \"user\": {\n          \"fullname\": \"Remy DeCausemaker\",\n      \
+        \    \"name\": \"decause\"\n        }\n      },\n      \"priorities\": {},\n\
+        \      \"settings\": {\n        \"Enforce_signed-off_commits_in_pull-request\"\
+        : false,\n        \"Minimum_score_to_merge_pull-request\": -1,\n        \"\
+        Only_assignee_can_merge_pull-request\": false,\n        \"Web-hooks\": null,\n\
+        \        \"always_merge\": false,\n        \"fedmsg_notifications\": true,\n\
+        \        \"issue_tracker\": false,\n        \"issues_default_to_private\"\
+        : false,\n        \"project_documentation\": false,\n        \"pull_request_access_only\"\
+        : false,\n        \"pull_requests\": false\n      },\n      \"tags\": [],\n\
+        \      \"url_path\": \"fork/abompard/fedora-hubs\",\n      \"user\": {\n \
+        \       \"fullname\": \"Aurelien Bompard\",\n        \"name\": \"abompard\"\
+        \n      }\n    },\n    {\n      \"access_groups\": {\n        \"admin\": [],\n\
+        \        \"commit\": [],\n        \"ticket\": []\n      },\n      \"access_users\"\
+        : {\n        \"admin\": [],\n        \"commit\": [],\n        \"owner\": [\n\
+        \          \"abompard\"\n        ],\n        \"ticket\": []\n      },\n  \
+        \    \"close_status\": [],\n      \"custom_keys\": [],\n      \"date_created\"\
+        : \"1490611717\",\n      \"date_modified\": \"1490611717\",\n      \"description\"\
+        : \"The Fedora Infrastructure Sphinx documentation project\",\n      \"fullname\"\
+        : \"forks/abompard/infra-docs\",\n      \"id\": 2156,\n      \"milestones\"\
+        : {},\n      \"name\": \"infra-docs\",\n      \"namespace\": null,\n     \
+        \ \"parent\": {\n        \"access_groups\": {\n          \"admin\": [],\n\
+        \          \"commit\": [],\n          \"ticket\": []\n        },\n       \
+        \ \"access_users\": {\n          \"admin\": [\n            \"abompard\",\n\
+        \            \"bowlofeggs\",\n            \"kushal\",\n            \"maxamillion\"\
+        ,\n            \"kevin\",\n            \"puiterwijk\",\n            \"pingou\"\
+        ,\n            \"sayanchowdhury\",\n            \"ryanlerch\",\n         \
+        \   \"pfrields\"\n          ],\n          \"commit\": [\n            \"tflink\"\
+        ,\n            \"kparal\"\n          ],\n          \"owner\": [\n        \
+        \    \"jcline\"\n          ],\n          \"ticket\": []\n        },\n    \
+        \    \"close_status\": [],\n        \"custom_keys\": [],\n        \"date_created\"\
+        : \"1485549712\",\n        \"date_modified\": \"1502897027\",\n        \"\
+        description\": \"The Fedora Infrastructure Sphinx documentation project\"\
+        ,\n        \"fullname\": \"infra-docs\",\n        \"id\": 1658,\n        \"\
+        milestones\": {},\n        \"name\": \"infra-docs\",\n        \"namespace\"\
+        : null,\n        \"parent\": null,\n        \"priorities\": {},\n        \"\
+        settings\": {\n          \"Enforce_signed-off_commits_in_pull-request\": false,\n\
+        \          \"Minimum_score_to_merge_pull-request\": -1,\n          \"Only_assignee_can_merge_pull-request\"\
+        : false,\n          \"Web-hooks\": null,\n          \"always_merge\": false,\n\
+        \          \"fedmsg_notifications\": true,\n          \"issue_tracker\": true,\n\
+        \          \"issues_default_to_private\": false,\n          \"project_documentation\"\
+        : true,\n          \"pull_request_access_only\": false,\n          \"pull_requests\"\
+        : true\n        },\n        \"tags\": [\n          \"fedora-infra\",\n   \
+        \       \"docs\",\n          \"infrastructure\",\n          \"fedora\",\n\
+        \          \"documentation\"\n        ],\n        \"url_path\": \"infra-docs\"\
+        ,\n        \"user\": {\n          \"fullname\": \"Jeremy Cline\",\n      \
+        \    \"name\": \"jcline\"\n        }\n      },\n      \"priorities\": {},\n\
+        \      \"settings\": {\n        \"Enforce_signed-off_commits_in_pull-request\"\
+        : false,\n        \"Minimum_score_to_merge_pull-request\": -1,\n        \"\
+        Only_assignee_can_merge_pull-request\": false,\n        \"Web-hooks\": null,\n\
+        \        \"always_merge\": false,\n        \"fedmsg_notifications\": true,\n\
+        \        \"issue_tracker\": false,\n        \"issues_default_to_private\"\
+        : false,\n        \"project_documentation\": false,\n        \"pull_request_access_only\"\
+        : false,\n        \"pull_requests\": false\n      },\n      \"tags\": [],\n\
+        \      \"url_path\": \"fork/abompard/infra-docs\",\n      \"user\": {\n  \
+        \      \"fullname\": \"Aurelien Bompard\",\n        \"name\": \"abompard\"\
+        \n      }\n    }\n  ],\n  \"repos\": [\n    {\n      \"access_groups\": {\n\
+        \        \"admin\": [],\n        \"commit\": [],\n        \"ticket\": []\n\
+        \      },\n      \"access_users\": {\n        \"admin\": [\n          \"duffy\"\
+        ,\n          \"lmacken\",\n          \"nyazdani\",\n          \"bee2502\"\
+        ,\n          \"devyani7\",\n          \"dhrish20\",\n          \"jflory7\"\
+        ,\n          \"pingou\",\n          \"ryanlerch\",\n          \"skrzepto\"\
+        ,\n          \"sayanchowdhury\",\n          \"bkorren\",\n          \"atelic\"\
+        ,\n          \"wispfox\",\n          \"jcline\",\n          \"abompard\"\n\
+        \        ],\n        \"commit\": [\n          \"etsang\"\n        ],\n   \
+        \     \"owner\": [\n          \"decause\"\n        ],\n        \"ticket\"\
+        : []\n      },\n      \"close_status\": [\n        \"Invalid\",\n        \"\
+        Insufficient data\",\n        \"Fixed\",\n        \"Duplicate\"\n      ],\n\
+        \      \"custom_keys\": [],\n      \"date_created\": \"1433438868\",\n   \
+        \   \"date_modified\": \"1505214285\",\n      \"description\": \"Fedora Hubs\"\
+        ,\n      \"fullname\": \"fedora-hubs\",\n      \"id\": 50,\n      \"milestones\"\
+        : {\n        \"2017 Year End\": \"\",\n        \"Flock 2017\": \"\",\n   \
+        \     \"None\": \"\",\n        \"Post Flock 2017\": \"\",\n        \"Production\"\
+        : \"\"\n      },\n      \"name\": \"fedora-hubs\",\n      \"namespace\": null,\n\
+        \      \"parent\": null,\n      \"priorities\": {\n        \"1\": \"High\"\
+        ,\n        \"2\": \"Normal\",\n        \"3\": \"Low\"\n      },\n      \"\
+        settings\": {\n        \"Enforce_signed-off_commits_in_pull-request\": false,\n\
+        \        \"Minimum_score_to_merge_pull-request\": -1,\n        \"Only_assignee_can_merge_pull-request\"\
+        : false,\n        \"Web-hooks\": null,\n        \"always_merge\": false,\n\
+        \        \"fedmsg_notifications\": true,\n        \"issue_tracker\": true,\n\
+        \        \"issues_default_to_private\": false,\n        \"project_documentation\"\
+        : true,\n        \"pull_request_access_only\": false,\n        \"pull_requests\"\
+        : true\n      },\n      \"tags\": [\n        \"fedora-infra\",\n        \"\
+        hubs\"\n      ],\n      \"url_path\": \"fedora-hubs\",\n      \"user\": {\n\
+        \        \"fullname\": \"Remy DeCausemaker\",\n        \"name\": \"decause\"\
+        \n      }\n    },\n    {\n      \"access_groups\": {\n        \"admin\": [],\n\
+        \        \"commit\": [],\n        \"ticket\": []\n      },\n      \"access_users\"\
+        : {\n        \"admin\": [],\n        \"commit\": [],\n        \"owner\": [\n\
+        \          \"abompard\"\n        ],\n        \"ticket\": []\n      },\n  \
+        \    \"close_status\": [],\n      \"custom_keys\": [],\n      \"date_created\"\
+        : \"1501323827\",\n      \"date_modified\": \"1501326044\",\n      \"description\"\
+        : \"A widget example for the Fedora Hubs workshop\",\n      \"fullname\":\
+        \ \"fedora-hubs-widget-workshop\",\n      \"id\": 2691,\n      \"milestones\"\
+        : {},\n      \"name\": \"fedora-hubs-widget-workshop\",\n      \"namespace\"\
+        : null,\n      \"parent\": null,\n      \"priorities\": {},\n      \"settings\"\
+        : {\n        \"Enforce_signed-off_commits_in_pull-request\": false,\n    \
+        \    \"Minimum_score_to_merge_pull-request\": -1,\n        \"Only_assignee_can_merge_pull-request\"\
+        : false,\n        \"Web-hooks\": null,\n        \"always_merge\": false,\n\
+        \        \"fedmsg_notifications\": true,\n        \"issue_tracker\": true,\n\
+        \        \"issues_default_to_private\": false,\n        \"project_documentation\"\
+        : true,\n        \"pull_request_access_only\": false,\n        \"pull_requests\"\
+        : true\n      },\n      \"tags\": [],\n      \"url_path\": \"fedora-hubs-widget-workshop\"\
+        ,\n      \"user\": {\n        \"fullname\": \"Aurelien Bompard\",\n      \
+        \  \"name\": \"abompard\"\n      }\n    },\n    {\n      \"access_groups\"\
+        : {\n        \"admin\": [\n          \"sysadmin-main\"\n        ],\n     \
+        \   \"commit\": [],\n        \"ticket\": []\n      },\n      \"access_users\"\
+        : {\n        \"admin\": [\n          \"smooge\",\n          \"puiterwijk\"\
+        ,\n          \"nb\",\n          \"pingou\",\n          \"ralph\",\n      \
+        \    \"abompard\"\n        ],\n        \"commit\": [],\n        \"owner\"\
+        : [\n          \"kevin\"\n        ],\n        \"ticket\": [\n          \"\
+        bowlofeggs\",\n          \"adrian\",\n          \"mizdebsk\",\n          \"\
+        jcline\"\n        ]\n      },\n      \"close_status\": [\n        \"Duplicate\"\
+        ,\n        \"Fixed\",\n        \"Insufficient data\",\n        \"Invalid\"\
+        ,\n        \"Upstream\",\n        \"Will Not/Can Not fix\"\n      ],\n   \
+        \   \"custom_keys\": [],\n      \"date_created\": \"1474746651\",\n      \"\
+        date_modified\": \"1508776105\",\n      \"description\": \"Fedora Infrastructure\"\
+        ,\n      \"fullname\": \"fedora-infrastructure\",\n      \"id\": 1176,\n \
+        \     \"milestones\": {},\n      \"name\": \"fedora-infrastructure\",\n  \
+        \    \"namespace\": null,\n      \"parent\": null,\n      \"priorities\":\
+        \ {\n        \"\": \"\",\n        \"1\": \"High\",\n        \"2\": \"Normal\"\
+        ,\n        \"3\": \"Low\",\n        \"4\": \"Handwavy Future\"\n      },\n\
+        \      \"settings\": {\n        \"Enforce_signed-off_commits_in_pull-request\"\
+        : false,\n        \"Minimum_score_to_merge_pull-request\": -1,\n        \"\
+        Only_assignee_can_merge_pull-request\": false,\n        \"Web-hooks\": null,\n\
+        \        \"always_merge\": false,\n        \"fedmsg_notifications\": true,\n\
+        \        \"issue_tracker\": true,\n        \"issues_default_to_private\":\
+        \ false,\n        \"project_documentation\": false,\n        \"pull_request_access_only\"\
+        : false,\n        \"pull_requests\": true\n      },\n      \"tags\": [\n \
+        \       \"easyfix\",\n        \"request-for-resources\",\n        \"outage\"\
+        ,\n        \"lists\",\n        \"authentication\"\n      ],\n      \"url_path\"\
+        : \"fedora-infrastructure\",\n      \"user\": {\n        \"fullname\": \"\
+        Kevin Fenzi\",\n        \"name\": \"kevin\"\n      }\n    },\n    {\n    \
+        \  \"access_groups\": {\n        \"admin\": [],\n        \"commit\": [],\n\
+        \        \"ticket\": []\n      },\n      \"access_users\": {\n        \"admin\"\
+        : [\n          \"abompard\",\n          \"bowlofeggs\",\n          \"kushal\"\
+        ,\n          \"maxamillion\",\n          \"kevin\",\n          \"puiterwijk\"\
+        ,\n          \"pingou\",\n          \"sayanchowdhury\",\n          \"ryanlerch\"\
+        ,\n          \"pfrields\"\n        ],\n        \"commit\": [\n          \"\
+        tflink\",\n          \"kparal\"\n        ],\n        \"owner\": [\n      \
+        \    \"jcline\"\n        ],\n        \"ticket\": []\n      },\n      \"close_status\"\
+        : [],\n      \"custom_keys\": [],\n      \"date_created\": \"1485549712\"\
+        ,\n      \"date_modified\": \"1502897027\",\n      \"description\": \"The\
+        \ Fedora Infrastructure Sphinx documentation project\",\n      \"fullname\"\
+        : \"infra-docs\",\n      \"id\": 1658,\n      \"milestones\": {},\n      \"\
+        name\": \"infra-docs\",\n      \"namespace\": null,\n      \"parent\": null,\n\
+        \      \"priorities\": {},\n      \"settings\": {\n        \"Enforce_signed-off_commits_in_pull-request\"\
+        : false,\n        \"Minimum_score_to_merge_pull-request\": -1,\n        \"\
+        Only_assignee_can_merge_pull-request\": false,\n        \"Web-hooks\": null,\n\
+        \        \"always_merge\": false,\n        \"fedmsg_notifications\": true,\n\
+        \        \"issue_tracker\": true,\n        \"issues_default_to_private\":\
+        \ false,\n        \"project_documentation\": true,\n        \"pull_request_access_only\"\
+        : false,\n        \"pull_requests\": true\n      },\n      \"tags\": [\n \
+        \       \"fedora-infra\",\n        \"docs\",\n        \"infrastructure\",\n\
+        \        \"fedora\",\n        \"documentation\"\n      ],\n      \"url_path\"\
+        : \"infra-docs\",\n      \"user\": {\n        \"fullname\": \"Jeremy Cline\"\
+        ,\n        \"name\": \"jcline\"\n      }\n    },\n    {\n      \"access_groups\"\
+        : {\n        \"admin\": [],\n        \"commit\": [],\n        \"ticket\":\
+        \ []\n      },\n      \"access_users\": {\n        \"admin\": [],\n      \
+        \  \"commit\": [],\n        \"owner\": [\n          \"abompard\"\n       \
+        \ ],\n        \"ticket\": []\n      },\n      \"close_status\": [\n      \
+        \  \"Invalid\",\n        \"Insufficient data\",\n        \"Fixed\",\n    \
+        \    \"Duplicate\"\n      ],\n      \"custom_keys\": [],\n      \"date_created\"\
+        : \"1467129373\",\n      \"date_modified\": \"1467129373\",\n      \"description\"\
+        : \"A REST-based service to send karma to a Fedora user.\",\n      \"fullname\"\
+        : \"plus-plus-service\",\n      \"id\": 807,\n      \"milestones\": {},\n\
+        \      \"name\": \"plus-plus-service\",\n      \"namespace\": null,\n    \
+        \  \"parent\": null,\n      \"priorities\": {},\n      \"settings\": {\n \
+        \       \"Enforce_signed-off_commits_in_pull-request\": false,\n        \"\
+        Minimum_score_to_merge_pull-request\": -1,\n        \"Only_assignee_can_merge_pull-request\"\
+        : false,\n        \"Web-hooks\": null,\n        \"always_merge\": false,\n\
+        \        \"fedmsg_notifications\": true,\n        \"issue_tracker\": true,\n\
+        \        \"issues_default_to_private\": false,\n        \"project_documentation\"\
+        : false,\n        \"pull_request_access_only\": false,\n        \"pull_requests\"\
+        : true\n      },\n      \"tags\": [],\n      \"url_path\": \"plus-plus-service\"\
+        ,\n      \"user\": {\n        \"fullname\": \"Aurelien Bompard\",\n      \
+        \  \"name\": \"abompard\"\n      }\n    }\n  ],\n  \"user\": {\n    \"fullname\"\
+        : \"Aurelien Bompard\",\n    \"name\": \"abompard\"\n  }\n}"}
+    headers:
+      connection: [Keep-Alive]
+      content-length: ['14727']
+      content-type: [application/json]
+      date: ['Sun, 05 Nov 2017 11:23:45 GMT']
+      keep-alive: ['timeout=5, max=100']
+      referrer-policy: [same-origin]
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.1e-fips mod_wsgi/3.4
+          Python/2.7.5]
+      set-cookie: ['pagure=eyJfcGVybWFuZW50Ijp0cnVlfQ.DOCGQg.zh-ell9n0Y9hxH-szbqIT8E9t_4;
+          Expires=Wed, 06-Dec-2017 11:23:46 GMT; Secure; HttpOnly; Path=/']
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+      x-content-type-options: [nosniff]
+      x-frame-options: ['ALLOW FROM https://pagure.io/']
+      x-xss-protection: [1; mode=block]
+    status: {code: 200, message: OK}
+- request:
+    body: null
+    headers:
+      Accept: ['*/*']
+      Accept-Encoding: ['gzip, deflate']
+      Connection: [keep-alive]
+      User-Agent: [python-requests/2.18.4]
+    method: GET
+    uri: https://pagure.io/api/0/user/someone-that-does-not-exist
+  response:
+    body: {string: !!python/unicode "{\n  \"error\": \"No such user found\",\n  \"\
+        error_code\": \"ENOUSER\"\n}"}
+    headers:
+      connection: [Keep-Alive]
+      content-length: ['62']
+      content-type: [application/json]
+      date: ['Sun, 05 Nov 2017 11:23:48 GMT']
+      keep-alive: ['timeout=5, max=100']
+      referrer-policy: [same-origin]
+      server: [Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.1e-fips mod_wsgi/3.4
+          Python/2.7.5]
+      set-cookie: ['pagure=eyJfcGVybWFuZW50Ijp0cnVlfQ.DOCGRA.owymPNg1OS1nm-kTzu84jjK9Vl8;
+          Expires=Wed, 06-Dec-2017 11:23:48 GMT; Secure; HttpOnly; Path=/']
+      strict-transport-security: [max-age=15768000; includeSubDomains; preload]
+      x-content-type-options: [nosniff]
+      x-frame-options: ['ALLOW FROM https://pagure.io/']
+      x-xss-protection: [1; mode=block]
+    status: {code: 404, message: NOT FOUND}
+version: 1
diff --git a/hubs/tests/widgets/test_user_issues.py b/hubs/tests/widgets/test_user_issues.py
new file mode 100644
index 0000000..ff7db31
--- /dev/null
+++ b/hubs/tests/widgets/test_user_issues.py
@@ -0,0 +1,115 @@
+from __future__ import unicode_literals
+
+from . import WidgetTest
+
+
+class TestUserIssues(WidgetTest):
+
+    plugin = "user_issues"
+    initial_widget_config = {
+        "bugzilla_user": "aurelien@bompard.org",
+        "github_user": "fedora-infra",
+        "pagure_user": "abompard",
+    }
+
+    def populate(self):
+        super(TestUserIssues, self).populate()
+        self._add_widget_under_test()
+
+    def _get_should_invalidate_result_bugzilla(self, msg):
+        func = self.widget.module.get_cached_functions()['GetBugzillaIssues']
+        return func(self.widget).should_invalidate(msg)
+
+    def test_should_invalidate_wrong_topic_bugzilla(self):
+        msg = {'topic': 'hubs.widget.update.WRONG.TOPIC'}
+        self.assertFalse(self._get_should_invalidate_result_bugzilla(msg))
+
+    def test_should_invalidate_good_match_bugzilla(self):
+        msg = {
+            'topic': 'tests.bugzilla.bug.new',
+            'msg': {
+                "bug": {
+                    "creator": "aurelien@bompard.org",
+                },
+            },
+        }
+        self.assertTrue(self._get_should_invalidate_result_bugzilla(msg))
+
+    def test_should_invalidate_wrong_user_bugzilla(self):
+        msg = {
+            'topic': 'tests.bugzilla.bug.new',
+            'msg': {
+                "bug": {
+                    "creator": "not-aurelien@bompard.org",
+                },
+            },
+        }
+        self.assertFalse(self._get_should_invalidate_result_bugzilla(msg))
+
+    def _get_should_invalidate_result_github(self, msg):
+        func = self.widget.module.get_cached_functions()['GetGithubIssues']
+        return func(self.widget).should_invalidate(msg)
+
+    def test_should_invalidate_wrong_topic_github(self):
+        msg = {'topic': 'hubs.widget.update.WRONG.TOPIC'}
+        self.assertFalse(self._get_should_invalidate_result_github(msg))
+
+    def test_should_invalidate_good_match_github(self):
+        msg = {
+            'topic': 'tests.github.issue.opened',
+            'msg': {
+                "issue": {
+                    "user": {
+                            "login": "fedora-infra",
+                    },
+                },
+            },
+        }
+        self.assertTrue(self._get_should_invalidate_result_github(msg))
+
+    def test_should_invalidate_wrong_user_github(self):
+        msg = {
+            'topic': 'tests.github.issue.opened',
+            'msg': {
+                "issue": {
+                    "user": {
+                            "login": "not-fedora-infra",
+                    },
+                },
+            },
+        }
+        self.assertFalse(self._get_should_invalidate_result_github(msg))
+
+    def _get_should_invalidate_result_pagure(self, msg):
+        func = self.widget.module.get_cached_functions()['GetPagureIssues']
+        return func(self.widget).should_invalidate(msg)
+
+    def test_should_invalidate_wrong_topic_pagure(self):
+        msg = {'topic': 'hubs.widget.update.WRONG.TOPIC'}
+        self.assertFalse(self._get_should_invalidate_result_pagure(msg))
+
+    def test_should_invalidate_good_match_pagure(self):
+        msg = {
+            'topic': 'tests.pagure.issue.new',
+            'msg': {
+                "issue": {
+                    "user": {
+                            "name": "abompard",
+                    },
+                },
+            },
+        }
+        self.assertTrue(self._get_should_invalidate_result_pagure(msg))
+
+    def test_should_invalidate_wrong_user_pagure(self):
+        msg = {
+            'topic': 'tests.pagure.issue.new',
+            'msg': {
+                "issue": {
+                    "user": {
+                            "name": "not-abompard",
+                    },
+                },
+            },
+        }
+        self.assertFalse(self._get_should_invalidate_result_pagure(msg))
diff --git a/hubs/widgets/user_issues/__init__.py b/hubs/widgets/user_issues/__init__.py
new file mode 100644
index 0000000..e08aa95
--- /dev/null
+++ b/hubs/widgets/user_issues/__init__.py
@@ -0,0 +1,68 @@
+from __future__ import unicode_literals
+
+from hubs.widgets import validators
+from hubs.widgets.base import Widget
+from hubs.widgets.view import RootWidgetView
+
+from .functions import (GetPagureIssues, GetGithubIssues, GetBugzillaIssues,
+                        combine)
+
+import logging
+
+log = logging.getLogger('hubs.widgets')
+
+
+class UserIssues(Widget):
+    name = "user_issues"
+    label = "Tickets I Filed"
+    position = "right"
+    parameters = [
+        dict(
+            name="bugzilla_user",
+            label="Bugzilla Email",
+            default="None",
+            validator=validators.BugzillaUser,
+            help="Bugzilla email to search issues for",
+        ),
+        dict(
+            name="github_user",
+            label="Github Username",
+            default="None",
+            validator=validators.GithubOrganization,
+            help="Github username to search issues for",
+        ),
+        dict(
+            name="pagure_user",
+            label="Pagure Username",
+            default="None",
+            validator=validators.PagureUser,
+            help="Pagure username to search issues for",
+        ),
+    ]
+    cached_functions_module = ".functions"
+
+    def validate_parameters(self, values):
+        config = {}
+        if sum(username != "None" for source, username in values.items()) == 0:
+            raise ValueError("You should specify at least one of usernames")
+
+        for param in self.get_parameters():
+            if values[param.name] == "None":
+                config[param.name] = "None"
+            else:
+                config[param.name] = param.validator(values[param.name])
+        return config
+
+
+class BaseView(RootWidgetView):
+
+    def get_context(self, instance, *args, **kwargs):
+        get_pagure_issues = GetPagureIssues(instance)
+        get_github_issues = GetGithubIssues(instance)
+        get_bugzilla_issues = GetBugzillaIssues(instance)
+        issues = combine(get_pagure_issues(), get_github_issues(),
+                         get_bugzilla_issues())
+        return dict(
+            display_number=min(10, len(issues)),
+            issues=issues,
+        )
diff --git a/hubs/widgets/user_issues/functions.py b/hubs/widgets/user_issues/functions.py
new file mode 100644
index 0000000..620d99a
--- /dev/null
+++ b/hubs/widgets/user_issues/functions.py
@@ -0,0 +1,135 @@
+from __future__ import unicode_literals, absolute_import
+from operator import itemgetter
+
+import requests
+import logging
+
+import arrow
+
+from hubs.widgets.caching import CachedFunction
+
+
+log = logging.getLogger('hubs.widgets')
+
+
+def combine(pagure, github, bugzilla):
+    ''' Data for user_issues widget
+    Combine pagure, github and bugzilla issues in one list
+    (sorted by last updated date)'''
+    # TODO: Possibly make this more versatile, allow different patterns for
+    # combining/filtering
+
+    return sorted(github + bugzilla + pagure,
+                  key=itemgetter("updated"), reverse=True)
+
+
+class GetPagureIssues(CachedFunction):
+    ''' Data for user_issues widget
+    Queries Pagure api for issues '''
+
+    def execute(self):
+        # TODO: Wait until api allows to search issues created
+        # by user in all repos.
+        return []
+
+    def should_invalidate(self, message):
+        if ".pagure.issue." not in message["topic"]:
+            return False
+        try:
+            pagure_user = message["msg"]["issue"]["user"]["name"]
+        except KeyError:
+            return False
+        return pagure_user == self.instance.config["pagure_user"]
+
+
+class GetGithubIssues(CachedFunction):
+    ''' Data for user_issues widget
+    Queries Github api for issues '''
+
+    github_date_format = "%Y-%m-%dT%H:%M:%SZ"
+
+    def execute(self):
+        username = self.instance.config["github_user"]
+        if username == "None":
+            return []
+        issues = []
+        tmpl = ("https://api.github.com"
+                "/search/issues?q=author:{}&sort=updated")
+        url = tmpl.format(username)
+        result = requests.get(url).json()
+        if "items" not in result:
+            return []
+        result = result["items"]
+        for issue in result:
+            if ("pull-request" not in issue and issue["state"] != "closed"):
+                issue_details = {}
+                issue_details["id"] = issue["id"]
+                issue_details["name"] = issue["title"]
+                issue_details["status"] = issue["state"]
+                issue_details["creator"] = issue["user"]["login"]
+                if "assignee" in issue:
+                    issue_details["assignee"] = issue["assignee"]
+                issue_details["url"] = issue["html_url"]
+                issue_details["updated"] = \
+                    arrow.get(issue.get("updated_at")).timestamp
+                log.info(issue_details["updated"], issue["updated_at"])
+                url = issue["repository_url"].split('/')
+                issue_details["repo"] = url[len(url)-1]
+                issue_details["from"] = "github.com"
+                issues.append(issue_details)
+        return issues
+
+    def should_invalidate(self, message):
+        if ".github.issue." not in message["topic"]:
+            return False
+        try:
+            username = message["msg"]["issue"]["user"]["login"]
+        except KeyError:
+            return False
+        return username == self.instance.config["github_user"]
+
+
+class GetBugzillaIssues(CachedFunction):
+    ''' Data for user_issues widget
+    Queries Bugzilla jsonrpc api for issues '''
+
+    def execute(self):
+        username = self.instance.config["bugzilla_user"]
+        if username == "None":
+            return []
+        issues = []
+        tmpl = ("https://bugzilla.redhat.com/jsonrpc.cgi?"
+                "method=Bug.search&params=[{{\"creator\":\"{}\"}}]")
+        url = tmpl.format(username)
+        result = requests.get(url).json()
+        if result["error"] is not None:
+            return []
+        result = result["result"]["bugs"]
+        for bug in result:
+            if bug["status"] == "CLOSED":
+                continue
+            issue_details = {}
+            issue_details["id"] = bug["id"]
+            issue_details["name"] = bug["summary"]
+            issue_details["status"] = bug["status"]
+            issue_details["creator"] = bug["creator"]
+            if "assigned_to" in bug:
+                issue_details["assignee"] = bug["assigned_to"]
+            issue_details["updated"] = \
+                arrow.get(bug["last_change_time"]).timestamp
+            issue_details["url"] = \
+                ("https://bugzilla.redhat.com/"
+                 "show_bug.cgi?id={id}").format(id=issue_details["id"])
+            issue_details["repo"] = bug["product"]
+            issue_details["from"] = "bugzilla.redhat.com"
+            issues.append(issue_details)
+        return issues
+
+    def should_invalidate(self, message):
+        if ".bugzilla.bug." not in message["topic"]:
+            return False
+        try:
+            username = message["msg"]["bug"]["creator"]
+        except KeyError:
+            return False
+        return username == self.instance.config["bugzilla_user"]
diff --git a/hubs/widgets/user_issues/templates/root.html b/hubs/widgets/user_issues/templates/root.html
new file mode 100644
index 0000000..180471a
--- /dev/null
+++ b/hubs/widgets/user_issues/templates/root.html
@@ -0,0 +1,52 @@
+<ul class="list-unstyled">
+  {% for i in range(display_number) %}
+    <li class="media py-2">
+      <div class="align-self-center mr-3">
+        <a  href="{{issues[i]['url']}}"
+            target="_blank">
+        <span class="badge badge-primary">#{{ issues[i]['id'] }}</span>
+      </a>
+      </div>
+      <div class="media-body">
+        <div style="text-align:right">
+          <span class="text-muted" title="{{issues[i]['from']}}">
+            {{issues[i]['from']}}
+          </span>
+        </div>
+        <div>
+          {{ issues[i]['repo'] }} |
+          <span title="{{ issues[i]['name'] }}">
+            {% if issues[i]['name']|length > 45 %}
+             {{ issues[i]['name'][:45] }}
+            {% else %}
+             {{ issues[i]['name'] }}
+            {% endif %}
+          </span>
+        </div>
+	<small>
+        {% if issues[i]['assignee'] %}
+          Assigned to:
+          {% if issues[i]['from'] == 'pagure.io' %}
+            <a href="https://pagure.io/user/{{ issues[i]['assignee'] }}"
+                target="_blank">
+                {{ issues[i]['assignee'] }}
+            </a>
+          {% elif issues[i]['from'] == 'github.com' %}
+            <a href="https://github.com/{{ issues[i]['assignee'] }}"
+                target="_blank">
+            {{ issues[i]['assignee'] }}
+            </a>
+          {% else %}
+            {{ issues[i]['assignee'] }}
+          {% endif %}
+
+        {% else %}
+          <span class="text-muted">Unassigned</span>
+        {% endif %}
+        --
+        <span class="text-muted">{{issues[i]['status']}}</span>
+	</small>
+      </div>
+    </li>
+  {% endfor %}
+</ul>
diff --git a/hubs/widgets/validators.py b/hubs/widgets/validators.py
index ef19ad0..0401be4 100644
--- a/hubs/widgets/validators.py
+++ b/hubs/widgets/validators.py
@@ -116,3 +116,21 @@ def CommaSeparatedList(value):
     if not isinstance(value, list):
         raise ValueError("Expected a list")
     return value
+
+
+def PagureUser(value):
+    url = "https://pagure.io/api/0/user/{}".format(value)
+    response = requests.get(url).json()
+    if "error" in response:
+        raise ValueError("Invalid Pagure username")
+    return value
+
+
+def BugzillaUser(value):
+    tmpl = ("https://bugzilla.redhat.com/jsonrpc.cgi?"
+            "method=User.get&params=[{{\"names\":[\"{}\"]}}]")
+    url = tmpl.format(value)
+    response = requests.get(url).json()
+    if response["error"] is not None:
+        raise ValueError("Invalid Bugzilla email")
+    return value

