Skip to content

Commit

Permalink
Merge branch 'dev' into node-peer-partial-update
Browse files Browse the repository at this point in the history
  • Loading branch information
khoaguin authored May 28, 2024
2 parents ef306f2 + 6e22c55 commit 009123e
Show file tree
Hide file tree
Showing 12 changed files with 2,079 additions and 48 deletions.
1,655 changes: 1,650 additions & 5 deletions packages/syft/src/syft/assets/css/tabulator_pysyft.min.css

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions packages/syft/src/syft/assets/jinja/table.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@
<script type="application/javascript">
{{ js | safe }}
buildTable(
table_{{ uid }} = buildTable(
{{ columns | safe }},
{{ row_header | safe }},
{{ data | safe }},
"{{ uid }}"
"{{ uid }}",
pagination={{ pagination }},
maxHeight={{ max_height }},
headerSort={{ header_sort }},
)
</script>

Expand Down
101 changes: 98 additions & 3 deletions packages/syft/src/syft/assets/js/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ TABULATOR_CSS =
document.querySelectorAll(".escape-unfocus").forEach((input) => {
input.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
console.log("Escape key pressed");
event.stopPropagation();
input.blur();
}
Expand Down Expand Up @@ -58,7 +57,15 @@ function load_tabulator(elementId) {
});
}

function buildTable(columns, rowHeader, data, uid) {
function buildTable(
columns,
rowHeader,
data,
uid,
pagination = true,
maxHeight = null,
headerSort = true,
) {
const tableId = `table-${uid}`;
const searchBarId = `search-${uid}`;
const numrowsId = `numrows-${uid}`;
Expand All @@ -73,11 +80,14 @@ function buildTable(columns, rowHeader, data, uid) {
data: data,
columns: columns,
rowHeader: rowHeader,
index: "_table_repr_index",
layout: "fitDataStretch",
resizableColumnFit: true,
resizableColumnGuide: true,
pagination: "local",
pagination: pagination,
paginationSize: 5,
maxHeight: maxHeight,
headerSort: headerSort,
});

// Events needed for cell overflow:
Expand All @@ -100,6 +110,7 @@ function buildTable(columns, rowHeader, data, uid) {
numrowsElement.innerHTML = data.length;
}

configureHighlightSingleRow(table, uid);
configureSearch(table, searchBarId, columns);

return table;
Expand Down Expand Up @@ -129,3 +140,87 @@ function configureSearch(table, searchBarId, columns) {
table.setFilter([filterArray]);
});
}

function configureHighlightSingleRow(table, uid) {
// Listener for rowHighlight events, with fields:
// uid: string, table uid
// index: number | string, row index to highlight
// jumpToRow: bool, if true, jumps to page where the row is located
document.addEventListener("rowHighlight", function (e) {
if (e.detail.uid === uid) {
let row_idx = e.detail.index;
let rows = table.getRows();
for (let row of rows) {
if (row.getIndex() == row_idx) {
row.select();
if (e.detail.jumpToRow) {
// catch promise in case the table does not have pagination
table.setPageToRow(row_idx).catch((_) => {});
table.scrollToRow(row_idx, "top", false);
}
} else {
row.deselect();
}
}
}
});
}

function waitForTable(uid, timeout = 1000) {
return new Promise((resolve, reject) => {
// Check if the table is ready immediately
if (window["table_" + uid]) {
resolve();
} else {
// Otherwise, poll until the table is ready or timeout
var startTime = Date.now();
var checkTableInterval = setInterval(function () {
if (window["table_" + uid]) {
clearInterval(checkTableInterval);
resolve();
} else if (Date.now() - startTime > timeout) {
clearInterval(checkTableInterval);
reject(`Timeout: table_"${uid}" not found.`);
}
}, 100);
}
});
}

function highlightSingleRow(uid, index = null, jumpToRow = false) {
// Highlight a single row in the table with the given uid
// If index is not provided or doesn't exist, all rows are deselected
waitForTable(uid)
.then(() => {
document.dispatchEvent(
new CustomEvent("rowHighlight", {
detail: { uid, index, jumpToRow },
}),
);
})
.catch((error) => {
console.log(error);
});
}

function updateTableCell(uid, index, field, value) {
// Update the value of a cell in the table with the given uid
waitForTable(uid)
.then(() => {
const table = window["table_" + uid];
if (!table) {
throw new Error(`Table with uid ${uid} not found.`);
}

const row = table.getRow(index);
if (!row) {
throw new Error(`Row with index ${index} not found.`);
}

// Update the cell value
row.update({ [field]: value });
})
.catch((error) => {
console.error(error);
});
}
29 changes: 10 additions & 19 deletions packages/syft/src/syft/client/syncing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ..service.sync.diff_state import NodeDiff
from ..service.sync.diff_state import ObjectDiffBatch
from ..service.sync.diff_state import SyncInstruction
from ..service.sync.resolve_widget import PaginatedResolveWidget
from ..service.sync.resolve_widget import ResolveWidget
from ..service.sync.sync_state import SyncState
from ..types.uid import UID
Expand Down Expand Up @@ -71,28 +72,18 @@ def compare_clients(
)


def get_user_input_for_resolve() -> SyncDecision:
options = [x.value for x in SyncDecision]
options_str = ", ".join(options[:-1]) + f" or {options[-1]}"
print(f"How do you want to sync these objects? choose between {options_str}")

while True:
decision = input()
decision = decision.lower()

try:
return SyncDecision(decision)
except ValueError:
print(f"Please choose between {options_str}")


def resolve(obj_diff_batch: ObjectDiffBatch) -> ResolveWidget:
widget = ResolveWidget(obj_diff_batch)
return widget
def resolve(obj: ObjectDiffBatch | NodeDiff) -> ResolveWidget | PaginatedResolveWidget:
if not isinstance(obj, ObjectDiffBatch | NodeDiff):
raise ValueError(
f"Invalid type: could not resolve object with type {type(obj).__qualname__}"
)
return obj.resolve()


@deprecated(reason="resolve_single has been renamed to resolve", return_syfterror=True)
def resolve_single(obj_diff_batch: ObjectDiffBatch) -> ResolveWidget:
def resolve_single(
obj_diff_batch: ObjectDiffBatch,
) -> ResolveWidget | PaginatedResolveWidget:
return resolve(obj_diff_batch)


Expand Down
4 changes: 4 additions & 0 deletions packages/syft/src/syft/service/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ def reconstruct_args_kwargs(
final_kwargs[param_key] = param.default
else:
raise Exception(f"Missing {param_key} not in kwargs.")

if "context":
final_kwargs["context"] = kwargs["context"]

return (args, final_kwargs)


Expand Down
30 changes: 28 additions & 2 deletions packages/syft/src/syft/service/settings/settings_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,36 @@ def set(
else:
return SyftError(message=result.err())

@service_method(path="settings.update", name="update")
@service_method(path="settings.update", name="update", autosplat=["settings"])
def update(
self, context: AuthedServiceContext, settings: NodeSettingsUpdate
) -> Result[SyftSuccess, SyftError]:
"""
Update the Node Settings using the provided values.
Args:
name: Optional[str]
Node name
organization: Optional[str]
Organization name
description: Optional[str]
Node description
on_board: Optional[bool]
Show onboarding panel when a user logs in for the first time
signup_enabled: Optional[bool]
Enable/Disable registration
admin_email: Optional[str]
Administrator email
association_request_auto_approval: Optional[bool]
Returns:
Result[SyftSuccess, SyftError]: A result indicating the success or failure of the update operation.
Example:
>>> node_client.update(name='foo', organization='bar', description='baz', signup_enabled=True)
SyftSuccess: Settings updated successfully.
"""

result = self.stash.get_all(context.credentials)
if result.is_ok():
current_settings = result.ok()
Expand Down Expand Up @@ -139,7 +165,7 @@ def allow_guest_signup(

result = method(context=context, settings=settings)

if result.is_err():
if isinstance(result, SyftError):
return SyftError(message=f"Failed to update settings: {result.err()}")

message = "enabled" if enable else "disabled"
Expand Down
37 changes: 36 additions & 1 deletion packages/syft/src/syft/service/sync/diff_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Any
from typing import ClassVar
from typing import Literal
from typing import TYPE_CHECKING

# third party
from loguru import logger
Expand Down Expand Up @@ -38,6 +39,7 @@
from ...types.uid import UID
from ...util import options
from ...util.colors import SURFACE
from ...util.notebook_ui.components.sync import Label
from ...util.notebook_ui.components.sync import SyncTableObject
from ...util.notebook_ui.icons import Icon
from ...util.notebook_ui.styles import FONT_CSS
Expand All @@ -59,6 +61,11 @@
from ..user.user import UserView
from .sync_state import SyncState

if TYPE_CHECKING:
# relative
from .resolve_widget import PaginatedResolveWidget
from .resolve_widget import ResolveWidget

sketchy_tab = "‎ " * 4


Expand Down Expand Up @@ -554,6 +561,12 @@ class ObjectDiffBatch(SyftObject):
root_diff: ObjectDiff
sync_direction: SyncDirection | None

def resolve(self) -> "ResolveWidget":
# relative
from .resolve_widget import ResolveWidget

return ResolveWidget(self)

def walk_graph(
self,
deps: dict[UID, list[UID]],
Expand Down Expand Up @@ -704,6 +717,21 @@ def root_id(self) -> UID:
def root_type(self) -> type:
return self.root_diff.obj_type

def decision_badge(self) -> str:
if self.decision is None:
return ""
if self.decision == SyncDecision.IGNORE:
decision_str = "IGNORED"
badge_color = "label-red"
if self.decision == SyncDecision.SKIP:
decision_str = "SKIPPED"
badge_color = "label-gray"
else:
decision_str = "SYNCED"
badge_color = "label-green"

return Label(value=decision_str, label_class=badge_color).to_html()

@property
def is_ignored(self) -> bool:
return self.decision == SyncDecision.IGNORE
Expand Down Expand Up @@ -846,9 +874,10 @@ def _coll_repr_(self) -> dict[str, Any]:
high_html = SyncTableObject(object=self.root_diff.high_obj).to_html()

return {
"Merge status": self.status_badge(),
"Diff status": self.status_badge(),
"Public Sync State": low_html,
"Private sync state": high_html,
"Decision": self.decision_badge(),
}

@property
Expand Down Expand Up @@ -1118,6 +1147,12 @@ class NodeDiff(SyftObject):

include_ignored: bool = False

def resolve(self) -> "PaginatedResolveWidget":
# relative
from .resolve_widget import PaginatedResolveWidget

return PaginatedResolveWidget(batches=self.batches)

def __getitem__(self, idx: Any) -> ObjectDiffBatch:
return self.batches[idx]

Expand Down
Loading

0 comments on commit 009123e

Please sign in to comment.