Skip to content

Commit

Permalink
Boards Iterations and Areas Unit tests and Backlog/Default team itera…
Browse files Browse the repository at this point in the history
…tion commands (Azure#663)

* Fix python versions in Mac machines

* UTs for Project iterations

* UTs

* UTs for Project Area commands

* UTs for Team area commands

* Unwanted import

* Bug bash fix: Change from relative to absolute path

* Absolute path in iterations

* Add commands to configure default iteration and backlog iteration

* Change help text for update commands

* Minor help text changes

* Changes in default area

* Hanlde empty backlog iteration ID error and adding a troubleshotting help page

* Fix UTs

* Pylint fixes

* Flake fixes

* Additional tests for Backlog/default and list work items command

* Renaming test helper file
  • Loading branch information
ishitam8 authored and gauravsaralMs committed Jul 10, 2019
1 parent 38fb2cf commit 7ac56fc
Show file tree
Hide file tree
Showing 12 changed files with 766 additions and 42 deletions.
52 changes: 44 additions & 8 deletions azure-devops/azext_devops/dev/boards/_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,54 @@ def _transform_team_iteration_row(row):
table_row = OrderedDict()
table_row['ID'] = row['id']
table_row['Name'] = row['name']
if row['attributes']['startDate'] is None:
table_row['Start Date'] = ''
else:
table_row['Start Date'] = row['attributes']['startDate']
if row['attributes']['finishDate'] is None:
table_row['Finish Date'] = ''
else:
table_row['Finish Date'] = row['attributes']['finishDate']
if row['attributes']:
if row['attributes']['startDate'] is None:
table_row['Start Date'] = ''
else:
table_row['Start Date'] = row['attributes']['startDate']
if row['attributes']['finishDate'] is None:
table_row['Finish Date'] = ''
else:
table_row['Finish Date'] = row['attributes']['finishDate']
if 'timeFrame' in row['attributes']:
table_row['Time Frame'] = row['attributes']['timeFrame']
table_row['Path'] = row['path']
return table_row


def transform_work_item_team_iteration_work_items(result):
table_output = []
for item in result['workItemRelations']:
table_output.append(_transform_team_iteration_work_item_row(item))
return table_output


def _transform_team_iteration_work_item_row(row):
table_row = OrderedDict()
if row['source']:
table_row['Source'] = row['source']['id']
if row['target']:
table_row['Target'] = row['target']['id']
table_row['Relation Type'] = row['rel']
return table_row


def transform_work_item_team_default_iteration_table_output(result):
table_output = []
table_row = OrderedDict()
if result['defaultIteration']:
table_row = _transform_team_iteration_row(result['defaultIteration'])
table_row['Default Iteration Macro'] = result['defaultIterationMacro']
table_output.append(table_row)
return table_output


def transform_work_item_team_backlog_iteration_table_output(result):
table_output = []
table_output.append(_transform_team_iteration_row(result['backlogIteration']))
return table_output


def transform_work_item_project_classification_nodes_table_output(response):
table_op = []
table_op = transform_work_item_project_classification_nodes_table_output_recursive(response, table_op)
Expand Down
27 changes: 27 additions & 0 deletions azure-devops/azext_devops/dev/boards/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ def load_boards_help():
long-summary:
"""

helps['boards iteration project update'] = """
type: command
long-summary: Move iteration or update iteration details like name AND/OR start-date and finish-date.
"""

helps['boards area'] = """
type: group
short-summary: Manage area paths.
Expand All @@ -55,12 +60,34 @@ def load_boards_help():
long-summary:
"""

helps['boards area project update'] = """
type: command
long-summary: Move area or update area name.
"""

helps['boards area team'] = """
type: group
short-summary: Manage areas for a team.
long-summary:
"""

helps['boards area team update'] = """
type: command
long-summary: Update any area to include/exclude sub areas OR Set already added area as default.
"""

helps['boards area team add'] = """
type: command
long-summary: Every team needs to have a default area configured which can't be empty.
Hence, you need to pass --set-as-default while adding first area to your team.
You can later configure any other area which already added to team as default
by using `az boards area team update -h` command.
examples:
- name: Add area to a team.
text: |
az boards area team --team 'ContosoTeam' --path '\\ContosoProject\\MyProjectAreaName'
"""

helps['boards work-item relation'] = """
type: group
short-summary: Manage work item relations.
Expand Down
25 changes: 17 additions & 8 deletions azure-devops/azext_devops/dev/boards/area.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@
from azext_devops.dev.common.services import (resolve_instance_and_project,
get_work_item_tracking_client,
get_work_client)

from .boards_helper import resolve_classification_node_path
_STRUCTURE_GROUP_AREA = 'areas'


def get_project_areas(depth=1, path=None, organization=None, project=None, detect=None):
"""List areas for a project.
:param depth: Depth of child nodes to be fetched.
:param depth: Depth of child nodes to be fetched. Example: --depth 3
:type depth: int
"""
organization, project = resolve_instance_and_project(detect=detect,
organization=organization,
project=project)
client = get_work_item_tracking_client(organization)
if path:
path = resolve_classification_node_path(client, path, project, _STRUCTURE_GROUP_AREA)
list_of_areas = client.get_classification_node(project=project,
structure_group=_STRUCTURE_GROUP_AREA,
depth=depth, path=path)
Expand All @@ -37,6 +39,7 @@ def delete_project_area(path, organization=None, project=None, detect=None):
organization=organization,
project=project)
client = get_work_item_tracking_client(organization)
path = resolve_classification_node_path(client, path, project, _STRUCTURE_GROUP_AREA)
response = client.delete_classification_node(project=project,
structure_group=_STRUCTURE_GROUP_AREA,
path=path)
Expand All @@ -52,6 +55,8 @@ def create_project_area(name, path=None, organization=None, project=None, detect
organization=organization,
project=project)
client = get_work_item_tracking_client(organization)
if path:
path = resolve_classification_node_path(client, path, project, _STRUCTURE_GROUP_AREA)
classification_node_object = WorkItemClassificationNode()
classification_node_object.name = name
response = client.create_or_update_classification_node(project=project,
Expand All @@ -76,8 +81,8 @@ def get_project_area(id, organization=None, project=None, detect=None): # pylin
return response


def update_project_area(path=None, name=None, child_id=None, organization=None, project=None, detect=None):
"""Move area or update area name.
def update_project_area(path, name=None, child_id=None, organization=None, project=None, detect=None):
"""Update area.
:param name: New name of the area.
:type: str
:param child_id: Move an existing area and add as child node for this area.
Expand All @@ -89,6 +94,7 @@ def update_project_area(path=None, name=None, child_id=None, organization=None,
organization=organization,
project=project)
client = get_work_item_tracking_client(organization)
path = resolve_classification_node_path(client, path, project, _STRUCTURE_GROUP_AREA)
if child_id:
move_classification_node_object = WorkItemClassificationNode()
move_classification_node_object.id = child_id
Expand Down Expand Up @@ -123,14 +129,13 @@ def get_team_areas(team, organization=None, project=None, detect=None):
def add_team_area(path, team, set_as_default=False, include_sub_areas=None,
organization=None, project=None, detect=None):
"""Add area to a team.
:param set_as_default: Set this area path as default area for this team.
:param set_as_default: Set this area path as default area for this team. Default: False
:type set_as_default: bool
"""
organization, project = resolve_instance_and_project(detect=detect,
organization=organization,
project=project)
client = get_work_client(organization)

team_context = TeamContext(project=project, team=team)
get_response = client.get_team_field_values(team_context=team_context)
patch_doc = TeamFieldValuesPatch()
Expand Down Expand Up @@ -159,9 +164,13 @@ def remove_team_area(path, team, organization=None, project=None, detect=None):
if get_response.default_value == path:
raise CLIError('You are trying to remove the default area for this team. '
'Please change the default area node and then try this command again.')
area_found = False
for entry in get_response.values:
if path == entry.value[:]:
area_found = True
get_response.values.remove(entry)
if not area_found:
raise CLIError('Path is not added to team area list.')
patch_doc = TeamFieldValuesPatch()
patch_doc.values = get_response.values
patch_doc.default_value = get_response.default_value
Expand All @@ -171,8 +180,8 @@ def remove_team_area(path, team, organization=None, project=None, detect=None):

def update_team_area(path, team, include_sub_areas=None, set_as_default=False,
organization=None, project=None, detect=None):
"""Update any area to include/exclude sub areas OR Set already added area as default.
:param default_area:set_as_default: Set as default team area path.
"""Update team area.
:param set_as_default: Set as default team area path. Default: False
:type set_as_default: bool
"""
if include_sub_areas is None and set_as_default is False:
Expand Down
14 changes: 10 additions & 4 deletions azure-devops/azext_devops/dev/boards/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def load_work_arguments(self, _):
Multiple values can be passed comma separated. Example: 1,2 ')

with self.argument_context('boards iteration project') as context:
context.argument('path', help='Iteration path.')
context.argument('path', help='Absolute path of an iteration. '
'Example:' + r'\ProjectName\Iteration\IterationName')
context.argument('start_date',
help='Start date of the iteration. Example : "2019-06-03"')
context.argument('finish_date',
Expand All @@ -46,15 +47,20 @@ def load_work_arguments(self, _):
context.argument('id', type=int)

with self.argument_context('boards iteration project create') as context:
context.argument('path', help='Iteration path. Creates an iteration at root level if --path is not specified.')
context.argument('path', help='Absolute path of an iteration. '
'Creates an iteration at root level if --path is not specified. '
'Example:' + r'\ProjectName\Iteration\IterationName.')

with self.argument_context('boards area') as context:
context.argument('path', help='Area path.')
context.argument('path', help='Absolute path of an area. Example:' + r'\ProjectName\Area\AreaName')

with self.argument_context('boards area project create') as context:
context.argument('path', help='Area path. Creates an area at root level if --path is not specified.')
context.argument('path', help='Absolute path of an area. '
'Creates an area at root level if --path is not specified. '
'Example:' + r'\ProjectName\Area\AreaName.')

with self.argument_context('boards area team') as context:
context.argument('team', help='The name or id of the team.')
context.argument('include_sub_areas', arg_type=get_three_state_flag(),
help='Include child nodes of this area.')
context.argument('path', help='Area path. Example:' + r'\ProjectName\AreaName')
18 changes: 18 additions & 0 deletions azure-devops/azext_devops/dev/boards/boards_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from knack.util import CLIError


def resolve_classification_node_path(client, path, project, structure_group):
get_root_node = client.get_root_nodes(project=project, depth=0)
root_node_path = None
for entry in get_root_node:
if entry.structure_type == structure_group[:-1]:
root_node_path = entry.additional_properties['path']
if root_node_path and path.lower().startswith(root_node_path.lower()):
updated_path = path[len(root_node_path):]
return updated_path
raise CLIError("--path parameter is expected to be absolute path.")
16 changes: 14 additions & 2 deletions azure-devops/azext_devops/dev/boards/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
transform_work_item_relations,
transform_work_item_team_iterations_table_output,
transform_work_item_team_iteration_table_output,
transform_work_item_team_iteration_work_items,
transform_work_item_team_default_iteration_table_output,
transform_work_item_team_backlog_iteration_table_output,
transform_work_item_project_classification_nodes_table_output,
transform_work_item_project_classification_node_table_output,
transform_work_item_team_areas_table_output)
Expand Down Expand Up @@ -62,7 +65,16 @@ def load_work_commands(self, _):
with self.command_group('boards iteration team', command_type=workProjectAndTeamIterationOps) as g:
# team iteration commands
g.command('list', 'get_team_iterations', table_transformer=transform_work_item_team_iterations_table_output)
g.command('show', 'get_team_iteration', table_transformer=transform_work_item_team_iteration_table_output)
g.command('list-work-items', 'list_iteration_work_items',
table_transformer=transform_work_item_team_iteration_work_items)
g.command('set-default-iteration', 'set_default_iteration',
table_transformer=transform_work_item_team_default_iteration_table_output)
g.command('set-backlog-iteration', 'set_backlog_iteration',
table_transformer=transform_work_item_team_backlog_iteration_table_output)
g.command('show-default-iteration', 'show_default_iteration',
table_transformer=transform_work_item_team_default_iteration_table_output)
g.command('show-backlog-iteration', 'show_backlog_iteration',
table_transformer=transform_work_item_team_backlog_iteration_table_output)
g.command('remove', 'delete_team_iteration', table_transformer=transform_work_item_team_iteration_table_output)
g.command('add', 'post_team_iteration', table_transformer=transform_work_item_team_iteration_table_output)

Expand All @@ -75,7 +87,7 @@ def load_work_commands(self, _):
g.command('delete', 'delete_project_iteration',
confirmation='Are you sure you want to delete this iteration?')
g.command('show', 'get_project_iteration',
table_transformer=transform_work_item_project_classification_nodes_table_output)
table_transformer=transform_work_item_project_classification_node_table_output)
g.command('create', 'create_project_iteration',
table_transformer=transform_work_item_project_classification_nodes_table_output)

Expand Down
Loading

0 comments on commit 7ac56fc

Please sign in to comment.