Skip to content

Commit

Permalink
Merge pull request #8820 from OpenMined/eelco/sync-table-fixes
Browse files Browse the repository at this point in the history
add created/updated properties to sync table
  • Loading branch information
eelcovdw committed May 21, 2024
2 parents 5c17a6c + 125e62a commit 9939eaa
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 18 deletions.
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:
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_6
from ...types.syft_object import SyftObject
Expand Down Expand Up @@ -96,7 +98,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 @@ -201,18 +205,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

0 comments on commit 9939eaa

Please sign in to comment.