-
Notifications
You must be signed in to change notification settings - Fork 13
Add events command to legal-hold #264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b285a68
ed72879
ea4381a
a0ebe57
077dea1
03a3814
f174992
205d559
02d6530
e5784cf
9bfcd42
2e303de
94fe3b9
0b79911
1bd1e2f
e4725c7
237ea31
7212fbf
2a917bd
2d1db8c
9a0afcb
a3dd28f
9677f23
c328588
2764bf0
537289f
2ab1ae0
73daebb
d8921c5
285a1ec
5e49055
e7d6a5d
8e55a4a
693126f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import json | ||
from collections import OrderedDict | ||
from functools import lru_cache | ||
from pprint import pformat | ||
|
||
|
@@ -14,33 +13,52 @@ | |
from code42cli.file_readers import read_csv_arg | ||
from code42cli.options import format_option | ||
from code42cli.options import sdk_options | ||
from code42cli.options import set_begin_default_dict | ||
from code42cli.options import set_end_default_dict | ||
from code42cli.output_formats import OutputFormat | ||
from code42cli.output_formats import OutputFormatter | ||
from code42cli.util import format_string_list_to_columns | ||
|
||
|
||
_MATTER_KEYS_MAP = OrderedDict() | ||
_MATTER_KEYS_MAP["legalHoldUid"] = "Matter ID" | ||
_MATTER_KEYS_MAP["name"] = "Name" | ||
_MATTER_KEYS_MAP["description"] = "Description" | ||
_MATTER_KEYS_MAP["creator_username"] = "Creator" | ||
_MATTER_KEYS_MAP["creationDate"] = "Creation Date" | ||
_MATTER_KEYS_MAP = { | ||
"legalHoldUid": "Matter ID", | ||
"name": "Name", | ||
"description": "Description", | ||
"creator_username": "Creator", | ||
"creationDate": "Creation Date", | ||
} | ||
_EVENT_KEYS_MAP = { | ||
"eventUid": "Event ID", | ||
"eventType": "Event Type", | ||
"eventDate": "Event Date", | ||
"legalHoldUid": "Legal Hold ID", | ||
"actorUsername": "Actor Username", | ||
"custodianUsername": "Custodian Username", | ||
} | ||
LEGAL_HOLD_KEYWORD = "legal hold events" | ||
LEGAL_HOLD_EVENT_TYPES = [ | ||
"MembershipCreated", | ||
"MembershipReactivated", | ||
"MembershipDeactivated", | ||
"HoldCreated", | ||
"HoldDeactivated", | ||
"HoldReactivated", | ||
"Restore", | ||
] | ||
BEGIN_DATE_DICT = set_begin_default_dict(LEGAL_HOLD_KEYWORD) | ||
END_DATE_DICT = set_end_default_dict(LEGAL_HOLD_KEYWORD) | ||
|
||
|
||
@click.group(cls=OrderedGroup) | ||
@sdk_options(hidden=True) | ||
def legal_hold(state): | ||
"""Add and remove custodians from legal hold matters.""" | ||
pass | ||
|
||
|
||
matter_id_option = click.option( | ||
"-m", | ||
"--matter-id", | ||
required=True, | ||
type=str, | ||
help="Identification number of the legal hold matter the custodian will be added to.", | ||
) | ||
def matter_id_option(required, help): | ||
return click.option("-m", "--matter-id", required=required, type=str, help=help) | ||
|
||
|
||
user_id_option = click.option( | ||
"-u", | ||
"--username", | ||
|
@@ -51,7 +69,10 @@ def legal_hold(state): | |
|
||
|
||
@legal_hold.command() | ||
@matter_id_option | ||
@matter_id_option( | ||
True, | ||
"Identification number of the legal hold matter the custodian will be added to.", | ||
) | ||
@user_id_option | ||
@sdk_options() | ||
def add_user(state, matter_id, username): | ||
|
@@ -60,7 +81,10 @@ def add_user(state, matter_id, username): | |
|
||
|
||
@legal_hold.command() | ||
@matter_id_option | ||
@matter_id_option( | ||
True, | ||
"Identification number of the legal hold matter the custodian will be removed from.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks good, thanks! |
||
) | ||
@user_id_option | ||
@sdk_options() | ||
def remove_user(state, matter_id, username): | ||
|
@@ -124,6 +148,30 @@ def show(state, matter_id, include_inactive=False, include_policy=False): | |
echo("") | ||
|
||
|
||
@legal_hold.command() | ||
@matter_id_option(False, "Filter results by legal hold UID.") | ||
@click.option( | ||
"--event-type", | ||
type=click.Choice(LEGAL_HOLD_EVENT_TYPES), | ||
help="Filter results by event types.", | ||
) | ||
@click.option("--begin", **BEGIN_DATE_DICT) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should use the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I avoided this option initially because the Also, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should implement the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh! @maddie-vargo Sorry, I did forget about that when I made my initial comments. I do like the idea from @timabrmsn, supporting checkingpointing would be a nice feature. We can always add it later though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @unparalleled-js would it be possible to use the I left checkpoint'ing off for now. |
||
@click.option("--end", **END_DATE_DICT) | ||
@format_option | ||
@sdk_options() | ||
def search_events(state, matter_id, event_type, begin, end, format): | ||
"""Tools for getting legal hold event data.""" | ||
formatter = OutputFormatter(format, _EVENT_KEYS_MAP) | ||
events = _get_all_events(state.sdk, matter_id, begin, end) | ||
if event_type: | ||
events = [event for event in events if event["eventType"] == event_type] | ||
if len(events) > 10: | ||
output = formatter.get_formatted_output(events) | ||
click.echo_via_pager(output) | ||
else: | ||
formatter.echo_formatted_list(events) | ||
maddie-vargo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
@legal_hold.group(cls=OrderedGroup) | ||
@sdk_options(hidden=True) | ||
def bulk(state): | ||
|
@@ -230,6 +278,14 @@ def _get_all_active_matters(sdk): | |
return matters | ||
|
||
|
||
def _get_all_events(sdk, legal_hold_uid, begin_date, end_date): | ||
events_generator = sdk.legalhold.get_all_events( | ||
legal_hold_uid, begin_date, end_date | ||
) | ||
events = [event for page in events_generator for event in page["legalHoldEvents"]] | ||
return events | ||
|
||
|
||
def _print_matter_members(username_list, member_type="active"): | ||
if username_list: | ||
echo("\n{} matter members:\n".format(member_type.capitalize())) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import datetime | ||
|
||
import pytest | ||
from py42.exceptions import Py42BadRequestError | ||
from py42.response import Py42Response | ||
|
@@ -6,9 +8,9 @@ | |
|
||
from code42cli import PRODUCT_NAME | ||
from code42cli.cmds.legal_hold import _check_matter_is_accessible | ||
from code42cli.date_helper import convert_datetime_to_timestamp | ||
from code42cli.main import cli | ||
|
||
|
||
_NAMESPACE = "{}.cmds.legal_hold".format(PRODUCT_NAME) | ||
TEST_MATTER_ID = "99999" | ||
TEST_LEGAL_HOLD_MEMBERSHIP_UID = "88888" | ||
|
@@ -18,6 +20,8 @@ | |
INACTIVE_TEST_USERNAME = "[email protected]" | ||
INACTIVE_TEST_USER_ID = "54321" | ||
TEST_POLICY_UID = "66666" | ||
_CREATE_EVENT_ID = "564564654566" | ||
_MEMBERSHIP_EVENT_ID = "74533457745" | ||
TEST_PRESERVATION_POLICY_UID = "1010101010" | ||
MATTER_RESPONSE = """ | ||
{ | ||
|
@@ -169,6 +173,42 @@ | |
] | ||
} | ||
""" | ||
TEST_EVENT_PAGE = { | ||
"legalHoldEvents": [ | ||
{ | ||
"eventUid": "564564654566", | ||
"eventType": "HoldCreated", | ||
"eventDate": "2015-05-16T15:07:44.820Z", | ||
"legalHoldUid": "88888", | ||
"actorUserUid": "12345", | ||
"actorUsername": "[email protected]", | ||
"actorFirstName": "john", | ||
"actorLastName": "doe", | ||
"actorUserExtRef": None, | ||
"actorEmail": "[email protected]", | ||
}, | ||
{ | ||
"eventUid": "74533457745", | ||
"eventType": "MembershipCreated", | ||
"eventDate": "2019-05-17T15:07:44.820Z", | ||
"legalHoldUid": "88888", | ||
"legalHoldMembershipUid": "645576514441664433", | ||
"custodianUserUid": "12345", | ||
"custodianUsername": "[email protected]", | ||
"custodianFirstName": "kim", | ||
"custodianLastName": "jones", | ||
"custodianUserExtRef": None, | ||
"custodianEmail": "[email protected]", | ||
"actorUserUid": "1234512345", | ||
"actorUsername": "[email protected]", | ||
"actorFirstName": "john", | ||
"actorLastName": "doe", | ||
"actorUserExtRef": None, | ||
"actorEmail": "[email protected]", | ||
}, | ||
] | ||
} | ||
EMPTY_EVENTS_RESPONSE = """{"legalHoldEvents": []}""" | ||
EMPTY_MATTERS_RESPONSE = """{"legalHolds": []}""" | ||
ALL_MATTERS_RESPONSE = """{{"legalHolds": [{}]}}""".format(MATTER_RESPONSE) | ||
LEGAL_HOLD_COMMAND = "legal-hold" | ||
|
@@ -212,6 +252,15 @@ def active_and_inactive_legal_hold_memberships_response(mocker): | |
return [_create_py42_response(mocker, ALL_ACTIVE_AND_INACTIVE_CUSTODIANS_RESPONSE)] | ||
|
||
|
||
@pytest.fixture | ||
def empty_events_response(mocker): | ||
return _create_py42_response(mocker, EMPTY_EVENTS_RESPONSE) | ||
|
||
|
||
def events_list_generator(): | ||
yield TEST_EVENT_PAGE | ||
|
||
|
||
@pytest.fixture | ||
def get_user_id_success(cli_state): | ||
cli_state.sdk.users.get_by_username.return_value = { | ||
|
@@ -246,6 +295,11 @@ def check_matter_accessible_failure(cli_state, custom_error): | |
) | ||
|
||
|
||
@pytest.fixture | ||
def get_all_events_success(cli_state): | ||
cli_state.sdk.legalhold.get_all_events.return_value = events_list_generator() | ||
|
||
|
||
@pytest.fixture | ||
def user_already_added_response(mocker): | ||
mock_response = mocker.MagicMock(spec=Response) | ||
|
@@ -575,6 +629,44 @@ def test_list_with_csv_format_returns_no_response_when_response_is_empty( | |
assert "Matter ID,Name,Description,Creator,Creation Date" not in result.output | ||
|
||
|
||
def test_search_events_shows_events_that_respect_type_filters( | ||
runner, cli_state, get_all_events_success | ||
): | ||
|
||
result = runner.invoke( | ||
cli, | ||
["legal-hold", "search-events", "--event-type", "HoldCreated"], | ||
obj=cli_state, | ||
) | ||
|
||
assert _CREATE_EVENT_ID in result.output | ||
assert _MEMBERSHIP_EVENT_ID not in result.output | ||
|
||
|
||
def test_search_events_with_csv_returns_no_events_when_response_is_empty( | ||
runner, cli_state, get_all_events_success, empty_events_response | ||
): | ||
cli_state.sdk.legalhold.get_all_events.return_value = empty_events_response | ||
result = runner.invoke(cli, ["legal-hold", "events", "-f", "csv"], obj=cli_state) | ||
|
||
assert ( | ||
"actorEmail,actorUsername,actorLastName,actorUserUid,actorUserExtRef" | ||
not in result.output | ||
) | ||
|
||
|
||
def test_search_events_is_called_with_expected_begin_timestamp(runner, cli_state): | ||
expected_timestamp = convert_datetime_to_timestamp( | ||
datetime.datetime.strptime("2017-01-01", "%Y-%m-%d") | ||
) | ||
command = ["legal-hold", "search-events", "--begin", "2017-01-01T00:00:00"] | ||
runner.invoke(cli, command, obj=cli_state) | ||
|
||
cli_state.sdk.legalhold.get_all_events.assert_called_once_with( | ||
None, expected_timestamp, None | ||
) | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we get test(s) for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll work on this. I had tried this initially, but had trouble getting a test to recognize the date inputs in the runner. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a test titled |
||
@pytest.mark.parametrize( | ||
"command, error_msg", | ||
[ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@annie-payseur When you have chance, could you help review these option help texts? I will tag you in spots.