Fix remaining review threads on the user alts tab

- Switch to the User Alts tab when it is created, so the first investigate
  click does not open it in the background.
- Treat an empty RespOk as "user not found" and use a generic message for
  error codes, matching what the server actually returns.
- Exclude the depth-0 seed row from the related-account count.
- Guard the reply with the requested user name so a stale response cannot
  overwrite a newer one.
- Move the duplicated formatEpoch body into widgets/utility/epoch_format.h.
This commit is contained in:
Lukas Brübach 2026-11-01 05:57:17 +01:00
parent 1e11f78ac2
commit ff6c0402a8
5 changed files with 51 additions and 11 deletions

View file

@ -285,6 +285,7 @@ set(cockatrice_SOURCES
src/interface/widgets/settings_page/storage_settings_page.cpp src/interface/widgets/settings_page/storage_settings_page.cpp
src/interface/widgets/settings_page/user_interface_settings_page.cpp src/interface/widgets/settings_page/user_interface_settings_page.cpp
src/interface/widgets/utility/custom_line_edit.cpp src/interface/widgets/utility/custom_line_edit.cpp
src/interface/widgets/utility/epoch_format.h
src/interface/widgets/utility/get_text_with_max.cpp src/interface/widgets/utility/get_text_with_max.cpp
src/interface/widgets/utility/report_utils.cpp src/interface/widgets/utility/report_utils.cpp
src/interface/widgets/utility/report_utils.h src/interface/widgets/utility/report_utils.h

View file

@ -1,5 +1,6 @@
#include "tab_alts.h" #include "tab_alts.h"
#include "../utility/epoch_format.h"
#include "abstract_client.h" #include "abstract_client.h"
#include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/response.pb.h"
#include "libcockatrice/protocol/pb/response_user_alts.pb.h" #include "libcockatrice/protocol/pb/response_user_alts.pb.h"
@ -99,10 +100,7 @@ void TabAlts::retranslateUi()
QString TabAlts::formatEpoch(quint64 ts) const QString TabAlts::formatEpoch(quint64 ts) const
{ {
if (ts == 0) { return formatEpochTimestamp(ts, tr("Unknown"));
return tr("Unknown");
}
return QDateTime::fromSecsSinceEpoch(ts).toLocalTime().toString("yyyy-MM-dd HH:mm");
} }
void TabAlts::investigate(const QString &userName) void TabAlts::investigate(const QString &userName)
@ -132,7 +130,11 @@ void TabAlts::requestAlts(const QString &userName, int depth)
cmd.set_user_name(userName.toStdString()); cmd.set_user_name(userName.toStdString());
cmd.set_depth(static_cast<google::protobuf::uint32>(depth)); cmd.set_depth(static_cast<google::protobuf::uint32>(depth));
PendingCommand *pend = client->prepareModeratorCommand(cmd); PendingCommand *pend = client->prepareModeratorCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabAlts::altsResponse); connect(pend, &PendingCommand::finished, this, [this, userName](const Response &response) {
if (userName == currentUser) {
altsResponse(response);
}
});
client->sendCommand(pend); client->sendCommand(pend);
} }
@ -140,14 +142,25 @@ void TabAlts::altsResponse(const Response &response)
{ {
altsTable->setRowCount(0); altsTable->setRowCount(0);
if (response.response_code() != Response::RespOk) { if (response.response_code() != Response::RespOk) {
statusLabel->setText(tr("User %1 not found.").arg(currentUser)); statusLabel->setText(tr("Could not load alts for %1.").arg(currentUser));
return; return;
} }
const Response_UserAlts &resp = response.GetExtension(Response_UserAlts::ext); const Response_UserAlts &resp = response.GetExtension(Response_UserAlts::ext);
// The server answers RespOk with no rows when the user does not exist.
if (resp.alts_size() == 0) {
statusLabel->setText(tr("User %1 not found.").arg(currentUser));
return;
}
altsTable->setRowCount(resp.alts_size()); altsTable->setRowCount(resp.alts_size());
int relatedCount = 0;
for (int i = 0; i < resp.alts_size(); ++i) { for (int i = 0; i < resp.alts_size(); ++i) {
const ServerInfo_UserAlt &alt = resp.alts(i); const ServerInfo_UserAlt &alt = resp.alts(i);
// The server appends the frontier user as a depth-0 seed row.
if (!alt.has_depth() || alt.depth() > 0) {
++relatedCount;
}
altsTable->setItem(i, COL_ALTS_USER, new QTableWidgetItem(QString::fromStdString(alt.user_name()))); altsTable->setItem(i, COL_ALTS_USER, new QTableWidgetItem(QString::fromStdString(alt.user_name())));
altsTable->setItem(i, COL_ALTS_VIA, altsTable->setItem(i, COL_ALTS_VIA,
new QTableWidgetItem(alt.has_match_reason() && !alt.match_reason().empty() new QTableWidgetItem(alt.has_match_reason() && !alt.match_reason().empty()
@ -166,5 +179,5 @@ void TabAlts::altsResponse(const Response &response)
altsTable->setItem(i, COL_ALTS_BANS, new QTableWidgetItem(QString::number(alt.ban_count()))); altsTable->setItem(i, COL_ALTS_BANS, new QTableWidgetItem(QString::number(alt.ban_count())));
altsTable->setItem(i, COL_ALTS_ACTIVE, new QTableWidgetItem(alt.is_active() ? tr("yes") : tr("no"))); altsTable->setItem(i, COL_ALTS_ACTIVE, new QTableWidgetItem(alt.is_active() ? tr("yes") : tr("no")));
} }
statusLabel->setText(tr("%1 account(s) related to %2.").arg(resp.alts_size()).arg(currentUser)); statusLabel->setText(tr("%1 account(s) related to %2.").arg(relatedCount).arg(currentUser));
} }

View file

@ -1,5 +1,6 @@
#include "tab_moderation.h" #include "tab_moderation.h"
#include "../utility/epoch_format.h"
#include "abstract_client.h" #include "abstract_client.h"
#include "libcockatrice/protocol/pb/admin_commands.pb.h" #include "libcockatrice/protocol/pb/admin_commands.pb.h"
#include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/response.pb.h"
@ -215,10 +216,7 @@ void TabModeration::retranslateUi()
QString TabModeration::formatEpoch(quint64 ts) const QString TabModeration::formatEpoch(quint64 ts) const
{ {
if (ts == 0) { return formatEpochTimestamp(ts, tr("Unknown"));
return tr("Unknown");
}
return QDateTime::fromSecsSinceEpoch(ts).toLocalTime().toString("yyyy-MM-dd HH:mm");
} }
void TabModeration::clearUserData() void TabModeration::clearUserData()

View file

@ -933,6 +933,7 @@ void TabSupervisor::openTabAlts(const QString &userName)
} }
tabAlts = new TabAlts(this, client, userName); tabAlts = new TabAlts(this, client, userName);
myAddTab(tabAlts); myAddTab(tabAlts);
setCurrentWidget(tabAlts);
connect(tabAlts, &QObject::destroyed, this, [this] { tabAlts = nullptr; }); connect(tabAlts, &QObject::destroyed, this, [this] { tabAlts = nullptr; });
} }

View file

@ -0,0 +1,27 @@
/**
* @file epoch_format.h
* @brief Shared timestamp formatting for the moderation tables.
*/
#ifndef EPOCH_FORMAT_H
#define EPOCH_FORMAT_H
#include <QDateTime>
#include <QString>
/**
* @brief Format a unix timestamp the way the moderation tabs show dates.
*
* @param ts Seconds since the epoch; 0 means "unknown".
* @param unknownText Label returned for 0, translated by the caller so the
* translation context stays with the tab.
*/
inline QString formatEpochTimestamp(quint64 ts, const QString &unknownText)
{
if (ts == 0) {
return unknownText;
}
return QDateTime::fromSecsSinceEpoch(ts).toLocalTime().toString(QStringLiteral("yyyy-MM-dd HH:mm"));
}
#endif // EPOCH_FORMAT_H