core: contest_scoreboard: disable pagination

This commit is contained in:
undefined committed 2022-09-26 23:59:33 +08:00
1 parent 757b5e6bc1
commit 9461499d7c
6 files changed
+32 -35

No files matched your search

+4 -5
View File
@@ -193,10 +193,9 @@ export class ContestBroadcastHandler extends ContestDetailBaseHandler {
export class ContestScoreboardHandler extends ContestDetailBaseHandler { export class ContestScoreboardHandler extends ContestDetailBaseHandler {
@param('tid', Types.ObjectID) @param('tid', Types.ObjectID)
@param('page', Types.PositiveInt, true)
@param('ext', Types.Range(['csv', 'html']), true) @param('ext', Types.Range(['csv', 'html']), true)
@param('ignoreLock', Types.Boolean, true) @param('ignoreLock', Types.Boolean, true)
async get(domainId: string, tid: ObjectID, page = 1, ext = '', ignoreLock = false) { async get(domainId: string, tid: ObjectID, ext = '', ignoreLock = false) {
if (ignoreLock && !this.user.own(this.tdoc)) { if (ignoreLock && !this.user.own(this.tdoc)) {
this.checkPerm(this.tdoc.rule === 'homework' this.checkPerm(this.tdoc.rule === 'homework'
? PERM.PERM_VIEW_HOMEWORK_HIDDEN_SCOREBOARD ? PERM.PERM_VIEW_HOMEWORK_HIDDEN_SCOREBOARD
@@ -210,14 +209,14 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler {
// Problem statistics display is allowed as we can view submission info in scoreboard. // Problem statistics display is allowed as we can view submission info in scoreboard.
...problem.PROJECTION_CONTEST_LIST, 'nSubmit', 'nAccept', ...problem.PROJECTION_CONTEST_LIST, 'nSubmit', 'nAccept',
]); ]);
const [, rows, udict, , nPages] = await contest.getScoreboard.call(this, domainId, tid, false, page, ignoreLock); const [, rows, udict] = await contest.getScoreboard.call(this, domainId, tid, false, ignoreLock);
this.response.template = 'contest_scoreboard.html'; this.response.template = 'contest_scoreboard.html';
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
const page_name = this.tdoc.rule === 'homework' const page_name = this.tdoc.rule === 'homework'
? 'homework_scoreboard' ? 'homework_scoreboard'
: 'contest_scoreboard'; : 'contest_scoreboard';
this.response.body = { this.response.body = {
tdoc: this.tdoc, rows, udict, nPages, page, pdict, page_name, tdoc: this.tdoc, rows, udict, pdict, page_name,
}; };
} }
@@ -227,7 +226,7 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler {
csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value?.toString().replace(/\n/g, ' ')).join(','))).join('\n')}`, csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value?.toString().replace(/\n/g, ' ')).join(','))).join('\n')}`,
html: (rows, tdoc) => this.renderHTML('contest_scoreboard_download_html.html', { rows, tdoc }), html: (rows, tdoc) => this.renderHTML('contest_scoreboard_download_html.html', { rows, tdoc }),
}; };
const [, rows] = await contest.getScoreboard.call(this, domainId, tid, true, 0, ignoreLock); const [, rows] = await contest.getScoreboard.call(this, domainId, tid, true, ignoreLock);
this.binary(await getContent[ext](rows, this.tdoc), `${this.tdoc.title}.${ext}`); this.binary(await getContent[ext](rows, this.tdoc), `${this.tdoc.title}.${ext}`);
} }
} }
+3 -3
View File
@@ -514,9 +514,9 @@ export interface ContestRule<T = any> {
) => Promise<ScoreboardRow>; ) => Promise<ScoreboardRow>;
scoreboard: ( scoreboard: (
this: ContestRule<T>, isExport: boolean, _: (s: string) => string, this: ContestRule<T>, isExport: boolean, _: (s: string) => string,
tdoc: Tdoc<30>, pdict: ProblemDict, cursor: Cursor<ContestStat & T>, page: number, tdoc: Tdoc<30>, pdict: ProblemDict, cursor: Cursor<ContestStat & T>,
) => Promise<[board: ScoreboardRow[], udict: Udict, nPages: number]>; ) => Promise<[board: ScoreboardRow[], udict: Udict]>;
ranked: (tdoc: Tdoc<30>, cursor: Cursor<ContestStat & T>) => Promise<[Array<[number, ContestStat & T]>, number]>; ranked: (tdoc: Tdoc<30>, cursor: Cursor<ContestStat & T>) => Promise<[number, ContestStat & T][]>;
} }
export type ContestRules = Dictionary<ContestRule>; export type ContestRules = Dictionary<ContestRule>;
+6 -7
View File
@@ -1,21 +1,20 @@
import { Cursor } from 'mongodb'; import { Cursor } from 'mongodb';
import paginate from './paginate';
type ranked = <T>(cursor: Cursor<T>, equ: ((a: T, b: T) => boolean), page?: number) => Promise<[[number, T][], number]>; type ranked = <T>(cursor: Cursor<T>, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>;
const ranked: ranked = async (cursor, equ, page) => { const ranked: ranked = async (cursor, equ) => {
let last = null; let last = null;
let r = page ? (page - 1) * 100 : 0; let r = 0;
let count = page ? r : 0; let count = 0;
const results = []; const results = [];
const [docs, nPages] = page ? await paginate(cursor, page, 100) : [await cursor.toArray(), 1]; const docs = await cursor.toArray();
for (const doc of docs) { for (const doc of docs) {
count++; count++;
if (!last || !equ(last, doc)) r = count; if (!last || !equ(last, doc)) r = count;
last = doc; last = doc;
results.push([r, doc]); results.push([r, doc]);
} }
return [results, nPages]; return results;
}; };
global.Hydro.lib.rank = ranked; global.Hydro.lib.rank = ranked;
+14 -14
View File
@@ -154,8 +154,8 @@ const acm = buildContestRule({
} }
return row; return row;
}, },
async scoreboard(isExport, _, tdoc, pdict, cursor, page) { async scoreboard(isExport, _, tdoc, pdict, cursor) {
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score && a.time === b.time, page); const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score && a.time === b.time);
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
const udict = await user.getList(tdoc.domainId, uids); const udict = await user.getList(tdoc.domainId, uids);
// Find first accept // Find first accept
@@ -186,7 +186,7 @@ const acm = buildContestRule({
), ),
)), )),
]; ];
return [rows, udict, nPages]; return [rows, udict];
}, },
async ranked(tdoc, cursor) { async ranked(tdoc, cursor) {
return await ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time); return await ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time);
@@ -272,8 +272,8 @@ const oi = buildContestRule({
} }
return row; return row;
}, },
async scoreboard(isExport, _, tdoc, pdict, cursor, page) { async scoreboard(isExport, _, tdoc, pdict, cursor) {
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page); const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score);
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
const udict = await user.getList(tdoc.domainId, uids); const udict = await user.getList(tdoc.domainId, uids);
const psdict = {}; const psdict = {};
@@ -307,7 +307,7 @@ const oi = buildContestRule({
), ),
)), )),
]; ];
return [rows, udict, nPages]; return [rows, udict];
}, },
async ranked(tdoc, cursor) { async ranked(tdoc, cursor) {
return await ranked(cursor, (a, b) => a.score === b.score); return await ranked(cursor, (a, b) => a.score === b.score);
@@ -453,8 +453,8 @@ const homework = buildContestRule({
} }
return row; return row;
}, },
async scoreboard(isExport, _, tdoc, pdict, cursor, page) { async scoreboard(isExport, _, tdoc, pdict, cursor) {
const [rankedTsdocs, nPages] = await ranked(cursor, (a, b) => a.score === b.score, page); const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score);
const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid);
const udict = await user.getList(tdoc.domainId, uids); const udict = await user.getList(tdoc.domainId, uids);
const columns = await this.scoreboardHeader(isExport, _, tdoc, pdict); const columns = await this.scoreboardHeader(isExport, _, tdoc, pdict);
@@ -464,7 +464,7 @@ const homework = buildContestRule({
([rank, tsdoc]) => this.scoreboardRow(isExport, _, tdoc, pdict, udict[tsdoc.uid], rank, tsdoc), ([rank, tsdoc]) => this.scoreboardRow(isExport, _, tdoc, pdict, udict[tsdoc.uid], rank, tsdoc),
)), )),
]; ];
return [rows, udict, nPages]; return [rows, udict];
}, },
async ranked(tdoc, cursor) { async ranked(tdoc, cursor) {
return await ranked(cursor, (a, b) => a.score === b.score); return await ranked(cursor, (a, b) => a.score === b.score);
@@ -679,18 +679,18 @@ export function canShowScoreboard(tdoc: Tdoc<30>, allowPermOverride = true) {
export async function getScoreboard( export async function getScoreboard(
this: Handler, domainId: string, tid: ObjectID, this: Handler, domainId: string, tid: ObjectID,
isExport = false, page: number, ignoreLock = false, isExport = false, ignoreLock = false,
): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, ProblemDict, number]> { ): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, ProblemDict]> {
const tdoc = await get(domainId, tid); const tdoc = await get(domainId, tid);
if (!canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid); if (!canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid);
if (ignoreLock) delete tdoc.lockAt; if (ignoreLock) delete tdoc.lockAt;
const tsdocsCursor = getMultiStatus(domainId, { docId: tid }).sort(RULES[tdoc.rule].statusSort); const tsdocsCursor = getMultiStatus(domainId, { docId: tid }).sort(RULES[tdoc.rule].statusSort);
const pdict = await problem.getList(domainId, tdoc.pids, true); const pdict = await problem.getList(domainId, tdoc.pids, true);
const [rows, udict, nPages] = await RULES[tdoc.rule].scoreboard( const [rows, udict] = await RULES[tdoc.rule].scoreboard(
isExport, this.translate.bind(this), isExport, this.translate.bind(this),
tdoc, pdict, tsdocsCursor, page, tdoc, pdict, tsdocsCursor,
); );
return [tdoc, rows, udict, pdict, nPages]; return [tdoc, rows, udict, pdict];
} }
export const statusText = (tdoc: Tdoc, tsdoc?: any) => ( export const statusText = (tdoc: Tdoc, tsdoc?: any) => (
+5 -5
View File
@@ -1,4 +1,4 @@
import { escapeRegExp, pick } from 'lodash'; import { escapeRegExp, pick, uniq } from 'lodash';
import LRU from 'lru-cache'; import LRU from 'lru-cache';
import { Collection, ObjectID } from 'mongodb'; import { Collection, ObjectID } from 'mongodb';
import { LoginError, UserAlreadyExistError, UserNotFoundError } from '../error'; import { LoginError, UserAlreadyExistError, UserNotFoundError } from '../error';
@@ -21,7 +21,7 @@ export const coll: Collection<Udoc> = db.collection('user');
// Virtual user, only for display in contest. // Virtual user, only for display in contest.
export const collV: Collection<VUdoc> = db.collection('vuser'); export const collV: Collection<VUdoc> = db.collection('vuser');
export const collGroup: Collection<GDoc> = db.collection('user.group'); export const collGroup: Collection<GDoc> = db.collection('user.group');
const cache = new LRU<string, User>({ max: 500, ttl: 300 * 1000 }); const cache = new LRU<string, User>({ max: 10000, ttl: 300 * 1000 });
export function deleteUserCache(udoc: User | Udoc | string | undefined | null, receiver = false) { export function deleteUserCache(udoc: User | Udoc | string | undefined | null, receiver = false) {
if (!udoc) return; if (!udoc) return;
@@ -192,10 +192,10 @@ class UserModel {
} }
static async getList(domainId: string, uids: number[]): Promise<Udict> { static async getList(domainId: string, uids: number[]): Promise<Udict> {
const _uids = new Set(uids);
const r: Udict = {}; const r: Udict = {};
// eslint-disable-next-line no-await-in-loop await Promise.all(uniq(uids).map(async (uid) => {
for (const uid of _uids) r[uid] = (await UserModel.getById(domainId, uid)) || new User(UserModel.defaultUser, {}); r[uid] = (await UserModel.getById(domainId, uid)) || new User(UserModel.defaultUser, {});
}));
return r; return r;
} }
@@ -74,7 +74,6 @@
{% endif %}{%- endfor -%} {% endif %}{%- endfor -%}
</tbody> </tbody>
</table> </table>
{{ paginator.render(page, nPages) }}
</div> </div>
</div> </div>
</div></div> </div></div>