├── .gitignore ├── .travis.yml ├── DFCC2B44-B93C-4199-B0F0-933ADBC8DB4A.png ├── Makefile ├── README.md ├── build └── Jira browse ticket.alfredworkflow ├── icon.png ├── info.plist ├── projects.txt ├── requirements.txt └── workflow ├── __init__.py ├── feedback.py ├── jira.py └── test_jira.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | projects.txt 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | script: make test 5 | -------------------------------------------------------------------------------- /DFCC2B44-B93C-4199-B0F0-933ADBC8DB4A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cskeppstedt/alfred-jira-browse-ticket/1c054f2f996677073437c8cc7acdbd9157a62f95/DFCC2B44-B93C-4199-B0F0-933ADBC8DB4A.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | pip install -r requirements.txt 3 | 4 | test: install 5 | nosetests 6 | 7 | watch: install 8 | nosetests --with-watch 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alfred-jira-browse-ticket 2 | 3 | [![Build Status](https://travis-ci.org/cskeppstedt/alfred-jira-browse-ticket.svg?branch=master)](https://travis-ci.org/cskeppstedt/alfred-jira-browse-ticket) 4 | 5 | Workflow for Alfred 2 that opens a JIRA ticket in the browser. Enter `jira 12345` or `jira PROJ-12345` in Alfred, and the full URL for the ticket will be suggested to you. 6 | 7 | ## Demo 8 | 9 | ![jira alfred](https://cloud.githubusercontent.com/assets/569742/13027725/a03e4db0-d25a-11e5-903c-0074bf37ef7e.gif) 10 | 11 | ## Setup 12 | 13 | [Download](http://www.packal.org/workflow/jira-browse-ticket) and install the workflow. Open alfred preferences, go to the Worflows tab. 14 | 15 | 1. Right click the workflow "Jira browse ticket" and select "Show in Finder" 16 | 2. Open projects.txt 17 | 3. Add the full URL for your JIRA setup, with the project prefix. Example: https://your.jira.com/PROJ 18 | 19 | Now you can use Alfred and enter "jira 12345" which will suggest "https://your.jira.com/PROJ-12345". You can repeat step 3 if you want additional projects/instances. 20 | -------------------------------------------------------------------------------- /build/Jira browse ticket.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cskeppstedt/alfred-jira-browse-ticket/1c054f2f996677073437c8cc7acdbd9157a62f95/build/Jira browse ticket.alfredworkflow -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cskeppstedt/alfred-jira-browse-ticket/1c054f2f996677073437c8cc7acdbd9157a62f95/icon.png -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | com.cskeppstedt.jirabrowseticket 7 | category 8 | Tools 9 | connections 10 | 11 | DFCC2B44-B93C-4199-B0F0-933ADBC8DB4A 12 | 13 | 14 | destinationuid 15 | 5AFD40AB-F4BA-4F42-A4D9-3BE458317E95 16 | modifiers 17 | 0 18 | modifiersubtext 19 | 20 | 21 | 22 | 23 | createdby 24 | Christoffer Skeppstedt 25 | description 26 | Enter your JIRA ticket number to get a full URL 27 | disabled 28 | 29 | name 30 | Jira browse ticket 31 | objects 32 | 33 | 34 | config 35 | 36 | plusspaces 37 | 38 | url 39 | {query} 40 | utf8 41 | 42 | 43 | type 44 | alfred.workflow.action.openurl 45 | uid 46 | 5AFD40AB-F4BA-4F42-A4D9-3BE458317E95 47 | version 48 | 0 49 | 50 | 51 | config 52 | 53 | argumenttype 54 | 0 55 | escaping 56 | 126 57 | keyword 58 | jira 59 | queuedelaycustom 60 | 3 61 | queuedelayimmediatelyinitially 62 | 63 | queuedelaymode 64 | 0 65 | queuemode 66 | 1 67 | script 68 | /usr/bin/python workflow/jira.py {query} 69 | subtext 70 | Enter ticket ID with or without project prefix, e.g. PROJ-12345 71 | title 72 | Browse JIRA ticket 73 | type 74 | 0 75 | withspace 76 | 77 | 78 | type 79 | alfred.workflow.input.scriptfilter 80 | uid 81 | DFCC2B44-B93C-4199-B0F0-933ADBC8DB4A 82 | version 83 | 0 84 | 85 | 86 | readme 87 | ## Setup 88 | 89 | 1. Right click the workflow "Jira browse ticket" and select "Show in Finder" 90 | 2. Open projects.txt 91 | 3. Add the full URL for your JIRA setup, with the project prefix. Example: https://your.jira.com/PROJ 92 | 93 | Now you can use Alfred and enter "jira 12345" which will suggest "https://your.jira.com/PROJ-12345". You can repeat step 3 if you want additional projects/instances. 94 | uidata 95 | 96 | 5AFD40AB-F4BA-4F42-A4D9-3BE458317E95 97 | 98 | ypos 99 | 10 100 | 101 | DFCC2B44-B93C-4199-B0F0-933ADBC8DB4A 102 | 103 | ypos 104 | 10 105 | 106 | 107 | webaddress 108 | https://github.com/cskeppstedt/alfred-jira-browse-ticket 109 | 110 | 111 | -------------------------------------------------------------------------------- /projects.txt: -------------------------------------------------------------------------------- 1 | https://jira.sample.com/jira/browse/PROJ 2 | https://jira.other.com/jira/browse/FOO 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML==3.11 2 | argh==0.26.1 3 | nose==1.3.7 4 | nose-watch==0.9.1 5 | pathtools==0.1.2 6 | watchdog==0.8.3 7 | wsgiref==0.1.2 8 | -------------------------------------------------------------------------------- /workflow/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cskeppstedt/alfred-jira-browse-ticket/1c054f2f996677073437c8cc7acdbd9157a62f95/workflow/__init__.py -------------------------------------------------------------------------------- /workflow/feedback.py: -------------------------------------------------------------------------------- 1 | # author: Peter Okma 2 | import xml.etree.ElementTree as et 3 | 4 | 5 | class Feedback(): 6 | """Feeback used by Alfred Script Filter 7 | 8 | Usage: 9 | fb = Feedback() 10 | fb.add_item('Hello', 'World') 11 | fb.add_item('Foo', 'Bar') 12 | print fb 13 | 14 | """ 15 | 16 | def __init__(self): 17 | self.feedback = et.Element('items') 18 | 19 | def __repr__(self): 20 | """XML representation used by Alfred 21 | 22 | Returns: 23 | XML string 24 | """ 25 | return et.tostring(self.feedback) 26 | 27 | def add_item(self, title, subtitle="", arg="", valid="yes", autocomplete="", icon="icon.png"): 28 | """ 29 | Add item to alfred Feedback 30 | 31 | Args: 32 | title(str): the title displayed by Alfred 33 | Keyword Args: 34 | subtitle(str): the subtitle displayed by Alfred 35 | arg(str): the value returned by alfred when item is selected 36 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 37 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 38 | icon(str): filename of icon that Alfred will display 39 | """ 40 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), 41 | arg=arg, valid=valid, autocomplete=autocomplete) 42 | _title = et.SubElement(item, 'title') 43 | _title.text = title 44 | _sub = et.SubElement(item, 'subtitle') 45 | _sub.text = subtitle 46 | _icon = et.SubElement(item, 'icon') 47 | _icon.text = icon 48 | -------------------------------------------------------------------------------- /workflow/jira.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from feedback import Feedback 4 | import sys 5 | import urllib 6 | 7 | 8 | def generate_items(projects=[], query=""): 9 | keys = set([p.split("/")[-1] for p in projects]) 10 | query_split = query.split("-") 11 | query_key = query_split[0].upper() 12 | matches = set(k for k in keys if k.startswith(query_key)) 13 | hasMatches = len(matches) > 0 14 | 15 | items = [] 16 | 17 | for project in projects: 18 | proj_key = project.split("/")[-1] 19 | ticket_url = "%s-%s" % (project, query) 20 | 21 | if hasMatches: 22 | if proj_key not in matches: 23 | continue 24 | else: 25 | query_num = "" 26 | if len(query_split) > 1: 27 | query_num = query_split[1] 28 | 29 | ticket_url = "%s-%s" % (project, query_num) 30 | 31 | items.append({ 32 | "title": ticket_url, 33 | "subtitle": ticket_url, 34 | "valid": "YES", 35 | "arg": ticket_url, 36 | "icon": "icon.png" 37 | }) 38 | 39 | return items 40 | 41 | 42 | def send_feedback(feeds, items): 43 | for item in items: 44 | feeds.add_item(**item) 45 | 46 | print feeds 47 | 48 | 49 | def main(projects, query): 50 | feeds = Feedback() 51 | items = generate_items(projects, query) 52 | send_feedback(feeds, items) 53 | 54 | 55 | if len(sys.argv) == 2: 56 | with open('projects.txt') as f: 57 | projects = f.read().splitlines() 58 | query = urllib.quote(sys.argv[1]) 59 | main(projects, query) 60 | -------------------------------------------------------------------------------- /workflow/test_jira.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import jira 3 | 4 | 5 | class TestGenerateItems(unittest.TestCase): 6 | def test_empty_query(self): 7 | query = "" 8 | projects = ["https://jira.somewhere/jira/GBL", "http://other/jira/JIR"] 9 | 10 | actual = jira.generate_items(projects, query) 11 | expected = [{ 12 | "title": "https://jira.somewhere/jira/GBL-", 13 | "subtitle": "https://jira.somewhere/jira/GBL-", 14 | "valid": "YES", 15 | "arg": "https://jira.somewhere/jira/GBL-", 16 | "icon": "icon.png" 17 | }, { 18 | "title": "http://other/jira/JIR-", 19 | "subtitle": "http://other/jira/JIR-", 20 | "valid": "YES", 21 | "arg": "http://other/jira/JIR-", 22 | "icon": "icon.png" 23 | }] 24 | 25 | self.assertEqual(actual, expected) 26 | 27 | def test_numeric_search(self): 28 | query = "123" 29 | projects = ["https://jira.somewhere/jira/GBL", "http://other/jira/JIR"] 30 | 31 | actual = jira.generate_items(projects, query) 32 | expected = [{ 33 | "title": "https://jira.somewhere/jira/GBL-123", 34 | "subtitle": "https://jira.somewhere/jira/GBL-123", 35 | "valid": "YES", 36 | "arg": "https://jira.somewhere/jira/GBL-123", 37 | "icon": "icon.png" 38 | }, { 39 | "title": "http://other/jira/JIR-123", 40 | "subtitle": "http://other/jira/JIR-123", 41 | "valid": "YES", 42 | "arg": "http://other/jira/JIR-123", 43 | "icon": "icon.png" 44 | }] 45 | 46 | self.assertEqual(actual, expected) 47 | 48 | def test_project_matching_search(self): 49 | query = "JIR-456" 50 | projects = ["https://jira.somewhere/jira/GBL", "http://other/jira/JIR"] 51 | 52 | actual = jira.generate_items(projects, query) 53 | expected = [{ 54 | "title": "http://other/jira/JIR-456", 55 | "subtitle": "http://other/jira/JIR-456", 56 | "valid": "YES", 57 | "arg": "http://other/jira/JIR-456", 58 | "icon": "icon.png" 59 | }] 60 | 61 | self.assertEqual(actual, expected) 62 | 63 | def test_project_prefix_search(self): 64 | queries = ["J", "JI", "JIR", "JIR-"] 65 | projects = ["https://jira.somewhere/jira/GBL", "http://other/jira/JIR"] 66 | 67 | for query in queries: 68 | actual = jira.generate_items(projects, query) 69 | expected = [{ 70 | "title": "http://other/jira/JIR-", 71 | "subtitle": "http://other/jira/JIR-", 72 | "valid": "YES", 73 | "arg": "http://other/jira/JIR-", 74 | "icon": "icon.png" 75 | }] 76 | 77 | self.assertEqual(actual, expected) 78 | 79 | 80 | class TestSendFeedback(unittest.TestCase): 81 | def test_init(self): 82 | self.assertEqual(4, 4) 83 | 84 | 85 | class TestMain(unittest.TestCase): 86 | def test_init(self): 87 | self.assertEqual(4, 4) 88 | --------------------------------------------------------------------------------