diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..52608af --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,2 @@ +[settings] +known_first_party=doing diff --git a/src/doing/issue/commands.py b/src/doing/issue/commands.py index 7380f9f..bbfe8fc 100644 --- a/src/doing/issue/commands.py +++ b/src/doing/issue/commands.py @@ -12,9 +12,7 @@ @click.group() def issue(): - """ - Work with issues. - """ + """Work with issues.""" pass @@ -24,8 +22,7 @@ def issue(): @issue.command() @click.argument("work_item_id", nargs=-1, required=True) def close(work_item_id): - """ - Close a specific WORK_ITEM_ID. + """Close a specific WORK_ITEM_ID. A '#' prefix is allowed. You can specify multiple IDs by separating with a space. """ @@ -125,8 +122,7 @@ def create( web: bool, story_points: str, ) -> None: - """ - Create an issue. + """Create an issue. ISSUE is the title to be used for the new work item. """ diff --git a/src/doing/list/_list.py b/src/doing/list/_list.py index 24c339a..fa39b4d 100644 --- a/src/doing/list/_list.py +++ b/src/doing/list/_list.py @@ -16,8 +16,7 @@ def work_item_query( assignee: str, author: str, label: str, state: str, area: str, iteration: str, type: str, story_points: str ): - """ - Build query in wiql. + """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 @@ -28,7 +27,7 @@ def work_item_query( # Get all workitems query = "SELECT [System.Id],[System.Title],[System.AssignedTo]," - query += "[System.WorkItemType],[System.State],[System.CreatedDate]" + 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}' " @@ -82,12 +81,11 @@ def cmd_list( organization: str, project: str, type: str, + show_state: bool, story_points: str = "", output_format: str = "table", ) -> None: - """ - Run `doing list` command. - """ + """Run `doing list` command.""" # Get config settings assignee = replace_user_aliases(assignee) author = replace_user_aliases(author) @@ -115,7 +113,17 @@ def cmd_list( query += '--status active --query "[].pullRequestId"' active_pullrequest_ids = run_command(query) - with Live(build_table(work_items, workitem_prs, iteration, False), refresh_per_second=4, console=console) as live: + with Live( + build_table( + work_items=work_items, + workitem_prs=workitem_prs, + iteration=iteration, + last_build=False, + show_state=show_state, + ), + refresh_per_second=4, + console=console, + ) as live: # For each PR, get linked work items. Note that "az repos pr list --include-links" does not work :( # Posted issue on bug here: https://github.com/Azure/azure-cli-extensions/issues/2946 @@ -129,21 +137,39 @@ def cmd_list( else: workitem_prs[work_item] = [str(pr_id)] - live.update(build_table(work_items, workitem_prs, iteration, False)) + live.update( + build_table( + work_items=work_items, + workitem_prs=workitem_prs, + iteration=iteration, + last_build=False, + show_state=show_state, + ) + ) - live.update(build_table(work_items, workitem_prs, iteration, last_build=True)) + live.update( + build_table( + work_items=work_items, + workitem_prs=workitem_prs, + iteration=iteration, + last_build=False, + show_state=show_state, + ) + ) -def build_table(work_items: List, workitem_prs: Dict, iteration: str, last_build: bool = False) -> Table: - """ - Build rich table with open issues. - """ +def build_table( + work_items: List, workitem_prs: Dict, iteration: str, show_state: bool, last_build: bool = False +) -> Table: + """Build rich table with open issues.""" # Create our table table = Table(title=f"Work-items in current iteration {iteration}") table.add_column("ID", justify="right", style="cyan", no_wrap=True) table.add_column("Title", justify="left", style="cyan", no_wrap=False) table.add_column("Assignee", justify="left", style="cyan", no_wrap=False) table.add_column("Type", justify="left", style="cyan", no_wrap=True) + if show_state: + table.add_column("State", justify="right", style="cyan", no_wrap=True) table.add_column("Created", justify="right", style="cyan", no_wrap=True) table.add_column("PRs", justify="right", style="cyan", no_wrap=True) @@ -157,6 +183,7 @@ def build_table(work_items: List, workitem_prs: Dict, iteration: str, last_build item_title = fields.get("System.Title") item_createdby = fields.get("System.AssignedTo", {}).get("displayName", "") item_type = fields.get("System.WorkItemType") + item_state = fields.get("System.State") # For example: # '2020-11-17T13:33:32.463Z' @@ -179,6 +206,9 @@ def build_table(work_items: List, workitem_prs: Dict, iteration: str, last_build item_linked_prs = "[bright_black]loading..[bright_black]" # TODO: If current git branch equal to branch ID name, different color. - table.add_row(str(item_id), item_title, item_createdby, item_type, item_datetime, item_linked_prs) + row = [str(item_id), item_title, item_createdby, item_type, item_datetime, item_linked_prs] + if show_state: + row.insert(4, item_state) + table.add_row(*row) return table diff --git a/src/doing/list/commands.py b/src/doing/list/commands.py index 03833ed..b46665c 100644 --- a/src/doing/list/commands.py +++ b/src/doing/list/commands.py @@ -79,10 +79,16 @@ help="Output format. 'table' has a rich display, 'array' will return a string list with ID's.", show_envvar=True, ) -def list(assignee, author, label, state, type, web, story_points, output_format): - """ - List issues related to the project. - """ +@click.option( + "--show_state/--no-show_state", + required=False, + default=True, + type=bool, + help="Show column with work item state.", + show_envvar=True, +) +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") @@ -101,12 +107,13 @@ def list(assignee, author, label, state, type, web, story_points, output_format) click.launch(f"{organization}/{project}/_workitems/?_a=query&wiql={quote(query)}") else: cmd_list( - assignee, - author, - label, - state, + assignee=assignee, + author=author, + label=label, + state=state, type=type, story_points=story_points, output_format=output_format, + show_state=show_state, **get_common_options(), ) diff --git a/tests/test_list.py b/tests/test_list.py new file mode 100644 index 0000000..ed4b718 --- /dev/null +++ b/tests/test_list.py @@ -0,0 +1,78 @@ +import pytest + +from doing.list._list import build_table + + +@pytest.fixture +def work_items(): + """Create a mock list of work items.""" + work_items = [ + { + "fields": { + "System.AssignedTo": { + "descriptor": "descriptor_mock_value", + "displayName": "John Doe", + "id": "id_mock_value", + "imageUrl": "https://imageUrl.mock.value", + "uniqueName": "john.doe@email.com", + "url": "https://url.mock.value", + }, + "System.CreatedDate": "2022-01-11T12:00:00.000Z", + "System.Id": 1, + "System.State": "Active", + "System.Title": "Mock User Story work item", + "System.WorkItemType": "User Story", + }, + "id": 1, + "relations": None, + "rev": 5, + "url": "https://dev.azure.com/mockProject/_apis/wit/workItems/1", + }, + { + "fields": { + "System.CreatedDate": "2022-02-15T12:00:00.000Z", + "System.Id": 2, + "System.State": "New", + "System.Title": "Mock Task work item", + "System.WorkItemType": "Task", + }, + "id": 2, + "relations": None, + "rev": 3, + "url": "https://dev.azure.com/mockProject/_apis/wit/workItems/2", + }, + ] + return work_items + + +@pytest.fixture +def workitem_prs(): + """Create a mock list of work items and their associated PRs.""" + workitem_prs = {1: ["3"], 2: ["4"]} + return workitem_prs + + +def test_build_table_show_state(work_items, workitem_prs): + """Test the show_state option of the build_table function.""" + iteration = "Test Iteration" + + # Generate table without state column + table = build_table( + work_items=work_items, workitem_prs=workitem_prs, iteration=iteration, last_build=False, show_state=False + ) + headers = [col.header for col in table.columns] + assert "State" not in headers + + # Generate table with state column + table = build_table( + work_items=work_items, workitem_prs=workitem_prs, iteration=iteration, last_build=False, show_state=True + ) + headers = [col.header for col in table.columns] + assert "State" in headers + + # Check if state column values are correct + state_col = [col for col in table.columns if col.header == "State"][0] + work_item_states = [] + for item in work_items: + work_item_states.append(item.get("fields").get("System.State")) + assert state_col._cells == work_item_states