Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 32 additions & 0 deletions src/uproot/_static/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -651,3 +651,35 @@ window.actuallyRedirect = function() {
uproot.alert("The action has completed.");
});
};

window.actuallyGroup = function() {
const action = uproot.selectedValue("group_action");

if (!action) {
return uproot.error(_("No action selected."));
}

const groupSize = parseInt(I("group-size")?.value ?? "2", 10);
const shuffle = !!I("group-shuffle")?.checked;
const reload = !!I("group-reload")?.checked;

if (action === "by_size" && (isNaN(groupSize) || groupSize < 1)) {
return uproot.error(_("Group size must be at least 1."));
}

window.bootstrap?.Modal.getOrCreateInstance(I("group-modal")).hide();
window.invokeFromMonitor("group_players", { action, group_size: groupSize, shuffle, reload })
.then((result) => {
loadExtraData();
if (result.groups_created) {
uproot.alert(_("Created #n# group(s).").replace("#n#", result.groups_created));
} else if (result.players_reset !== undefined) {
uproot.alert(_("Reset group assignment for #n# player(s).").replace("#n#", result.players_reset));
} else {
uproot.alert(_("The action has completed."));
}
})
.catch((err) => {
uproot.error(err.message || _("An error occurred."));
});
};
2 changes: 2 additions & 0 deletions src/uproot/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
adminmessage,
advance_by_one,
fields_from_all,
group_players,
info_online,
insert_fields,
mark_dropout,
Expand Down Expand Up @@ -113,6 +114,7 @@
"adminmessage",
"advance_by_one",
"fields_from_all",
"group_players",
"info_online",
"insert_fields",
"mark_dropout",
Expand Down
47 changes: 47 additions & 0 deletions src/uproot/default/admin/Session.html
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ <h3 class="flex-grow-1 fw-semibold me-3 mb-3 mt-2">{% translate %}Player monitor
{% translate %}Redirect to URL{% endtranslate %}
</span>
</li>
<li>
<span class="dropdown-item" onclick="mmodal('group')" role="button">
{% translate %}Group players{% endtranslate %}
</span>
</li>
</ul>
</div>
</div>
Expand Down Expand Up @@ -326,6 +331,48 @@ <h3 class="flex-grow-1 fw-semibold me-3 mb-3 mt-2">{% translate %}Player monitor
</div>
{% endcall %}

<!-- Modal for grouping players -->
{% call modal(_, "group", title=_("Group players"), action_text=_("Apply grouping"), action_handler="actuallyGroup()") %}
{{ player_count(_) }}
<div class="mb-1">
<div class="form-check mb-3">
<input class="form-check-input" type="radio" name="group_action" id="group_same" value="same_group">
<label class="form-check-label" for="group_same">
{% translate %}Put all selected players in the same group{% endtranslate %}
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="radio" name="group_action" id="group_reset" value="reset">
<label class="form-check-label" for="group_reset">
{% translate %}Reset group assignment for selected players{% endtranslate %}
</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="radio" name="group_action" id="group_by_size" value="by_size">
<label class="form-check-label" for="group_by_size">
{% translate %}Create groups of size{% endtranslate %}:
</label>
</div>
<div class="ms-4 mb-3">
<input type="number" class="form-control form-control-sm" id="group-size" value="2" min="1" style="width: 80px;" onclick="I('group_by_size').checked = true;">
<div class="form-text">{% translate %}Number of selected players must be divisible by group size.{% endtranslate %}</div>
</div>
<div class="form-check mb-3 ms-4">
<input class="form-check-input" type="checkbox" id="group-shuffle" value="shuffle">
<label class="form-check-label" for="group-shuffle">
{% translate %}Shuffle players before grouping{% endtranslate %}
</label>
</div>
</div>
<hr>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="group-reload" value="reload">
<label class="form-check-label" for="group-reload">
{% translate %}Reload players' pages{% endtranslate %}
</label>
</div>
{% endcall %}

<!-- Modal for player URLs -->

<div class="modal fade" id="urlModal" tabindex="-1" aria-labelledby="urlModalLabel" aria-hidden="true">
Expand Down
1 change: 1 addition & 0 deletions src/uproot/server2.py
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,7 @@ async def dummy(
fields_from_all=a.fields_from_all,
flip_active=a.flip_active,
flip_testing=a.flip_testing,
group_players=a.group_players,
insert_fields=a.insert_fields,
mark_dropout=a.mark_dropout,
praise=a.praise,
Expand Down
100 changes: 100 additions & 0 deletions src/uproot/services/player_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,3 +224,103 @@ async def adminmessage(sname: t.Sessionname, unames: list[str], msg: str) -> Non
event="_uproot_AdminMessaged",
),
)


async def group_players(
sname: t.Sessionname,
unames: list[str],
action: str,
group_size: int = 1,
shuffle: bool = False,
reload: bool = False,
) -> dict[str, Any]:
"""
Manage player group assignments.

Actions:
- "same_group": Put all selected players in the same group
- "reset": Remove group assignments from selected players
- "by_size": Create groups of specified size

Args:
sname: Session name
unames: List of player usernames
action: One of "same_group", "reset", "by_size"
group_size: Size of groups when action is "by_size"
shuffle: Whether to shuffle players before grouping (for "by_size")
reload: Whether to reload player pages after grouping

Returns:
Result dict with info about created/modified groups
"""
import random

import uproot.core as c

session_exists(sname, False)

sid = t.SessionIdentifier(sname)
pids = [t.PlayerIdentifier(sname, uname) for uname in unames]

# Shuffle players if requested
if shuffle:
random.shuffle(pids)

result: dict[str, Any] = {"action": action, "players": unames}

with sid() as session:
if action == "same_group":
# Put all selected players in the same group
gid = c.create_group(session, pids, overwrite=True)
result["groups_created"] = 1
result["group_name"] = gid.gname

elif action == "reset":
# Remove group assignments from selected players
reset_count = 0
for pid in pids:
with pid() as player:
if player._uproot_group is not None:
player._uproot_group = None
player.member_id = None
reset_count += 1
result["players_reset"] = reset_count

elif action == "by_size":
# Create groups of specified size
if group_size < 1:
raise ValueError("Group size must be at least 1")
if len(pids) % group_size != 0:
raise ValueError(
f"Number of selected players ({len(pids)}) "
f"must be divisible by group size ({group_size})"
)

groups_created = []
for i in range(0, len(pids), group_size):
group_pids = pids[i : i + group_size]
gid = c.create_group(session, group_pids, overwrite=True)
groups_created.append(gid.gname)

result["groups_created"] = len(groups_created)
result["group_names"] = groups_created

else:
raise ValueError(f"Unknown action: {action}")

# Optionally reload player pages
if reload:
for uname in unames:
ptuple = sname, uname
q.enqueue(
ptuple,
dict(
source="admin",
kind="action",
payload=dict(
action="reload",
),
),
)

return result
Loading