Skip to content

Commit 4ca646d

Browse files
maddie-vargoJuliya Smithtimabrmsn
authored
Users command: option to report on legal hold membership (#290)
* Command option to report on user legal hold membership and tests * Fixing conlicts * fix style on users.py * updated branch to match main * Adding changes to meet tests comments * use create_mock_respose * finish using create_mock_response * black Co-authored-by: Juliya Smith <[email protected]> Co-authored-by: Tim Abramson <[email protected]>
1 parent fe448b1 commit 4ca646d

3 files changed

Lines changed: 188 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
1212

1313
### Added
1414

15+
- New option `--include-legal-hold-membership` on command `code42 users list` that includes the legal hold matter name and ID for any user on legal hold.
16+
1517
- New commands:
1618
- `code42 users deactivate`
1719
- `code42 users reactivate`

src/code42cli/cmds/users.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import click
22
from pandas import DataFrame
3+
from pandas import json_normalize
34

45
from code42cli.bulk import generate_template_cmd_factory
56
from code42cli.bulk import run_bulk_process
@@ -59,9 +60,17 @@ def username_option(help, required=False):
5960
@role_name_option("Limit results to only users having the specified role.")
6061
@active_option
6162
@inactive_option
63+
@click.option(
64+
"--include-legal-hold-membership",
65+
default=False,
66+
is_flag=True,
67+
help="Include legal hold membership in output.",
68+
)
6269
@format_option
6370
@sdk_options()
64-
def list_users(state, org_uid, role_name, active, inactive, format):
71+
def list_users(
72+
state, org_uid, role_name, active, inactive, include_legal_hold_membership, format
73+
):
6574
"""List users in your Code42 environment."""
6675
if inactive:
6776
active = False
@@ -72,6 +81,8 @@ def list_users(state, org_uid, role_name, active, inactive, format):
7281
else None
7382
)
7483
df = _get_users_dataframe(state.sdk, columns, org_uid, role_id, active)
84+
if include_legal_hold_membership:
85+
df = _add_legal_hold_membership_to_user_dataframe(state.sdk, df)
7586
if df.empty:
7687
click.echo("No results found.")
7788
else:
@@ -398,6 +409,44 @@ def _get_users_dataframe(sdk, columns, org_uid, role_id, active):
398409
return DataFrame.from_records(users_list, columns=columns)
399410

400411

412+
def _add_legal_hold_membership_to_user_dataframe(sdk, df):
413+
columns = ["legalHold.legalHoldUid", "legalHold.name", "user.userUid"]
414+
415+
custodians = list(_get_all_active_hold_memberships(sdk))
416+
if len(custodians) == 0:
417+
return df
418+
419+
legal_hold_member_dataframe = (
420+
json_normalize(custodians)[columns]
421+
.groupby(["user.userUid"])
422+
.agg(",".join)
423+
.rename(
424+
{
425+
"legalHold.legalHoldUid": "legalHoldUid",
426+
"legalHold.name": "legalHoldName",
427+
},
428+
axis=1,
429+
)
430+
)
431+
df = df.merge(
432+
legal_hold_member_dataframe,
433+
how="left",
434+
left_on="userUid",
435+
right_on="user.userUid",
436+
)
437+
438+
return df
439+
440+
441+
def _get_all_active_hold_memberships(sdk):
442+
for page in sdk.legalhold.get_all_matters(active=True):
443+
for matter in page["legalHolds"]:
444+
for _page in sdk.legalhold.get_all_matter_custodians(
445+
legal_hold_uid=matter["legalHoldUid"], active=True
446+
):
447+
yield from _page["legalHoldMemberships"]
448+
449+
401450
def _update_user(
402451
sdk,
403452
user_id,

tests/cmds/test_users.py

Lines changed: 136 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,42 @@
3535
}
3636
]
3737
}
38+
TEST_MATTER_RESPONSE = {
39+
"legalHolds": [
40+
{"legalHoldUid": "123456789", "name": "Legal Hold #1", "active": True},
41+
{"legalHoldUid": "987654321", "name": "Legal Hold #2", "active": True},
42+
]
43+
}
44+
TEST_CUSTODIANS_RESPONSE = {
45+
"legalHoldMemberships": [
46+
{
47+
"legalHoldMembershipUid": "99999",
48+
"active": True,
49+
"creationDate": "2020-07-16T08:50:23.405Z",
50+
"legalHold": {"legalHoldUid": "123456789", "name": "Legal Hold #1"},
51+
"user": {
52+
"userUid": "911162111513111325",
53+
"username": "[email protected]",
54+
"email": "[email protected]",
55+
"userExtRef": None,
56+
},
57+
},
58+
{
59+
"legalHoldMembershipUid": "11111",
60+
"active": True,
61+
"creationDate": "2020-07-16T08:50:23.405Z",
62+
"legalHold": {"legalHoldUid": "987654321", "name": "Legal Hold #2"},
63+
"user": {
64+
"userUid": "911162111513111325",
65+
"username": "[email protected]",
66+
"email": "[email protected]",
67+
"userExtRef": None,
68+
},
69+
},
70+
]
71+
}
72+
TEST_EMPTY_CUSTODIANS_RESPONSE = {"legalHoldMemberships": []}
73+
TEST_EMPTY_MATTERS_RESPONSE = {"legalHolds": []}
3874
TEST_EMPTY_USERS_RESPONSE = {"users": []}
3975
TEST_USERNAME = TEST_USERS_RESPONSE["users"][0]["username"]
4076
TEST_USER_ID = TEST_USERS_RESPONSE["users"][0]["userId"]
@@ -69,10 +105,6 @@
69105
}
70106

71107

72-
def get_all_users_generator():
73-
yield TEST_USERS_RESPONSE
74-
75-
76108
@pytest.fixture
77109
def update_user_response(mocker):
78110
return create_mock_response(mocker)
@@ -104,7 +136,10 @@ def get_org_success(cli_state, get_org_response):
104136

105137

106138
@pytest.fixture
107-
def get_all_users_success(cli_state):
139+
def get_all_users_success(mocker, cli_state):
140+
def get_all_users_generator():
141+
yield create_mock_response(mocker, data=TEST_USERS_RESPONSE)
142+
108143
cli_state.sdk.users.get_all.return_value = get_all_users_generator()
109144

110145

@@ -121,6 +156,42 @@ def get_user_id_failure(mocker, cli_state):
121156
)
122157

123158

159+
@pytest.fixture
160+
def get_custodian_failure(mocker, cli_state):
161+
def empty_custodian_list_generator():
162+
yield create_mock_response(mocker, data=TEST_EMPTY_CUSTODIANS_RESPONSE)
163+
164+
cli_state.sdk.legalhold.get_all_matter_custodians.return_value = (
165+
empty_custodian_list_generator()
166+
)
167+
168+
169+
@pytest.fixture
170+
def get_matter_failure(mocker, cli_state):
171+
def empty_matter_list_generator():
172+
yield create_mock_response(mocker, data=TEST_EMPTY_MATTERS_RESPONSE)
173+
174+
cli_state.sdk.legalhold.get_all_matters.return_value = empty_matter_list_generator()
175+
176+
177+
@pytest.fixture
178+
def get_all_matter_success(mocker, cli_state):
179+
def matter_list_generator():
180+
yield create_mock_response(mocker, data=TEST_MATTER_RESPONSE)
181+
182+
cli_state.sdk.legalhold.get_all_matters.return_value = matter_list_generator()
183+
184+
185+
@pytest.fixture
186+
def get_all_custodian_success(mocker, cli_state):
187+
def custodian_list_generator():
188+
yield create_mock_response(mocker, data=TEST_CUSTODIANS_RESPONSE)
189+
190+
cli_state.sdk.legalhold.get_all_matter_custodians.return_value = (
191+
custodian_list_generator()
192+
)
193+
194+
124195
@pytest.fixture
125196
def get_available_roles_success(cli_state, get_available_roles_response):
126197
cli_state.sdk.users.get_available_roles.return_value = get_available_roles_response
@@ -259,6 +330,66 @@ def test_list_users_when_given_excluding_active_and_inactive_uses_active_equals_
259330
)
260331

261332

333+
def test_list_legal_hold_flag_reports_none_for_users_not_on_legal_hold(
334+
runner,
335+
cli_state,
336+
get_all_users_success,
337+
get_custodian_failure,
338+
get_all_matter_success,
339+
):
340+
result = runner.invoke(
341+
cli,
342+
["users", "list", "--include-legal-hold-membership", "-f", "CSV"],
343+
obj=cli_state,
344+
)
345+
346+
assert "Legal Hold #1,Legal Hold #2" not in result.output
347+
assert "123456789,987654321" not in result.output
348+
assert "legalHoldUid" not in result.output
349+
assert "[email protected]" in result.output
350+
351+
352+
def test_list_legal_hold_flag_reports_none_if_no_matters_exist(
353+
runner, cli_state, get_all_users_success, get_custodian_failure, get_matter_failure
354+
):
355+
result = runner.invoke(
356+
cli, ["users", "list", "--include-legal-hold-membership"], obj=cli_state
357+
)
358+
359+
assert "Legal Hold #1,Legal Hold #2" not in result.output
360+
assert "123456789,987654321" not in result.output
361+
assert "legalHoldUid" not in result.output
362+
assert "[email protected]" in result.output
363+
364+
365+
def test_list_legal_hold_values_not_included_for_legal_hold_user_if_legal_hold_flag_not_passed(
366+
runner,
367+
cli_state,
368+
get_all_users_success,
369+
get_all_custodian_success,
370+
get_all_matter_success,
371+
):
372+
result = runner.invoke(cli, ["users", "list"], obj=cli_state)
373+
assert "Legal Hold #1,Legal Hold #2" not in result.output
374+
assert "123456789,987654321" not in result.output
375+
assert "[email protected]" in result.output
376+
377+
378+
def test_list_include_legal_hold_membership_merges_in_and_concats_legal_hold_info(
379+
runner,
380+
cli_state,
381+
get_all_users_success,
382+
get_all_custodian_success,
383+
get_all_matter_success,
384+
):
385+
result = runner.invoke(
386+
cli, ["users", "list", "--include-legal-hold-membership"], obj=cli_state
387+
)
388+
389+
assert "Legal Hold #1,Legal Hold #2" in result.output
390+
assert "123456789,987654321" in result.output
391+
392+
262393
def test_add_user_role_adds(
263394
runner, cli_state, get_user_id_success, get_available_roles_success
264395
):

0 commit comments

Comments
 (0)