-
Notifications
You must be signed in to change notification settings - Fork 14
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
Add legal hold membership to device reporting #192
Changes from 17 commits
b285a68
ed72879
ea4381a
a0ebe57
077dea1
03a3814
f174992
205d559
02d6530
e5784cf
9bfcd42
2e303de
94fe3b9
0b79911
1bd1e2f
e4725c7
237ea31
7212fbf
2a917bd
2d1db8c
9a0afcb
a3dd28f
9677f23
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 |
---|---|---|
|
@@ -58,6 +58,7 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta | |
- `search` to search for audit-logs. | ||
- `send-to` to send audit-logs to server. | ||
|
||
|
||
## Unreleased | ||
|
||
### Changed | ||
|
@@ -71,6 +72,12 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta | |
- Now, when adding a cloud alias to a detection list user, such as during `departing-employee add`, it will remove the existing cloud alias if one exists. | ||
- Before, it would error and the cloud alias would not get added. | ||
|
||
### Added | ||
|
||
- `code42 devices list` option: | ||
- `--include-legal-hold-membership` to add legal hold columns to the result output, printing the legal hold matter name and ID for any active device actively on legal hold | ||
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. can we rephrase atleast 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. yes, that is terrible phrasing. Resolved with latest commit |
||
- `--include-total-storage` to add columns with total number of archives and total sum of archive bytes to each device in the result output | ||
|
||
## 1.0.0 - 2020-08-31 | ||
|
||
### Fixed | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,11 @@ | ||
from datetime import date | ||
|
||
import click | ||
import numpy as np | ||
from pandas import concat | ||
from pandas import DataFrame | ||
from pandas import json_normalize | ||
from pandas import Series | ||
from pandas import to_datetime | ||
from py42 import exceptions | ||
from py42.exceptions import Py42NotFoundError | ||
|
@@ -244,6 +247,22 @@ def _get_device_info(sdk, device_guid): | |
is_flag=True, | ||
help="Include device settings in output.", | ||
) | ||
@click.option( | ||
"--include-legal-hold-membership", | ||
required=False, | ||
type=bool, | ||
default=False, | ||
is_flag=True, | ||
help="Include legal hold membership in output.", | ||
) | ||
@click.option( | ||
"--include-total-storage", | ||
required=False, | ||
type=bool, | ||
default=False, | ||
is_flag=True, | ||
help="Include archive count and total storage in output.", | ||
) | ||
@click.option( | ||
"--exclude-most-recently-connected", | ||
type=int, | ||
|
@@ -285,6 +304,8 @@ def list_devices( | |
include_backup_usage, | ||
include_usernames, | ||
include_settings, | ||
include_legal_hold_membership, | ||
include_total_storage, | ||
exclude_most_recently_connected, | ||
last_connected_after, | ||
last_connected_before, | ||
|
@@ -309,7 +330,11 @@ def list_devices( | |
"userUid", | ||
] | ||
df = _get_device_dataframe( | ||
state.sdk, columns, active, org_uid, include_backup_usage | ||
state.sdk, | ||
columns, | ||
active, | ||
org_uid, | ||
(include_backup_usage or include_total_storage), | ||
) | ||
if last_connected_after: | ||
df = df.loc[to_datetime(df.lastConnected) > last_connected_after] | ||
|
@@ -326,17 +351,52 @@ def list_devices( | |
.head(exclude_most_recently_connected) | ||
) | ||
df = df.drop(most_recent.index) | ||
if include_total_storage: | ||
df = _add_storage_totals_to_dataframe(df, include_backup_usage) | ||
if include_settings: | ||
df = _add_settings_to_dataframe(state.sdk, df) | ||
if include_usernames: | ||
df = _add_usernames_to_device_dataframe(state.sdk, df) | ||
if include_legal_hold_membership: | ||
df = _add_legal_hold_membership_to_device_dataframe(state.sdk, df) | ||
if df.empty: | ||
click.echo("No results found.") | ||
else: | ||
formatter = DataFrameOutputFormatter(format) | ||
formatter.echo_formatted_dataframe(df) | ||
|
||
|
||
def _add_legal_hold_membership_to_device_dataframe(sdk, df): | ||
columns = ["legalHold.legalHoldUid", "legalHold.name", "user.userUid"] | ||
|
||
legal_hold_member_dataframe = ( | ||
json_normalize(list(_get_all_active_hold_memberships(sdk)))[columns] | ||
.groupby(["user.userUid"]) | ||
.agg(",".join) | ||
) | ||
df = df.merge( | ||
legal_hold_member_dataframe, | ||
how="left", | ||
left_on="userUid", | ||
right_on="user.userUid", | ||
) | ||
|
||
df.loc[ | ||
df["status"] == "Deactivated", ["legalHold.legalHoldUid", "legalHold.name"], | ||
] = np.nan | ||
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. Instead of using np.nan for null values here, let's use an empty string, I think 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 chose To confirm: which of the following would you prefer?
(Either will produce null values in the other output formats) 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. Oops, I meant to say "resulting from the I think we should convert NaN/None values to an empty string for table/CSV outputs, but we'd probably want to keep them as NaN/None when we output to json so it translates to I'll start a new PR to rework the formatter to handle that, since I'd like to refactor it a bit for clarity and add some tests around handling dataframe nulls. 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. NaNs removed 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. Ok, I've opened #245 to add the null handling in the DataFrameOutputFormatter, so we don't need to be concerned with converting them here, since we want the conversion to be conditional on the output format chosen by the end-user. Sorry to have you flip/flop this! 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. Resolved, and awaiting #245 |
||
|
||
return df | ||
|
||
|
||
def _get_all_active_hold_memberships(sdk): | ||
for page in sdk.legalhold.get_all_matters(active=True): | ||
for matter in page["legalHolds"]: | ||
for _page in sdk.legalhold.get_all_matter_custodians( | ||
legal_hold_uid=matter["legalHoldUid"], active=True | ||
): | ||
yield from _page["legalHoldMemberships"] | ||
|
||
|
||
def _get_device_dataframe( | ||
sdk, columns, active=None, org_uid=None, include_backup_usage=False | ||
): | ||
|
@@ -392,6 +452,25 @@ def _add_usernames_to_device_dataframe(sdk, device_dataframe): | |
return device_dataframe.merge(users_dataframe, how="left", on="userUid") | ||
|
||
|
||
def _add_storage_totals_to_dataframe(df, include_backup_usage): | ||
df[["archiveCount", "totalStorageBytes"]] = df["backupUsage"].apply( | ||
_break_backup_usage_into_total_storage | ||
) | ||
|
||
if not include_backup_usage: | ||
df = df.drop("backupUsage", axis=1) | ||
return df | ||
|
||
|
||
def _break_backup_usage_into_total_storage(backup_usage): | ||
total_storage = 0 | ||
archive_count = 0 | ||
for archive in backup_usage: | ||
maddie-vargo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
archive_count += 1 | ||
total_storage += archive["archiveBytes"] | ||
return Series([archive_count, total_storage]) | ||
|
||
|
||
@devices.command() | ||
@active_option | ||
@inactive_option | ||
|
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.
Looks like our changelog has gotten messed up? The
#Unreleased
tag should not be there...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.
@unparalleled-js I re-pulled from master. The #Unreleased tag should be at the top, right? Let me know if I should fix it with this PR