예제 #1
0
def list(assignee, author, label, state, type, web, story_points,
         output_format, show_state):
    """List issues related to the project."""
    if web:
        iteration = get_config("iteration")
        area = get_config("area")
        project = get_config("project")
        organization = get_config("organization")
        query = work_item_query(
            assignee=assignee,
            author=author,
            label=label,
            state=state,
            area=area,
            iteration=iteration,
            work_item_type=type,
            story_points=story_points,
        )
        click.launch(
            f"{organization}/{project}/_workitems/?_a=query&wiql={quote(query)}"
        )
    else:
        cmd_list(
            assignee=assignee,
            author=author,
            label=label,
            state=state,
            work_item_type=type,
            story_points=story_points,
            output_format=output_format,
            show_state=show_state,
            **get_common_options(),
        )
예제 #2
0
def test_get_config_fallback():
    """
    Test overrides via env vars.
    """
    with pytest.raises(Exception):
        get_config("team1")

    assert get_config("team1", "foobar") == "foobar"
예제 #3
0
def check_merge_strategy_policy() -> None:
    """
    Make sure merge strategy is set correctly.
    """
    merge_strategy = get_config("merge_strategy", fallback="")
    if merge_strategy != "":
        set_merge_strategy_policy(
            merge_strategy=merge_strategy,
            organization=get_config("organization"),
            project=get_config("project"),
        )
예제 #4
0
def cmd_open_pr(pullrequest_id: Union[str, int]) -> None:
    """
    Open a specific PULLREQUEST_ID. '!' prefix is allowed.
    """
    pullrequest_id = str(pullrequest_id).lstrip("!").strip()

    project = get_config("project")
    organization = get_config("organization")

    click.launch(
        f"{organization}/{project}/_git/{get_repo_name()}/pullrequest/{pullrequest_id}"
    )
예제 #5
0
def get_common_options():
    """
    Retrieve common config options.

    Retrieves set of config settings from config file that are used in every command.
    """
    return {
        "team": get_config("team"),
        "area": get_config("area"),
        "iteration": get_config("iteration"),
        "organization": get_config("organization"),
        "project": get_config("project"),
    }
예제 #6
0
def common_options(function):
    """
    Custom decorator to avoid repeating commonly used options.

    To be used in Click commands.
    """
    function = click.option(
        "--team",
        required=True,
        type=str,
        default=lambda: get_config("team"),
        help="The code of the team in azure",
    )(function)
    function = click.option(
        "--area",
        required=True,
        type=str,
        default=lambda: get_config("area"),
        help="The area code",
    )(function)
    function = click.option(
        "--iteration",
        required=True,
        type=str,
        default=lambda: get_config("iteration"),
        help="The current iteration (sprint)",
    )(function)
    function = click.option(
        "--organization",
        required=True,
        type=str,
        default=lambda: get_config("organization"),
        help="The organization in azure",
    )(function)
    function = click.option(
        "--project",
        required=True,
        type=str,
        default=lambda: get_config("project"),
        help="The project in azure",
    )(function)
    return function
예제 #7
0
def list(assignee, label, limit, state, web):
    """
    List pull requests related to the project.
    """
    project = get_config("project")
    organization = get_config("organization")

    # Translate github's {open|closed|merged|all}
    # To devops's {active|abandoned|completed|all}
    if state == "closed":
        state = "abandoned"
    if state == "open":
        state = "active"
    if state == "merged":
        state = "completed"

    if web:
        console.print(
            "[dark_orange3]>[/dark_orange3] Opening the pull requests web view."
        )

        if state == "all":
            console.print(
                "\t You specified state='all' but ignoring because the web view does not support it."
            )
            state = "active"
        if label:
            console.print(
                f"\t You specified label='{label}' but ignoring because the web view does not support it."
            )
        if assignee:
            console.print(
                f"\t You need to manually filter on 'Assigned to' = '{assignee}'"
            )

        click.launch(
            f"{organization}/{project}/_git/{get_repo_name()}/pullrequests?_a={state}"
        )

    else:
        cmd_list_pr(assignee, label, limit, state, project, organization)
예제 #8
0
def test_create_file(tmp_path):
    """
    Test reading a config file.
    """
    config = {
        "user_aliases": {
            "john": "*****@*****.**",
            "jane": "*****@*****.**"
        }
    }

    with working_directory(tmp_path):
        with open(".doing-cli-config.yml", "w") as file:
            yaml.dump(config, file)

        assert get_config("user_aliases") == config["user_aliases"]
예제 #9
0
def cli():
    """
    CLI for repository/issue workflow on Azure Devops.
    """
    # Set doing default as environment variables
    defaults = get_config("defaults", fallback="")
    if defaults:
        for setting, default in defaults.items():
            if str(setting) not in os.environ:
                os.environ[setting] = str(default)
            else:
                if os.environ[setting] != default:
                    console.print(
                        f"Warning: Trying to set {setting} to '{default}' (specified in .doing-ing-config.yml)"
                    )
                    console.print(
                        f"\tbut {setting} has already been set to '{os.environ[setting]}' in the environment variables."
                    )
예제 #10
0
def close(pr_id):
    """
    Close a specific PR_ID.

    PR_ID is the ID number of a pull request. '!' prefix is allowed.
    You can specify multiple IDs by separating with a space.
    """
    organization = get_config("organization")
    state = "abandoned"

    for id in pr_id:
        id = str(id).lstrip("!")
        cmd = f'az repos pr update --id {id} --status "{state}" '
        cmd += f'--org "{organization}"'
        result = run_command(cmd)
        assert result.get("status") == state
        console.print(
            f"[dark_orange3]>[/dark_orange3] pullrequest !{id} set to '{state}'"
        )
예제 #11
0
    show_envvar=True,
)
@click.option(
    "--parent",
    "-p",
    required=False,
    default="",
    type=str,
    help="To create a child work item, specify the ID of the parent work item.",
    show_envvar=True,
)
@click.option(
    "--reviewers",
    "-r",
    required=False,
    default=lambda: get_config("default_reviewers", ""),
    type=str,
    help=f"Space separated list of reviewer emails. Defaults to \"{get_config('default_reviewers','')}\"",
    show_envvar=True,
)
@click.option(
    "--draft/--no-draft",
    required=False,
    default=True,
    help="Create draft/WIP pull request. Reviewers will not be notified until you publish. Default is --draft.",
    show_envvar=True,
)
@click.option(
    "--auto-complete/--no-auto-complete",
    required=False,
    default=True,
예제 #12
0
def work_item_query(
    assignee: str,
    author: str,
    label: str,
    state: str,
    area: str,
    iteration: str,
    work_item_type: str,
    story_points: str,
):
    """Build query in wiql.

    # More on 'work item query language' syntax:
    # https://docs.microsoft.com/en-us/azure/devops/boards/queries/wiql-syntax?view=azure-devops
    """
    # ensure using user aliases
    assignee = replace_user_aliases(assignee)
    author = replace_user_aliases(author)

    # Get all workitems
    query = "SELECT [System.Id],[System.Title],[System.AssignedTo],"
    query += "[System.WorkItemType],[System.State],[System.CreatedDate], [System.State] "
    query += f"FROM WorkItems WHERE [System.AreaPath] = '{area}' "
    # Filter on iteration. Note we use UNDER so that user can choose to provide teams path for all sprints.
    query += f"AND [System.IterationPath] UNDER '{iteration}' "

    if assignee:
        query += f"AND [System.AssignedTo] = '{assignee}' "

    if author:
        query += f"AND [System.CreatedBy] = '{author}' "

    if label:
        for lab in label.split(","):
            query += f"AND [System.Tags] Contains '{lab.strip()}' "

    if state:
        custom_states = get_config("custom_states", {})
        if state in custom_states.keys():
            if type(custom_states[state]) is not list:
                custom_states[state] = [custom_states[state]]
            state_list = ",".join([f"'{x}'" for x in custom_states[state]])
            query += f"AND [System.State] IN ({state_list}) "
        elif state == "open":
            query += "AND [System.State] NOT IN ('Resolved','Closed','Done','Removed') "
        elif state == "closed":
            query += "AND [System.State] IN ('Resolved','Closed','Done') "
        elif state == "all":
            query += "AND [System.State] <> 'Removed' "
        elif state.startswith("'") and state.endswith("'"):
            query += f"AND [System.State] = {state} "
        else:
            raise ValueError(
                f"Invalid state: '{state}'. State should be:\n"
                "- one of the doing-cli default states: 'open', 'closed', 'all'\n"
                "- a custom state defined under 'custom_states' in the .doing-cli.config.yml file\n"
                "- a state available in this team, between quotes, e.g. \"'Active'\""
            )

    if work_item_type:
        validate_work_item_type(work_item_type)
        query += f"AND [System.WorkItemType] = '{work_item_type}' "

    if story_points:
        if story_points == "unassigned":
            query += "AND [Microsoft.VSTS.Scheduling.StoryPoints] = '' "
        elif story_points.startswith("<="):
            story_points = story_points.lstrip("<=")
            query += f"AND [Microsoft.VSTS.Scheduling.StoryPoints] <= '{story_points}' "
        elif story_points.startswith(">="):
            story_points = story_points.lstrip(">=")
            query += f"AND [Microsoft.VSTS.Scheduling.StoryPoints] >= '{story_points}' "
        elif story_points.startswith(">"):
            story_points = story_points.lstrip(">")
            query += f"AND [Microsoft.VSTS.Scheduling.StoryPoints] > '{story_points}' "
        elif story_points.startswith("<"):
            story_points = story_points.lstrip("<")
            query += f"AND [Microsoft.VSTS.Scheduling.StoryPoints] < '{story_points}' "
        else:
            query += f"AND [Microsoft.VSTS.Scheduling.StoryPoints] = '{story_points}' "

    # Ordering of results
    query += "ORDER BY [System.CreatedDate] asc"

    return query
예제 #13
0
def test_get_config_key():
    """
    Test overrides via env vars.
    """
    os.environ["DOING_CONFIG_TEAM"] = "my team"
    assert get_config("team") == "my team"