def menu(self, args): workflow().add_item( "Search for projects", "Search for projects and open the project page in your default browser", autocomplete=":projects ", icon=icons.PROJECTS, ) workflow().add_item( "Search for repositories", "Search for repositories and open the repository page in your default browser", autocomplete=":repos ", icon=icons.REPOSITORIES, ) workflow().add_item( "Search or raise pull requests", "Search for pull requests or raise new ones based on Stash's suggestions", autocomplete=":pullrequests ", icon=icons.PULL_REQUESTS, ) workflow().add_item( "Preferences", "Change Stash connection settings, refresh the cache or the workflow itself", autocomplete=":config ", icon=icons.SETTINGS, ) workflow().add_item( "Help", "Get help about the workflow and how to get support", autocomplete=":help ", icon=icons.HELP )
def _add_to_result_list(self, project): workflow().add_item(title=project.name, subtitle=project.description, arg=':projects {}'.format(project.key), valid=True, largetext=project.name, icon=workflow().cachefile('{}/{}'.format(PROJECT_AVATAR_DIR, project.key)))
def menu(self, args): workflow().add_item( 'Search for projects', 'Search for projects and open the project page in your default browser', autocomplete=':projects ', icon=icons.PROJECTS ) workflow().add_item( 'Search for repositories', 'Search for repositories and open the repository page in your default browser', autocomplete=':repos ', icon=icons.REPOSITORIES ) workflow().add_item( 'Search for pull requests', 'Search for pull requests and open them in your default browser', autocomplete=':pullrequests ', icon=icons.PULL_REQUESTS ) workflow().add_item( 'Preferences', 'Change Stash connection settings, refresh the cache or the workflow itself', autocomplete=':config ', icon=icons.SETTINGS ) workflow().add_item( 'Help', 'Get help about the workflow and how to get support', autocomplete=':help ', icon=icons.HELP )
def menu(self, args): if len(args) == 2: workflow().add_item( u"Suggested pull requests {}".format(_num_pull_requests("suggestions")), "Suggested pull requests to raise based on your recent commits", arg=":pullrequests suggestions", icon=icons.PR_SUGGESTIONS, valid=True, ) workflow().add_item( u"All open pull requests {}".format(_num_pull_requests("open")), "Search in all open pull requests", arg=":pullrequests open", icon=icons.OPEN, valid=True, ) workflow().add_item( u"Your pull requests to review {}".format(_num_pull_requests("review")), "Search in pull requests you have to review", arg=":pullrequests review", icon=icons.REVIEW, valid=True, ) workflow().add_item( u"Your created pull requests {}".format(_num_pull_requests("created")), "Search in pull requests you created", arg=":pullrequests created", icon=icons.CREATED, valid=True, ) workflow().add_item("Main menu", autocomplete="", icon=icons.GO_BACK) else: cache_key, update_interval = _pull_request_modes[args[-2]] pull_request_menu = PullRequestFilterableMenu(args, update_interval, cache_key) return pull_request_menu.run()
def _add_to_result_list(self, pull_request): workflow().add_item(title=u'{} #{}: {} → {}'.format(pull_request.repo_name, pull_request.pull_request_id, pull_request.from_branch, pull_request.to_branch), subtitle=pull_request.title, arg=':pullrequests ' + pull_request.link, largetext=pull_request.title, valid=True, icon=workflow().cachefile('{}/{}'.format(PROJECT_AVATAR_DIR, pull_request.project_key)))
def _notify_if_upgrade_available(): if workflow().update_available: new_version = workflow().cached_data('__workflow_update_status', max_age=0)['version'] workflow().add_item('An update is available!', 'Update the workflow from version {} to {}'.format(__version__, new_version), arg=':config update', valid=True, icon=icons.UPDATE)
def _add_to_result_list(self, plan): workflow().add_item(title=u'{} {}'.format(plan.name, FAVOURITE_PLAN if plan.is_favourite else ''), subtitle=plan.description, largetext=plan.name, arg=':plans ' + plan.plan_key, modifier_subtitles={u'shift': u'Trigger build execution for this plan'}, # `cmd``, ``ctrl``, ``shift``, ``alt`` and ``fn`` copytext='{}/browse/{}'.format(workflow().settings.get(HOST_URL), plan.plan_key), valid=True)
def _add_to_result_list(self, pull_request): workflow().add_item( title=unicode(pull_request), subtitle=pull_request.title, arg=":pullrequests " + pull_request.link, largetext=pull_request.title, valid=True, icon=workflow().cachefile("{}/{}".format(PROJECT_AVATAR_DIR, pull_request.project_key)), )
def _add_to_result_list(self, repo): workflow().add_item(title=u'{} / {} {} {}'.format(repo.project_name, repo.name, PUBLIC if repo.public else '', FORK if repo.fork else ''), arg=':repos {}/{}'.format(repo.project_key, repo.slug), valid=True, largetext=repo.name, copytext=repo.clone_url, # allows to use CMD+C to copy the clone URL icon=workflow().cachefile('{}/{}'.format(PROJECT_AVATAR_DIR, repo.project_key)))
def _add_to_result_list(self, branch): workflow().add_item(title=branch.name, subtitle=branch.description, largetext=branch.name, arg=':branches ' + branch.key, modifier_subtitles={ u'shift': u'Trigger build execution for this plan branch' }, # `cmd``, ``ctrl``, ``shift``, ``alt`` and ``fn`` copytext='{}/browse/{}'.format(workflow().settings.get(HOST_URL), branch.key), valid=True)
def execute(self, args, ctrl_pressed, shift_pressed): if 'sethost' in args: workflow().settings[HOST_URL] = args[-1] print('New Bamboo host: {}'.format(args[-1])) elif 'setuser' in args: workflow().settings[USER_NAME] = args[-1] print('New Bamboo user: {}'.format(args[-1])) elif 'setpw' in args: workflow().save_password(USER_PW, args[-1]) print('Saved Bamboo password in keychain') elif 'verifycert' in args: verify_cert = workflow().settings.get(VERIFY_CERT, 'false') == 'true' toggle = (str(not verify_cert)).lower() workflow().settings[VERIFY_CERT] = toggle print('Enabled certificate verification' if toggle == 'true' else 'Disabled certificate verification') elif 'sync' in args: workflow().clear_cache() update_bamboo_cache() print('Bamboo data synchronization triggered') elif 'update' in args: try: if workflow().start_update(): print('Update of workflow finished') else: print('You already have the latest workflow version') except Exception, e: print('Update of workflow failed: {}'.format(str(e)))
def _fetch_bamboo_data_if_necessary(bamboo_facade): # cached_data can only take a bare callable (no args), # so we need to wrap callables needing arguments in a function # that needs none. def wrapper_projects(): return bamboo_facade.projects() projects = workflow().cached_data(PROJECTS_CACHE_KEY, wrapper_projects, max_age=UPDATE_INTERVAL_PROJECTS) workflow().logger.debug('{} projects cached'.format(len(projects))) def wrapper_plans(): plans_with_branches = bamboo_facade.plans() for plan in plans_with_branches: branches = bamboo_facade.branches(plan.plan_key) plan.branches = branches return plans_with_branches plans = workflow().cached_data(PLANS_CACHE_KEY, wrapper_plans, max_age=UPDATE_INTERVAL_PLANS) workflow().logger.debug('{} plans cached'.format(len(plans))) def wrapper_results(): return bamboo_facade.results() results = workflow().cached_data(STATUS_CACHE_KEY, wrapper_results, max_age=UPDATE_INTERVAL_STATUS) workflow().logger.debug('{} build results cached'.format(len(results)))
def build_bamboo_facade(): bamboo_host = workflow().settings.get(HOST_URL, None) if not bamboo_host: raise ValueError("Bamboo host URL not set.") bamboo_user = workflow().settings.get(USER_NAME, None) try: bamboo_pw = workflow().get_password(USER_PW) except PasswordNotFound: bamboo_pw = None verify_cert = workflow().settings.get(VERIFY_CERT, "false") == "true" return BambooFacade(bamboo_host, bamboo_user, bamboo_pw, verify_cert)
def build_stash_facade(): stash_host = workflow().settings.get(HOST_URL, None) if stash_host is None: raise ValueError("Stash host URL not set.") stash_user = workflow().settings.get(USER_NAME, None) try: stash_pw = workflow().get_password(USER_PW) except PasswordNotFound: stash_pw = None verify_cert = workflow().settings.get(VERIFY_CERT, "false") == "true" return StashFacade(stash_host, stash_user, stash_pw, verify_cert)
def menu(self, args): if len(args) == 2: workflow().add_item( u'All open pull requests {}'.format(_num_pull_requests('open')), 'Search in all open pull requests', arg=':pullrequests open', icon=icons.OPEN, valid=True ) workflow().add_item( u'Your pull requests to review {}'.format(_num_pull_requests('review')), 'Search in pull requests you have to review', arg=':pullrequests review', icon=icons.REVIEW, valid=True ) workflow().add_item( u'Your created pull requests {}'.format(_num_pull_requests('created')), 'Search in pull requests you created', arg=':pullrequests created', icon=icons.CREATED, valid=True ) workflow().add_item( 'Main menu', autocomplete='', icon=icons.GO_BACK ) else: cache_key, update_interval = _pull_request_modes[args[-2]] pull_request_menu = PullRequestFilterableMenu(args, update_interval, cache_key) return pull_request_menu.run()
def execute(self, args, ctrl_pressed, shift_pressed): import webbrowser host_url = workflow().settings.get(HOST_URL) if args[-1] == 'viewAgents': webbrowser.open('{}/admin/agent/viewAgents.action'.format(host_url)) elif shift_pressed: from src.actions import build_bamboo_facade bamboo_facade = build_bamboo_facade() try: bamboo_facade.stop_build(args[-1]) print('Successfully stopped build for {}'.format(args[-1])) except Exception, e: workflow().logger.debug('workflow ' + str(e)) print('Failed to stop build for {}: {}'.format(args[-1], str(e)))
def alfred_is_dark(): # copied from wunderlist workflow: background_rgba = workflow().alfred_env['theme_background'] if background_rgba: rgb = [int(x) for x in background_rgba[5:-6].split(',')] return (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255 < 0.5 return False
def get_data_from_cache(cache_key, update_interval): # Set `data_func` to None, as we don't want to update the cache in this script and `max_age` to 0 # because we want the cached data regardless of age try: data = workflow().cached_data(cache_key, None, max_age=0) except Exception: # this might happen when there are incompatible model changes and the pickle cache cannot be deserialzed # anymore => in this case it is better to clear the cache and to re-trigger data syncing workflow().clear_cache() data = [] # Start update script if cached data is too old (or doesn't exist) if not workflow().cached_data_fresh(cache_key, max_age=update_interval): update_stash_cache() return data
def _add_to_result_list(self, build_result): build_result_info = u'{} / #{}'.format(build_result.plan_name, str(build_result.build_number)) workflow().add_item(title=build_result_info, icon=self._icon_for_build_status(build_result.build_state), subtitle=u'{}, duration: {}, {}, {}'.format(build_result.test_summary, build_result.duration_desc, build_result.relative_time, strip_tags(build_result.build_reason)), modifier_subtitles={ u'ctrl': u'Open page of artifacts to download', u'shift': u'Trigger build execution for this plan' }, # `cmd``, ``ctrl``, ``shift``, ``alt`` and ``fn`` largetext=build_result_info, arg=':results {} {}'.format(build_result.build_result_key, build_result.plan_key), copytext='{}/browse/{}'.format(workflow().settings.get(HOST_URL), build_result.build_result_key), valid=True)
def icon_theme(): global _icon_theme if not _icon_theme: icon_theme = workflow().settings.get(ICON_THEME) if icon_theme: _icon_theme = icon_theme else: _icon_theme = 'light' if alfred_is_dark() else 'dark' return _icon_theme
def _fetch_user_avatars(repositories, stash_facade): user_repos = set(filter(lambda r: r.project_key.startswith('~'), repositories)) for r in user_repos: try: avatar = stash_facade.fetch_user_avatar(r.project_key[1:]) if avatar is not None: with open(workflow().cachefile('{}/{}'.format(PROJECT_AVATAR_DIR, r.project_key)), 'wb') as avatar_file: shutil.copyfileobj(avatar, avatar_file) except HTTPError: pass # ignore as not every Stash user might have an avatar configured
def route(args): # e.g., args = ":config sethost http://localhost,--exec" command_string = args[0] # :config sethost http://localhost command = command_string.split(' ') if not workflow().settings.get(HOST_URL, None) and 'sethost' not in command: call_alfred('stash:config sethost ') return handler = IndexWorkflowAction action = next(iter(command), None) if action: handler = WORKFLOW_ACTIONS.get(action, IndexWorkflowAction) if '--exec' in args: handler().execute(command, cmd_pressed='--cmd' in args, shift_pressed='--shift' in args) else: # show menu handler().menu(command) _notify_if_upgrade_available() workflow().send_feedback()
def menu(self, args): from src.actions import build_bamboo_facade bamboo_facade = build_bamboo_facade() dashboard = bamboo_facade.dashboard() workflow().add_item(title=dashboard.agent_summary, largetext=dashboard.agent_summary, arg=':dashboard viewAgents', icon=self._icon_for_overall_status(dashboard.status), valid=True) for q in dashboard.builds: try: agent_name = 'on {}'.format(q['agent']['name']) if 'agent' in q else '' workflow().add_item(title='{}: {}'.format(q['planName'], q['jobName']), subtitle='{}: {} {}'.format(q['messageType'], q['messageText'], agent_name), largetext=q['messageType'], icon=self._icon_for_build_status(q['messageType']), arg=':dashboard {}'.format(q['resultKey']), modifier_subtitles={u'shift': u'Stop currently running build'}, # `cmd``, ``ctrl``, ``shift``, ``alt`` and ``fn`` valid=True) except: # sometimes the build results JSON does not contain proper build items pass workflow().add_item('Main menu', autocomplete='', icon=icons.GO_BACK)
def _find_all_projects(stash_facade): workflow().logger.debug('Starting to fetch projects...') projects = stash_facade.all_projects() if not os.path.exists(workflow().cachefile(PROJECT_AVATAR_DIR)): os.makedirs(workflow().cachefile(PROJECT_AVATAR_DIR)) for p in projects: avatar = stash_facade.fetch_project_avatar(p.key) with open(workflow().cachefile('{}/{}'.format(PROJECT_AVATAR_DIR, p.key)), 'wb') as avatar_file: shutil.copyfileobj(avatar, avatar_file) workflow().logger.debug('Found {} projects '.format(len(projects))) return projects
def menu(self, args): workflow().add_item( 'About this version', 'Current version: {}. See here for the changelog.'.format(__version__), arg=':help version', valid=True, icon=icons.WHATS_NEW ) workflow().add_item( 'Found a bug or miss a feature?', 'Go through our issue database or create a new issue.', arg=':help issues', valid=True, icon=icons.ISSUES ) workflow().add_item( 'About us', 'We are Mibex Software. Find out more about us.', arg=':help about', valid=True, icon=icons.INFO ) workflow().add_item( 'Credits', 'Dean Jackson: Python Alfred Workflow, Ian Paterson: inspiration for a menu-based Alfred workflow.', arg=':help credits', valid=False, icon=icons.CREDITS ) workflow().add_item('Main menu', autocomplete='', icon=icons.GO_BACK)
def try_bamboo_connection(show_success=True): try: bamboo_facade = build_bamboo_facade() bamboo_facade.is_running() if show_success: workflow().add_item("Congratulations, connection to Bamboo was successful!", icon=icons.OK) return True except SSLError: workflow().add_item("SSL error: Try with certificate verification disabled", icon=icons.ERROR) return False except Exception, e: workflow().add_item("Error when connecting Bamboo server", str(e), icon=icons.ERROR) return False
def try_stash_connection(show_success=True): try: stash_facade = build_stash_facade() stash_facade.verify_stash_connection() if show_success: workflow().add_item("Congratulations, connection to Stash was successful!", icon=icons.OK) return True except SSLError: workflow().add_item("SSL error: Try with certificate verification disabled", icon=icons.ERROR) return False except Exception, e: workflow().add_item("Error when connecting Stash server", str(e), icon=icons.ERROR) return False
def run(self): workflow().logger.debug("workflow args: {}".format(self.args)) _notify_if_cache_update_in_progress() data = get_data_from_cache(self.cache_key, self.update_interval) query = self.args[-1] # query may not be empty or contain only whitespace. This will raise a ValueError. if query and data: data = workflow().filter(query, data, key=self._get_result_filter(), min_score=SEARCH_MIN_SCORE) workflow().logger.debug("{} {} matching `{}`".format(self.entity_name, len(data), query)) if not data: # only do a REST call in case there is no query given because only in that case it is likely that there # is a problem with the connection to Stash and we would like to prevent doing slow calls in here if query or (not query and try_stash_connection(show_success=False)): workflow().add_item("No matching {} found.".format(self.entity_name), icon=icons.ERROR) else: for e in data: self._add_to_result_list(e) self._add_item_after_last_result()
def _fetch_stash_data_if_necessary(stash_facade): # cached_data can only take a bare callable (no args), # so we need to wrap callables needing arguments in a function # that needs none. def wrapper_projects(): return _find_all_projects(stash_facade) projects = workflow().cached_data(PROJECTS_CACHE_KEY, wrapper_projects, max_age=UPDATE_INTERVAL_PROJECTS) workflow().logger.debug('{} projects cached'.format(len(projects))) def wrapper_repositories(): return _find_all_repositories(stash_facade) repos = workflow().cached_data(REPOS_CACHE_KEY, wrapper_repositories, max_age=UPDATE_INTERVAL_REPOS) workflow().logger.debug('{} repositories cached'.format(len(repos))) def wrapper_pull_requests_to_review(): return _my_pull_requests_to_review(stash_facade) pull_requests_to_review = workflow().cached_data(PULL_REQUESTS_REVIEW_CACHE_KEY, wrapper_pull_requests_to_review, max_age=UPDATE_INTERVAL_MY_PULL_REQUESTS) workflow().logger.debug('{} pull requests to review cached'.format(len(pull_requests_to_review))) def wrapper_created_pull_requests(): return _my_created_pull_requests(stash_facade) pull_requests_created = workflow().cached_data(PULL_REQUESTS_CREATED_CACHE_KEY, wrapper_created_pull_requests, max_age=UPDATE_INTERVAL_MY_PULL_REQUESTS) workflow().logger.debug('{} pull requests created cached'.format(len(pull_requests_created))) def wrapper_open_pull_requests(): return _open_pull_requests(stash_facade) open_pull_requests = workflow().cached_data(PULL_REQUESTS_OPEN_CACHE_KEY, wrapper_open_pull_requests, max_age=UPDATE_INTERVAL_OPEN_PULL_REQUESTS) workflow().logger.debug('{} open pull requests cached'.format(len(open_pull_requests)))
def _find_all_repositories(stash_facade): workflow().logger.debug('Starting to fetch repositories...') repositories = stash_facade.all_repositories() _fetch_user_avatars(repositories, stash_facade) workflow().logger.debug('Found {} repositories '.format(len(repositories))) return repositories