Skip to content

Commit

Permalink
Merge pull request #8856 from OpenMined/eelco/multi-resolve-widget
Browse files Browse the repository at this point in the history
multiple resolve widget
  • Loading branch information
eelcovdw authored May 27, 2024
2 parents 9417d07 + deedeeb commit 6e22c55
Show file tree
Hide file tree
Showing 8 changed files with 2,037 additions and 36 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
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 6e22c55

Please sign in to comment.