Merge "Implements subtree_as_ids query param"
This commit is contained in:
commit
cb0f9a1986
@ -425,6 +425,8 @@ class ProjectV3(controller.V3Controller):
|
||||
|
||||
subtree_as_list = 'subtree_as_list' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_list']))
|
||||
subtree_as_ids = 'subtree_as_ids' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_ids']))
|
||||
|
||||
# parents_as_list and parents_as_ids are mutually exclusive
|
||||
if parents_as_list and parents_as_ids:
|
||||
@ -432,6 +434,12 @@ class ProjectV3(controller.V3Controller):
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
# subtree_as_list and subtree_as_ids are mutually exclusive
|
||||
if subtree_as_list and subtree_as_ids:
|
||||
msg = _('Cannot use subtree_as_list and subtree_as_ids query '
|
||||
'params at the same time.')
|
||||
raise exception.ValidationError(msg)
|
||||
|
||||
user_id = self.get_auth_context(context).get('user_id')
|
||||
|
||||
if parents_as_list:
|
||||
@ -447,6 +455,9 @@ class ProjectV3(controller.V3Controller):
|
||||
ref['id'], user_id)
|
||||
ref['subtree'] = [ProjectV3.wrap_member(context, p)
|
||||
for p in subtree]
|
||||
elif subtree_as_ids:
|
||||
ref['subtree'] = self.resource_api.get_projects_in_subtree_as_ids(
|
||||
ref['id'])
|
||||
|
||||
@controller.protected()
|
||||
def get_project(self, context, project_id):
|
||||
|
@ -281,6 +281,65 @@ class Manager(manager.Manager):
|
||||
self._filter_projects_list(subtree, user_id)
|
||||
return subtree
|
||||
|
||||
def _build_subtree_as_ids_dict(self, project_id, subtree_by_parent):
|
||||
# NOTE(rodrigods): we perform a depth first search to construct the
|
||||
# dictionaries representing each level of the subtree hierarchy. In
|
||||
# order to improve this traversal performance, we create a cache of
|
||||
# projects (subtree_py_parent) that accesses in constant time the
|
||||
# direct children of a given project.
|
||||
def traverse_subtree_hierarchy(project_id):
|
||||
children = subtree_by_parent.get(project_id)
|
||||
if not children:
|
||||
return None
|
||||
|
||||
children_ids = {}
|
||||
for child in children:
|
||||
children_ids[child['id']] = traverse_subtree_hierarchy(
|
||||
child['id'])
|
||||
return children_ids
|
||||
|
||||
return traverse_subtree_hierarchy(project_id)
|
||||
|
||||
def get_projects_in_subtree_as_ids(self, project_id):
|
||||
"""Gets the IDs from the projects in the subtree from a given project.
|
||||
|
||||
The project IDs are returned as a structured dictionary representing
|
||||
their hierarchy. For example, considering the following project
|
||||
hierarchy::
|
||||
|
||||
A
|
||||
|
|
||||
+-B-+
|
||||
| |
|
||||
C D
|
||||
|
||||
If we query for project A subtree, the expected return is the following
|
||||
dictionary::
|
||||
|
||||
'subtree': {
|
||||
B['id']: {
|
||||
C['id']: None,
|
||||
D['id']: None
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
def _projects_indexed_by_parent(projects_list):
|
||||
projects_by_parent = {}
|
||||
for proj in projects_list:
|
||||
parent_id = proj.get('parent_id')
|
||||
if parent_id:
|
||||
if parent_id in projects_by_parent:
|
||||
projects_by_parent[parent_id].append(proj)
|
||||
else:
|
||||
projects_by_parent[parent_id] = [proj]
|
||||
return projects_by_parent
|
||||
|
||||
subtree_list = self.list_projects_in_subtree(project_id)
|
||||
subtree_as_ids = self._build_subtree_as_ids_dict(
|
||||
project_id, _projects_indexed_by_parent(subtree_list))
|
||||
return subtree_as_ids
|
||||
|
||||
@cache.on_arguments(should_cache_fn=SHOULD_CACHE,
|
||||
expiration_time=EXPIRATION_TIME)
|
||||
def get_domain(self, domain_id):
|
||||
|
@ -572,7 +572,98 @@ class AssignmentTestCase(test_v3.RestfulTestCase):
|
||||
'project_id': projects[1]['project']['id']},
|
||||
expected_status=400)
|
||||
|
||||
def test_get_project_with_subtree_list(self):
|
||||
def test_get_project_with_subtree_as_ids(self):
|
||||
"""Call ``GET /projects/{project_id}?subtree_as_ids``.
|
||||
|
||||
This test creates a more complex hierarchy to test if the structured
|
||||
dictionary returned by using the ``subtree_as_ids`` query param
|
||||
correctly represents the hierarchy.
|
||||
|
||||
The hierarchy contains 5 projects with the following structure::
|
||||
|
||||
+--A--+
|
||||
| |
|
||||
+--B--+ C
|
||||
| |
|
||||
D E
|
||||
|
||||
|
||||
"""
|
||||
projects = self._create_projects_hierarchy(hierarchy_size=2)
|
||||
|
||||
# Add another child to projects[0] - it will be projects[3]
|
||||
new_ref = self.new_project_ref(
|
||||
domain_id=self.domain_id,
|
||||
parent_id=projects[0]['project']['id'])
|
||||
resp = self.post('/projects',
|
||||
body={'project': new_ref})
|
||||
self.assertValidProjectResponse(resp, new_ref)
|
||||
projects.append(resp.result)
|
||||
|
||||
# Add another child to projects[1] - it will be projects[4]
|
||||
new_ref = self.new_project_ref(
|
||||
domain_id=self.domain_id,
|
||||
parent_id=projects[1]['project']['id'])
|
||||
resp = self.post('/projects',
|
||||
body={'project': new_ref})
|
||||
self.assertValidProjectResponse(resp, new_ref)
|
||||
projects.append(resp.result)
|
||||
|
||||
# Query for projects[0] subtree_as_ids
|
||||
r = self.get(
|
||||
'/projects/%(project_id)s?subtree_as_ids' % {
|
||||
'project_id': projects[0]['project']['id']})
|
||||
self.assertValidProjectResponse(r, projects[0]['project'])
|
||||
subtree_as_ids = r.result['project']['subtree']
|
||||
|
||||
# The subtree hierarchy from projects[0] should have the following
|
||||
# structure:
|
||||
# {
|
||||
# projects[1]: {
|
||||
# projects[2]: None,
|
||||
# projects[4]: None
|
||||
# },
|
||||
# projects[3]: None
|
||||
# }
|
||||
expected_dict = {
|
||||
projects[1]['project']['id']: {
|
||||
projects[2]['project']['id']: None,
|
||||
projects[4]['project']['id']: None
|
||||
},
|
||||
projects[3]['project']['id']: None
|
||||
}
|
||||
self.assertDictEqual(expected_dict, subtree_as_ids)
|
||||
|
||||
# Now query for projects[1] subtree_as_ids
|
||||
r = self.get(
|
||||
'/projects/%(project_id)s?subtree_as_ids' % {
|
||||
'project_id': projects[1]['project']['id']})
|
||||
self.assertValidProjectResponse(r, projects[1]['project'])
|
||||
subtree_as_ids = r.result['project']['subtree']
|
||||
|
||||
# The subtree hierarchy from projects[1] should have the following
|
||||
# structure:
|
||||
# {
|
||||
# projects[2]: None,
|
||||
# projects[4]: None
|
||||
# }
|
||||
expected_dict = {
|
||||
projects[2]['project']['id']: None,
|
||||
projects[4]['project']['id']: None
|
||||
}
|
||||
self.assertDictEqual(expected_dict, subtree_as_ids)
|
||||
|
||||
# Now query for projects[3] subtree_as_ids
|
||||
r = self.get(
|
||||
'/projects/%(project_id)s?subtree_as_ids' % {
|
||||
'project_id': projects[3]['project']['id']})
|
||||
self.assertValidProjectResponse(r, projects[3]['project'])
|
||||
subtree_as_ids = r.result['project']['subtree']
|
||||
|
||||
# projects[3] has no subtree, subtree_as_ids must be None
|
||||
self.assertIsNone(subtree_as_ids)
|
||||
|
||||
def test_get_project_with_subtree_as_list(self):
|
||||
"""Call ``GET /projects/{project_id}?subtree_as_list``."""
|
||||
projects = self._create_projects_hierarchy(hierarchy_size=2)
|
||||
|
||||
@ -585,6 +676,17 @@ class AssignmentTestCase(test_v3.RestfulTestCase):
|
||||
self.assertNotIn(projects[0], r.result['project']['subtree'])
|
||||
self.assertIn(projects[2], r.result['project']['subtree'])
|
||||
|
||||
def test_get_project_with_subtree_as_list_and_subtree_as_ids(self):
|
||||
"""Call ``GET /projects/{project_id}?subtree_as_list&subtree_as_ids``.
|
||||
|
||||
"""
|
||||
projects = self._create_projects_hierarchy(hierarchy_size=2)
|
||||
|
||||
self.get(
|
||||
'/projects/%(project_id)s?subtree_as_list&subtree_as_ids' % {
|
||||
'project_id': projects[1]['project']['id']},
|
||||
expected_status=400)
|
||||
|
||||
def test_update_project(self):
|
||||
"""Call ``PATCH /projects/{project_id}``."""
|
||||
ref = self.new_project_ref(domain_id=self.domain_id)
|
||||
|
Loading…
Reference in New Issue
Block a user