Пример #1
0
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)
Пример #2
0
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)
Пример #3
0
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)
Пример #4
0
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
Пример #5
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)
Пример #6
0
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