mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
feat(dashboard): UGC list sorts by when a model was made and by how long it took
The models list has separate Made (when) and Took (the time, CPU and estimated memory) columns, each sortable in either direction; /api/ugc takes sort=made (processed_at) besides sort=slowest, and the gallery can sort by recently made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -241,7 +241,7 @@ namespace UgcRoutes {
|
|||||||
"A page of player models (kind=model) or of car and rocket assemblies (kind=modular: one per combination of modules, however many builds use "
|
"A page of player models (kind=model) or of car and rocket assemblies (kind=modular: one per combination of modules, however many builds use "
|
||||||
"it). Query: q= (\"state:\", \"kind:\"/\"type:\" (a build type, e.g. build6), \"owner:\", \"account:\", \"property:\", \"name:\", "
|
"it). Query: q= (\"state:\", \"kind:\"/\"type:\" (a build type, e.g. build6), \"owner:\", \"account:\", \"property:\", \"name:\", "
|
||||||
"\"lot:\"/\"module:\" (a LOT or a module's name), \"id:\", or plain text across names, owners and ids), state=, type=, sort=newest|oldest|owner|name|"
|
"\"lot:\"/\"module:\" (a LOT or a module's name), \"id:\", or plain text across names, owners and ids), state=, type=, sort=newest|oldest|owner|name|"
|
||||||
"bricks|triangles|slowest (models) or newest|oldest|references|name (assemblies), reverse=1 (the sort's other direction), page= (from 0), size= (1-200). {items, total, page, size, counts, "
|
"bricks|triangles|slowest|made (models) or newest|oldest|references|name (assemblies), reverse=1 (the sort's other direction), page= (from 0), size= (1-200). {items, total, page, size, counts, "
|
||||||
"kinds, ugcPublicUrl, canManage}",
|
"kinds, ugcPublicUrl, canManage}",
|
||||||
[](HTTPReply& reply, const HTTPContext& context) {
|
[](HTTPReply& reply, const HTTPContext& context) {
|
||||||
const bool modular = QueryValue(context.queryString, "kind") == "modular";
|
const bool modular = QueryValue(context.queryString, "kind") == "modular";
|
||||||
@@ -260,7 +260,7 @@ namespace UgcRoutes {
|
|||||||
query.search = parsed.search;
|
query.search = parsed.search;
|
||||||
query.state = parsed.state;
|
query.state = parsed.state;
|
||||||
static const std::map<std::string, IUgcLookup::eSort> SORTS = { { "newest", IUgcLookup::eSort::NEWEST }, { "oldest", IUgcLookup::eSort::OLDEST },
|
static const std::map<std::string, IUgcLookup::eSort> SORTS = { { "newest", IUgcLookup::eSort::NEWEST }, { "oldest", IUgcLookup::eSort::OLDEST },
|
||||||
{ "owner", IUgcLookup::eSort::OWNER }, { "name", IUgcLookup::eSort::NAME }, { "bricks", IUgcLookup::eSort::BRICKS }, { "triangles", IUgcLookup::eSort::TRIANGLES }, { "slowest", IUgcLookup::eSort::SLOWEST } };
|
{ "owner", IUgcLookup::eSort::OWNER }, { "name", IUgcLookup::eSort::NAME }, { "bricks", IUgcLookup::eSort::BRICKS }, { "triangles", IUgcLookup::eSort::TRIANGLES }, { "slowest", IUgcLookup::eSort::SLOWEST }, { "made", IUgcLookup::eSort::MADE } };
|
||||||
if (const auto it = SORTS.find(sortText); it != SORTS.end()) query.sort = it->second;
|
if (const auto it = SORTS.find(sortText); it != SORTS.end()) query.sort = it->second;
|
||||||
query.reverse = reverse;
|
query.reverse = reverse;
|
||||||
query.offset = page * size;
|
query.offset = page * size;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
var STATES = { pending: ['Waiting', 'secondary'], done: ['Made', 'success'], failed: ['Failed', 'danger'], empty: ['Empty', 'light'] };
|
var STATES = { pending: ['Waiting', 'secondary'], done: ['Made', 'success'], failed: ['Failed', 'danger'], empty: ['Empty', 'light'] };
|
||||||
var SORTS = {
|
var SORTS = {
|
||||||
model: [['newest', 'Newest'], ['oldest', 'Oldest'], ['bricks', 'Most bricks'], ['triangles', 'Most triangles'], ['slowest', 'Slowest to make'], ['owner', 'Owner'], ['name', 'File name']],
|
model: [['newest', 'Newest'], ['oldest', 'Oldest'], ['bricks', 'Most bricks'], ['triangles', 'Most triangles'], ['made', 'Recently made'], ['slowest', 'Slowest to make'], ['owner', 'Owner'], ['name', 'File name']],
|
||||||
modular: [['newest', 'Newest'], ['oldest', 'Oldest'], ['references', 'Most builds'], ['name', 'Name']]
|
modular: [['newest', 'Newest'], ['oldest', 'Oldest'], ['references', 'Most builds'], ['name', 'Name']]
|
||||||
};
|
};
|
||||||
// The list's state, as in the address: ?kind=&q=&state=&type=&sort=&page= (from 1)&view=, and the open item (&item=, &build=)
|
// The list's state, as in the address: ?kind=&q=&state=&type=&sort=&page= (from 1)&view=, and the open item (&item=, &build=)
|
||||||
@@ -141,7 +141,8 @@
|
|||||||
column('State', function (i) {
|
column('State', function (i) {
|
||||||
return badge(i.state) + (i.attempts ? ' <span class="small text-body-secondary">' + esc(i.attempts) + ' attempt' + (i.attempts === 1 ? '' : 's') + '</span>' : '') + waitBadge(i);
|
return badge(i.state) + (i.attempts ? ' <span class="small text-body-secondary">' + esc(i.attempts) + ' attempt' + (i.attempts === 1 ? '' : 's') + '</span>' : '') + waitBadge(i);
|
||||||
}),
|
}),
|
||||||
column('Made', function (i) { return '<span class="small">' + esc(madeText(i)) + '</span>'; }, 'slowest', true),
|
column('Made', function (i) { return '<span class="small">' + (i.processedAt ? esc(fmt.unix(i.processedAt)) : '') + '</span>'; }, 'made', true),
|
||||||
|
column('Took', function (i) { return '<span class="small">' + esc(costText(i).replace(/^took /, '')) + '</span>'; }, 'slowest', true),
|
||||||
column('Size', function (i) { return '<span class="small">' + (i.bricks ? esc(i.bricks) + ' bricks<br>' + esc(i.triangles.toLocaleString()) + ' triangles' : '') + '</span>'; }, 'bricks', true),
|
column('Size', function (i) { return '<span class="small">' + (i.bricks ? esc(i.bricks) + ' bricks<br>' + esc(i.triangles.toLocaleString()) + ' triangles' : '') + '</span>'; }, 'bricks', true),
|
||||||
column('File', function (i) { return '<span class="small">' + esc(i.detail || '') + '</span>' + errorText(i); }, 'name'),
|
column('File', function (i) { return '<span class="small">' + esc(i.detail || '') + '</span>' + errorText(i); }, 'name'),
|
||||||
column('', function (i) { return '<div class="text-end text-nowrap">' + actions(i, i.id) + '</div>'; })
|
column('', function (i) { return '<div class="text-end text-nowrap">' + actions(i, i.id) + '</div>'; })
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
<div id="gallery" class="ugc-gallery mb-2"></div>
|
<div id="gallery" class="ugc-gallery mb-2"></div>
|
||||||
<div class="card d-none" id="listCard"><div class="card-body p-2">
|
<div class="card d-none" id="listCard"><div class="card-body p-2">
|
||||||
<div id="modelTableBox"><table class="table table-sm align-middle mb-0 w-100 table-stack" id="ugcModelTable">
|
<div id="modelTableBox"><table class="table table-sm align-middle mb-0 w-100 table-stack" id="ugcModelTable">
|
||||||
<thead><tr><th>Icon</th><th>ID</th><th>Owner</th><th>State</th><th>Made</th><th>Size</th><th>File</th><th></th></tr></thead></table></div>
|
<thead><tr><th>Icon</th><th>ID</th><th>Owner</th><th>State</th><th>Made</th><th>Took</th><th>Size</th><th>File</th><th></th></tr></thead></table></div>
|
||||||
<div id="assemblyTableBox" class="d-none"><table class="table table-sm align-middle mb-0 w-100 table-stack" id="ugcAssemblyTable">
|
<div id="assemblyTableBox" class="d-none"><table class="table table-sm align-middle mb-0 w-100 table-stack" id="ugcAssemblyTable">
|
||||||
<thead><tr><th>Icon</th><th>Newest build</th><th>Type</th><th>Modules</th><th>State</th><th>Builds</th><th>Owners</th><th></th></tr></thead></table></div>
|
<thead><tr><th>Icon</th><th>Newest build</th><th>Type</th><th>Modules</th><th>State</th><th>Builds</th><th>Owners</th><th></th></tr></thead></table></div>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public:
|
|||||||
BRICKS, // models: the most bricks first
|
BRICKS, // models: the most bricks first
|
||||||
TRIANGLES, // models: the most triangles first
|
TRIANGLES, // models: the most triangles first
|
||||||
SLOWEST, // the longest last make first (process_ms)
|
SLOWEST, // the longest last make first (process_ms)
|
||||||
|
MADE, // the most recently made (or attempted) first (processed_at)
|
||||||
};
|
};
|
||||||
|
|
||||||
// A page of one kind: all of them or those matching the search (the same matching as SearchUgc), in a state or any
|
// A page of one kind: all of them or those matching the search (the same matching as SearchUgc), in a state or any
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ namespace UgcLookupSql {
|
|||||||
case eSort::NAME: return std::string("ORDER BY ") + (modular ? "b.ldf_config" : "u.filename") + dir(false) + ", " + id + " DESC ";
|
case eSort::NAME: return std::string("ORDER BY ") + (modular ? "b.ldf_config" : "u.filename") + dir(false) + ", " + id + " DESC ";
|
||||||
case eSort::BRICKS: return modular ? "ORDER BY " + id + dir(true) + " " : "ORDER BY u.brick_count" + std::string(dir(true)) + ", u.id DESC ";
|
case eSort::BRICKS: return modular ? "ORDER BY " + id + dir(true) + " " : "ORDER BY u.brick_count" + std::string(dir(true)) + ", u.id DESC ";
|
||||||
case eSort::TRIANGLES: return modular ? "ORDER BY " + id + dir(true) + " " : "ORDER BY u.triangle_count" + std::string(dir(true)) + ", u.id DESC ";
|
case eSort::TRIANGLES: return modular ? "ORDER BY " + id + dir(true) + " " : "ORDER BY u.triangle_count" + std::string(dir(true)) + ", u.id DESC ";
|
||||||
|
case eSort::MADE: return "ORDER BY " + std::string(modular ? "b" : "u") + ".processed_at" + dir(true) + ", " + id + " DESC ";
|
||||||
case eSort::SLOWEST: return "ORDER BY " + std::string(modular ? "b" : "u") + ".process_ms" + dir(true) + ", " + id + " DESC ";
|
case eSort::SLOWEST: return "ORDER BY " + std::string(modular ? "b" : "u") + ".process_ms" + dir(true) + ", " + id + " DESC ";
|
||||||
default: return "ORDER BY " + id + dir(true) + " ";
|
default: return "ORDER BY " + id + dir(true) + " ";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,6 +182,8 @@ TEST_F(UgcLookupSqlTests, ListsPagesWithCounts) {
|
|||||||
query.reverse = true; // the oldest first
|
query.reverse = true; // the oldest first
|
||||||
EXPECT_EQ(List(query, false).first, (std::vector<int64_t>{ 1000, 1001 }));
|
EXPECT_EQ(List(query, false).first, (std::vector<int64_t>{ 1000, 1001 }));
|
||||||
query.reverse = false;
|
query.reverse = false;
|
||||||
|
query.sort = IUgcLookup::eSort::MADE; // neither has been made: ties go newest first
|
||||||
|
EXPECT_EQ(List(query, false).first, (std::vector<int64_t>{ 1001, 1000 }));
|
||||||
query.sort = IUgcLookup::eSort::SLOWEST; // the tower took 5.2 s; the boat isn't made
|
query.sort = IUgcLookup::eSort::SLOWEST; // the tower took 5.2 s; the boat isn't made
|
||||||
EXPECT_EQ(List(query, false).first, (std::vector<int64_t>{ 1000, 1001 }));
|
EXPECT_EQ(List(query, false).first, (std::vector<int64_t>{ 1000, 1001 }));
|
||||||
query.reverse = true;
|
query.reverse = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user