Skip to content
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 created/updated properties to sync table #8820

Merged
merged 8 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/syft/src/syft/service/code/user_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
from ..response import SyftNotReady
from ..response import SyftSuccess
from ..response import SyftWarning
from ..user.user import UserView
from .code_parse import GlobalsVisitor
from .code_parse import LaunchJobVisitor
from .unparse import unparse
Expand Down Expand Up @@ -348,6 +349,18 @@ def _coll_repr_(self) -> dict[str, Any]:
"Submit time": str(self.submit_time),
}

@property
def user(self) -> UserView | SyftError:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this correct? doesnt this just get the user for the current client? What if the current client is the admin, but the DS created this user code?

api = APIRegistry.api_for(
node_uid=self.syft_node_location,
user_verify_key=self.user_verify_key,
)
if api is None:
return SyftError(
message=f"Can't access Syft API. You must login to {self.syft_node_location}"
)
return api.services.user.get_current_user()

@property
def status(self) -> UserCodeStatusCollection | SyftError:
# Clientside only
Expand Down
19 changes: 6 additions & 13 deletions packages/syft/src/syft/service/job/job_stash.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# stdlib
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from enum import Enum
import random
from string import Template
Expand Down Expand Up @@ -28,6 +29,7 @@
from ...store.document_store import QueryKeys
from ...store.document_store import UIDPartitionKey
from ...types.datetime import DateTime
from ...types.datetime import format_timedelta
from ...types.syft_object import SYFT_OBJECT_VERSION_2
from ...types.syft_object import SYFT_OBJECT_VERSION_5
from ...types.syft_object import SyftObject
Expand Down Expand Up @@ -87,7 +89,9 @@ class Job(SyncableSyftObject):
parent_job_id: UID | None = None
n_iters: int | None = 0
current_iter: int | None = None
creation_time: str | None = Field(default_factory=lambda: str(datetime.now()))
creation_time: str | None = Field(
default_factory=lambda: str(datetime.now(tz=timezone.utc))
)
action: Action | None = None
job_pid: int | None = None
job_worker_id: UID | None = None
Expand Down Expand Up @@ -191,18 +195,7 @@ def eta_string(self) -> str | None:
):
return None

def format_timedelta(local_timedelta: timedelta) -> str:
total_seconds = int(local_timedelta.total_seconds())
hours, leftover = divmod(total_seconds, 3600)
minutes, seconds = divmod(leftover, 60)

hours_string = f"{hours}:" if hours != 0 else ""
minutes_string = f"{minutes}:".zfill(3)
seconds_string = f"{seconds}".zfill(2)

return f"{hours_string}{minutes_string}{seconds_string}"

now = datetime.now()
now = datetime.now(tz=timezone.utc)
time_passed = now - datetime.fromisoformat(self.creation_time)
iter_duration_seconds: float = time_passed.total_seconds() / self.current_iter
iters_remaining = self.n_iters - self.current_iter
Expand Down
32 changes: 32 additions & 0 deletions packages/syft/src/syft/types/datetime.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# stdlib
from datetime import datetime
from datetime import timedelta
from functools import total_ordering
import re
from typing import Any
Expand Down Expand Up @@ -57,3 +58,34 @@ def __eq__(self, other: Any) -> bool:

def __lt__(self, other: Self) -> bool:
return self.utc_timestamp < other.utc_timestamp

def timedelta(self, other: "DateTime") -> timedelta:
utc_timestamp_delta = self.utc_timestamp - other.utc_timestamp
return timedelta(seconds=utc_timestamp_delta)


def format_timedelta(local_timedelta: timedelta) -> str:
total_seconds = int(local_timedelta.total_seconds())
hours, leftover = divmod(total_seconds, 3600)
minutes, seconds = divmod(leftover, 60)

hours_string = f"{hours}:" if hours != 0 else ""
minutes_string = f"{minutes}:".zfill(3)
seconds_string = f"{seconds}".zfill(2)

return f"{hours_string}{minutes_string}{seconds_string}"


def format_timedelta_human_readable(local_timedelta: timedelta) -> str:
# Returns a human-readable string representing the timedelta
units = [("day", 86400), ("hour", 3600), ("minute", 60), ("second", 1)]
total_seconds = int(local_timedelta.total_seconds())

for unit_name, unit_seconds in units:
unit_value, total_seconds = divmod(total_seconds, unit_seconds)
if unit_value > 0:
if unit_value == 1:
return f"{unit_value} {unit_name}"
else:
return f"{unit_value} {unit_name}s"
return "0 seconds"
52 changes: 48 additions & 4 deletions packages/syft/src/syft/util/notebook_ui/components/sync.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# stdlib
import datetime
from typing import Any

# third party
Expand All @@ -9,6 +10,10 @@
from ....service.code.user_code import UserCode
from ....service.job.job_stash import Job
from ....service.request.request import Request
from ....service.response import SyftError
from ....service.user.user import UserView
from ....types.datetime import DateTime
from ....types.datetime import format_timedelta_human_readable
from ....types.syft_object import SYFT_OBJECT_VERSION_1
from ....types.syft_object import SyftObject
from ..icons import Icon
Expand Down Expand Up @@ -101,6 +106,43 @@ def get_status_str(self) -> str:
return status.value
return "" # type: ignore

def get_updated_by(self) -> str:
# TODO replace with centralized SyftObject created/updated by attribute
if isinstance(self.object, Request):
email = self.object.requesting_user_email
if email is not None:
return f"Requested by {email}"

user_view: UserView | SyftError | None = None
if isinstance(self.object, UserCode):
user_view = self.object.user

if isinstance(user_view, UserView):
return f"Created by {user_view.email}"
return ""

def get_updated_delta_str(self) -> str:
# TODO replace with centralized SyftObject created/updated by attribute
if isinstance(self.object, Job):
# NOTE Job is not using DateTime for creation_time, so we need to handle it separately
time_str = self.object.creation_time
if time_str is not None:
t = datetime.datetime.fromisoformat(time_str)
delta = datetime.datetime.now(datetime.timezone.utc) - t
return f"{format_timedelta_human_readable(delta)} ago"

dt: DateTime | None = None
if isinstance(self.object, Request):
dt = self.object.request_time
if isinstance(self.object, UserCode):
dt = self.object.submit_time
if dt is not None:
delta = DateTime.now().timedelta(dt)
delta_str = format_timedelta_human_readable(delta)
return f"{delta_str} ago"

return ""

def to_html(self) -> str:
type_html = TypeLabel(object=self.object).to_html()

Expand All @@ -110,10 +152,12 @@ def to_html(self) -> str:
copy_text=str(self.object.id.id), max_width=60
).to_html()

updated_delta_str = "29m ago"
updated_by = "[email protected]"
updated_delta_str = self.get_updated_delta_str()
updated_by = self.get_updated_by()
status_str = self.get_status_str()
status_seperator = " • " if len(status_str) else ""
status_row = " • ".join(
s for s in [status_str, updated_by, updated_delta_str] if s
)
summary_html = f"""
<div style="display: flex; gap: 8px; justify-content: space-between; width: 100%; overflow: hidden; align-items: center;">
<div style="display: flex; gap: 8px; justify-content: start; align-items: center;">
Expand All @@ -123,7 +167,7 @@ def to_html(self) -> str:
</div>
<div style="display: table-row">
<span class='syncstate-col-footer'>
{status_str}{status_seperator}Updated by {updated_by} {updated_delta_str}
{status_row}
</span>
</div>
""" # noqa: E501
Expand Down
3 changes: 2 additions & 1 deletion packages/syft/tests/syft/service/jobs/job_stash_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# stdlib
from datetime import datetime
from datetime import timedelta
from datetime import timezone

# third party
import pytest
Expand Down Expand Up @@ -33,7 +34,7 @@ def test_eta_string(current_iter, n_iters, status, creation_time_delta, expected
node_uid=UID(),
n_iters=n_iters,
current_iter=current_iter,
creation_time=(datetime.now() - creation_time_delta).isoformat(),
creation_time=(datetime.now(tz=timezone.utc) - creation_time_delta).isoformat(),
status=status,
)

Expand Down
Loading