def main(): parser = argparse.ArgumentParser() config_dir = appdirs.user_config_dir('OSGoalTools', 'OpenStack') config_file = os.path.join(config_dir, 'storyboard.ini') parser.add_argument( '--config-file', default=config_file, help='configuration file (%(default)s)', ) group = parser.add_mutually_exclusive_group() group.add_argument( '-v', help='verbose mode', dest='log_level', default=logging.WARNING, action='store_const', const=logging.INFO, ) group.add_argument( '-q', help='quiet mode', dest='log_level', action='store_const', const=logging.WARNING, ) parser.add_argument( 'story_id', help='ID of the story to update', ) parser.add_argument( 'task_id', help='ID of the task to update', ) parser.add_argument( 'owner', type=int, help='owner of the task', ) args = parser.parse_args() warnings.filterwarnings( 'ignore', '.*Unverified HTTPS request is being made.*', ) logging.basicConfig(level=args.log_level, format='%(message)s') config = storyboard.get_config(args.config_file) try: sbc = storyboard.get_client(config) except Exception as err: parser.error(err) story = sbc.stories.get(id=args.story_id) task = story.tasks.get(id=args.task_id) LOG.info('Updating owner of task %s (%s)', task.id, task.title) story.tasks.update(id=task.id, assignee_id=args.owner)
def main(): parser = argparse.ArgumentParser() config_dir = appdirs.user_config_dir('OSGoalTools', 'OpenStack') config_file = os.path.join(config_dir, 'storyboard.ini') parser.add_argument( '--config-file', default=config_file, help='configuration file (%(default)s)', ) group = parser.add_mutually_exclusive_group() group.add_argument( '-v', help='verbose mode', dest='log_level', default=logging.WARNING, action='store_const', const=logging.INFO, ) group.add_argument( '-q', help='quiet mode', dest='log_level', action='store_const', const=logging.WARNING, ) parser.add_argument( 'story_id', help='ID of the story to update', ) args = parser.parse_args() warnings.filterwarnings( 'ignore', '.*Unverified HTTPS request is being made.*', ) logging.basicConfig(level=args.log_level, format='%(message)s') config = storyboard.get_config(args.config_file) try: sbc = storyboard.get_client(config) except Exception as err: parser.error(err) story = sbc.stories.get(id=args.story_id) LOG.info('Found story %s (%s)', story.id, story.title) for task in story.tasks.get_all(): if task.assignee_id: user = sbc.users.get(id=task.assignee_id) owner = '{} ({})'.format( user.full_name, user.id, ) else: owner = 'None' LOG.info('%s: %s assigned to %s', task.id, task.title, owner)
def main(): parser = argparse.ArgumentParser() config_dir = appdirs.user_config_dir('OSGoalTools', 'OpenStack') config_file = os.path.join(config_dir, 'storyboard.ini') parser.add_argument( '--config-file', default=config_file, help='configuration file (%(default)s)', ) group = parser.add_mutually_exclusive_group() group.add_argument( '-v', help='verbose mode', dest='log_level', default=logging.WARNING, action='store_const', const=logging.INFO, ) group.add_argument( '-q', help='quiet mode', dest='log_level', action='store_const', const=logging.WARNING, ) parser.add_argument( '--status', default=None, choices=(None, 'todo', 'inprogress', 'invalid', 'review', 'merged'), help='the next status', ) parser.add_argument( 'story_id', help='ID of the story to update', ) args = parser.parse_args() warnings.filterwarnings( 'ignore', '.*Unverified HTTPS request is being made.*', ) logging.basicConfig(level=args.log_level, format='%(message)s') config = storyboard.get_config(args.config_file) try: sbc = storyboard.get_client(config) except Exception as err: parser.error(err) transitions = { 'todo': 'review', 'inprogress': 'merged', 'review': 'inprogress', } story = sbc.stories.get(id=args.story_id) LOG.info('Found story %s (%s)', story.id, story.title) for task in story.tasks.get_all(): if args.status: next_status = args.status else: next_status = transitions.get(task.status) if next_status and next_status != task.status: LOG.info('Updating task %s to %s (%s)', task.id, next_status, task.title) story.tasks.update(id=task.id, status=next_status)
def main(): parser = argparse.ArgumentParser() config_dir = appdirs.user_config_dir('OSGoalTools', 'OpenStack') config_file = os.path.join(config_dir, 'storyboard.ini') parser.add_argument( '--config-file', default=config_file, help='configuration file (%(default)s)', ) group = parser.add_mutually_exclusive_group() group.add_argument( '-v', help='verbose mode', dest='log_level', default=logging.WARNING, action='store_const', const=logging.INFO, ) group.add_argument( '-q', help='quiet mode', dest='log_level', action='store_const', const=logging.WARNING, ) parser.add_argument( 'goal_url', help='published HTML page describing the goal', ) parser.add_argument( 'team', help='project team name', ) args = parser.parse_args() warnings.filterwarnings( 'ignore', '.*Unverified HTTPS request is being made.*', ) logging.basicConfig(level=args.log_level, format='%(message)s') config = storyboard.get_config(args.config_file) try: LOG.debug('reading goal info from {}'.format(args.goal_url)) goal_info = goals.get_info(args.goal_url) except Exception as err: parser.error(err) try: sbc = storyboard.get_client(config) except Exception as err: parser.error(err) title = '{}: {}'.format(args.team, goal_info['title']) story = storyboard.find_story(sbc=sbc, title=title) if not story: LOG.error('Did not find story with title %r', title) return 1 print(story.id) return 0
def take_action(self, parsed_args): gov_dat = governance.Governance(url=parsed_args.project_list) sb_config = storyboard.get_config(parsed_args.config_file) LOG.debug('finding champion assignments') sbc = storyboard.get_client(sb_config) story = sbc.stories.get(id='2002586') assignments = {} for task in story.tasks.get_all(): if task.assignee_id: user = sbc.users.get(id=task.assignee_id) assignments[task.title] = user.full_name else: assignments[task.title] = '' cleanup_changes = get_cleanup_changes_by_team() changes = all_changes(False) # We aren't going to migrate the settings for the infra team. interesting_teams = gov_dat.get_teams() interesting_teams.remove('Infrastructure') # The loci team had no work to do. interesting_teams.remove('loci') count_init = { team: 0 for team in interesting_teams } team_counts = { title: collections.Counter(count_init) for title, subject in self._subjects } open_counts = { title: collections.Counter(count_init) for title, subject in self._subjects } unreviewed_counts = collections.Counter(count_init) fail_counts = collections.Counter(count_init) subject_lookup = { subject: title for title, subject_list in self._subjects for subject in subject_list } all_titles = tuple(t for t, s in self._subjects) LOG.debug('counting in-tree changes') for c in changes: status = c.get('status') if status == 'ABANDONED': continue item = {gov_dat.get_repo_owner(c.get('project')) or 'other': 1} title = subject_lookup.get(c.get('subject')) if not title: continue team_counts[title].update(item) if c.get('status') != 'MERGED': open_counts[title].update(item) verified_votes = count_votes(c, 'Verified') if verified_votes.get(-1) or verified_votes.get(-2): fail_counts.update(item) # We count reviewers as anyone posting +/- 1 or +/- 2 # votes on a patch. reviewed_votes = count_votes(c, 'Code-Review') reviewers = ( sum(reviewed_votes.values()) - reviewed_votes.get(0, 0) ) if not reviewers: unreviewed_counts.update(item) columns = ( ('Team',) + all_titles + ('Failing', 'Unreviewed', 'Total', 'Champion') ) def get_done_value(title, team, done_msg='+'): if title != 'zuul': return done_msg if not team_counts['zuul'][team]: n_repos = len(list(gov_dat.get_repos_for_team(team))) return 'not started, {} repos'.format(n_repos) cleanup = cleanup_changes.get(team.lower()) if not cleanup: return 'cleanup patch not found' workflow_votes = count_votes(cleanup, 'Workflow') if cleanup.get('status') == 'MERGED': return done_msg if open_counts['zuul'][team]: return 'in progress' if workflow_votes.get(-1): if parsed_args.minimal: return 'ready for cleanup' return 'need to remove WIP from {}{}'.format( self._url_base, cleanup.get('_number')) if parsed_args.minimal: return 'waiting for cleanup' return 'waiting for cleanup {}{}'.format( self._url_base, cleanup.get('_number')) def format_count(title, team, done_msg='+'): oc = open_counts[title].get(team, 0) tc = team_counts[title].get(team, 0) if tc: if oc: return '{:3}/{:3}'.format(oc, tc) return get_done_value(title, team, done_msg) return '-' data = [ (team,) + tuple(format_count(t, team) for t in all_titles) + ( fail_counts.get(team, 0), unreviewed_counts.get(team, 0), sum(v.get(team, 0) for v in team_counts.values()), assignments.get(team, '') ) for team in sorted(interesting_teams, key=lambda x: x.lower()) ] # How many projects needed changes of this type? needed_counts = { title: 0 for title in all_titles } # How many projects have completed the changes of this type? done_counts = { title: 0 for title in all_titles } for row in data: for i, t in enumerate(all_titles, 1): if row[i] == '-': # ignore this row for this column continue needed_counts[t] += 1 if row[i] == '+': done_counts[t] += 1 summary_lines = {} for title, count in done_counts.items(): summary_lines[title] = '{:3}/{:3}'.format( count, needed_counts[title]) total_fail = sum(fail_counts.values()) total_unreviewed = sum(unreviewed_counts.values()) total_all = sum(sum(v.values()) for v in team_counts.values()) data.append( ('',) + tuple(summary_lines.get(t, '') for t in all_titles) + ( total_fail, total_unreviewed, total_all, '') ) if parsed_args.only_open: data = [ row for row in data if ''.join(row[1:4]).strip('+-') ] return (columns, data)
def main(): parser = argparse.ArgumentParser() config_dir = appdirs.user_config_dir('OSGoalTools', 'OpenStack') config_file = os.path.join(config_dir, 'storyboard.ini') parser.add_argument( '--config-file', default=config_file, help='configuration file (%(default)s)', ) parser.add_argument( '--project-list', default= 'http://git.openstack.org/cgit/openstack/governance/plain/reference/projects.yaml', # noqa help='URL or file path for governance projects.yaml list ' '(%(default)s)', ) group = parser.add_mutually_exclusive_group() group.add_argument( '-v', help='verbose mode', dest='log_level', default=logging.INFO, action='store_const', const=logging.DEBUG, ) group.add_argument( '-q', help='quiet mode', dest='log_level', action='store_const', const=logging.WARNING, ) story_group = parser.add_mutually_exclusive_group() story_group.add_argument( '--story', help='ID of an existing story to use', ) story_group.add_argument( '--separate-stories', default=False, action='store_true', help='create a story per project and task per repo', ) parser.add_argument( '--add-board', default=False, action='store_true', help='create a board as well as the story and tasks', ) parser.add_argument( '--tag', help='provide a tag for the stories', ) parser.add_argument( 'goal_url', help='published HTML page describing the goal', ) args = parser.parse_args() warnings.filterwarnings( 'ignore', '.*Unverified HTTPS request is being made.*', ) logging.basicConfig(level=args.log_level, format='%(message)s') config = storyboard.get_config(args.config_file) try: LOG.debug('reading goal info from {}'.format(args.goal_url)) goal_info = goals.get_info(args.goal_url) except Exception as err: parser.error(err) full_description = (goal_info['description'] + '\n\n' + goal_info['url']) try: LOG.debug('reading project list from {}'.format(args.project_list)) project_info = _get_project_info(args.project_list) except Exception as err: parser.error(err) project_names = sorted(project_info.keys(), key=lambda x: x.lower()) try: sbc = storyboard.get_client(config) except Exception as err: parser.error(err) try: governance_project = _find_project(sbc, _GOVERNANCE_PROJECT_NAME) except ValueError: parser.error('Could not find governance project {}'.format( _GOVERNANCE_PROJECT_NAME)) print('Governance project {} with id {}'.format(governance_project.name, governance_project.id)) print('Goal: {}\n\n{}\n'.format(goal_info['title'], goal_info['description'])) urls_to_show = [] if not args.separate_stories: # One story with a task per project team. if args.story: LOG.info('using specified story') story = sbc.stories.get(args.story) else: story = storyboard.find_or_create_story( sbc=sbc, title=goal_info['title'], description=full_description, ) _update_tags(sbc, story, args.tag) urls_to_show.append(_STORY_URL_TEMPLATE.format(story.id)) existing_tasks = {task.title: task for task in story.tasks.get_all()} for project_name in project_names: if project_name not in existing_tasks: LOG.info('adding task for %s', project_name) # NOTE(dhellmann): We always use the governance # repository for these tasks because a team does not # have a main repository. sbc.tasks.create( title=project_name, project_id=governance_project.id, story_id=story.id, ) else: LOG.info('already have task for %s', project_name) else: # One story per project team with a task per repo. for project_name in project_names: deliverables = project_info[project_name]['deliverables'].items() story = storyboard.find_or_create_story( sbc=sbc, title='{}: {}'.format(project_name, goal_info['title']), description=(goal_info['description'] + '\n\n' + goal_info['url'])) _update_tags(sbc, story, args.tag) urls_to_show.append(_STORY_URL_TEMPLATE.format(story.id)) existing_tasks = { task.title: task for task in story.tasks.get_all() } for d_name, d_info in sorted(deliverables): LOG.info('processing %s - %s', project_name, d_name) for repo in d_info['repos']: title = '{} - {}'.format(project_name, repo) if title not in existing_tasks: # Try to attach the task to the repository and # fall back to the governance repository if # storyboard doesn't know about the repo. try: sb_project = _find_project(sbc, repo) except ValueError: sb_project = governance_project LOG.info('adding task for %s (%s)', title, sb_project.name) sbc.tasks.create( title=title, project_id=sb_project.id, story_id=story.id, ) print() if args.add_board: existing = sbc.boards.get_all(title=goal_info['title']) if not existing: lanes = [] for position, worklist_settings in enumerate( _get_worklist_settings(args.tag)): title = worklist_settings['title'] LOG.debug('creating {} worklist'.format(title)) new_worklist = sbc.worklists.create(**worklist_settings) lanes.append({ 'position': position, 'list_id': str(new_worklist.id), }) LOG.info('creating new board') board = sbc.boards.create( title=goal_info['title'], description=full_description, lanes=lanes, ) LOG.info('created board {}'.format(board.id)) else: board = existing[0] LOG.info('found existing board {}'.format(board.id)) urls_to_show.append(_BOARD_URL_TEMPLATE.format(board.id)) for url in urls_to_show: print(url) return 0