Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core&ui: add bulk rejudge #763

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
83 changes: 81 additions & 2 deletions packages/hydrooj/src/handler/manage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import { exec } from 'child_process';
import { inspect } from 'util';
import * as yaml from 'js-yaml';
import { omit } from 'lodash';
import { Filter, ObjectId } from 'mongodb';
import Schema from 'schemastery';
import {
CannotEditSuperAdminError, NotLaunchedByPM2Error, UserNotFoundError, ValidationError,
CannotEditSuperAdminError, ContestNotFoundError,
NotLaunchedByPM2Error, ProblemConfigError, ProblemNotFoundError, UserNotFoundError, ValidationError,
} from '../error';
import { RecordDoc } from '../interface';
import { Logger } from '../logger';
import { PRIV, STATUS } from '../model/builtin';
import * as contest from '../model/contest';
import domain from '../model/domain';
import problem from '../model/problem';
import record from '../model/record';
import * as setting from '../model/setting';
import * as system from '../model/system';
Expand Down Expand Up @@ -48,7 +53,7 @@ function set(key: string, value: any) {
}

class SystemHandler extends Handler {
async prepare() {
async _prepare() {
this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
}
}
Expand Down Expand Up @@ -319,6 +324,79 @@ class SystemUserPrivHandler extends SystemHandler {
}
}

class SystemBulkRejudgeHandler extends SystemHandler {
@requireSudo
async get() {
this.response.template = 'manage_bulk_rejudge.html';
this.response.body = {
filterPid: null,
filterTid: null,
filterUidOrName: null,
filterLang: null,
filterStatus: null,
message: '',
};
}

@requireSudo
@param('pid', Types.ProblemId, true)
@param('tid', Types.ObjectId, true)
@param('uidOrName', Types.UidOrName, true)
@param('lang', Types.String, true)
@param('status', Types.Int, true)
@param('draft', Types.Boolean)
async post(domainId: string, pid: string, tid: ObjectId, uidOrName: string, lang: string, status: number, draft = false) {
if (!pid && !tid && !uidOrName && !lang && status === undefined) throw new ValidationError();
const q: Filter<RecordDoc> = { 'files.hack': { $exists: false } };
let tdoc = null;
if (uidOrName) {
const udoc = await user.getById(domainId, +uidOrName)
|| await user.getByUname(domainId, uidOrName)
|| await user.getByEmail(domainId, uidOrName);
if (udoc) q.uid = udoc._id;
else throw new UserNotFoundError(uidOrName);
}
if (tid) {
if ([record.RECORD_GENERATE, record.RECORD_PRETEST].includes(tid)) throw new ValidationError('tid');
tdoc = await contest.get(domainId, tid);
if (tdoc) q.contest = tid;
else throw new ContestNotFoundError(domainId, tid);
} else q.contest = { $nin: [record.RECORD_GENERATE, record.RECORD_PRETEST] };
if (pid) {
if (typeof pid === 'string' && tdoc && /^[A-Z]$/.test(pid)) {
pid = tdoc.pids[parseInt(pid, 36) - 10];
}
const pdoc = await problem.get(domainId, pid);
if (pdoc) q.pid = pdoc.docId;
else throw new ProblemNotFoundError(domainId, pid);
if (!pdoc.config || typeof pdoc.config === 'string') throw new ProblemConfigError();
}
if (lang) q.lang = lang;
if (typeof status === 'number') q.status = status;
this.response.body = {
filterPid: pid,
filterTid: tid,
filterUidOrName: uidOrName,
filterLang: lang,
filterStatus: status,
};
if (draft) {
const count = await record.coll.count(q);
this.response.body.message = `${count} records found, please confirm.`;
this.response.template = 'manage_bulk_rejudge.html';
} else {
const rdocs = await record.getMulti(domainId, q).project({ _id: 1, contest: 1 }).toArray();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be an ultra slow query taking several minutes on large collection, converting into background task would be preferred.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert request to task?

const priority = await record.submissionPriority(this.user._id, -10000 - rdocs.length * 5 - 50);
await record.reset(domainId, rdocs.map((rdoc) => rdoc._id), true);
await Promise.all([
record.judge(domainId, rdocs.filter((i) => i.contest).map((i) => i._id), priority, { detail: false }, { rejudge: true }),
record.judge(domainId, rdocs.filter((i) => !i.contest).map((i) => i._id), priority, {}, { rejudge: true }),
]);
this.back();
}
}
}

export async function apply(ctx) {
ctx.Route('manage', '/manage', SystemMainHandler);
ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler);
Expand All @@ -327,5 +405,6 @@ export async function apply(ctx) {
ctx.Route('manage_config', '/manage/config', SystemConfigHandler);
ctx.Route('manage_user_import', '/manage/userimport', SystemUserImportHandler);
ctx.Route('manage_user_priv', '/manage/userpriv', SystemUserPrivHandler);
ctx.Route('manage_bulk_rejudge', '/manage/bulkrejudge', SystemBulkRejudgeHandler);
ctx.Connection('manage_check', '/manage/check-conn', SystemCheckConnHandler);
}
1 change: 1 addition & 0 deletions packages/hydrooj/src/lib/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ inject('ControlPanel', 'manage_dashboard');
inject('ControlPanel', 'manage_script');
inject('ControlPanel', 'manage_user_import');
inject('ControlPanel', 'manage_user_priv');
inject('ControlPanel', 'manage_bulk_rejudge');
inject('ControlPanel', 'manage_setting');
inject('ControlPanel', 'manage_config');
inject('DomainManage', 'domain_dashboard', { family: 'Properties', icon: 'info' });
Expand Down
3 changes: 2 additions & 1 deletion packages/ui-default/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Current Status: Status
Handled Requests: Req
Hint::icon::difficulty: The difficulty of the problems in Hydro will be calculated by algorithm, depending on current submission, sometimes the difficulty may not so exact.
Hint::icon::switch_account: As a superadmin, you can switch to anyone's account.
manage_bulk_rejudge: Bulk Rejudge
manage: System Manage
no_translation_warn: <blockquote class="warn">This part of content is under translation.</blockquote>
page.problem_detail.sidebar.show_category: Click to Show
Expand Down Expand Up @@ -64,4 +65,4 @@ user_logout: Logout
user_lostpass: Lost Password
user_register: Register
wiki_help: Help
You can also upload your avatar to Gravatar and it will be automatically updated here.: You can also upload your avatar to <a href="https://gravatar.com/">Gravatar</a> and it will be automatically updated here.
You can also upload your avatar to Gravatar and it will be automatically updated here.: You can also upload your avatar to <a href="https://gravatar.com/">Gravatar</a> and it will be automatically updated here.
3 changes: 3 additions & 0 deletions packages/ui-default/locales/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ Bold: 加粗
Boom!: 炸了!
Browser: 浏览器
Built-in: 内置
Bulk Rejudge: 批量重测
Bulletin: 公告
By clicking the button, you will become a member of the domain {0}.: 点击按钮,您将成为域 {0} 的成员。
By Contest: 由比赛
Expand Down Expand Up @@ -530,6 +531,7 @@ Logout This Session: 注销该会话
Logout: 登出
Lost Password: 忘记密码
Lucky: 手气不错
manage_bulk_rejudge: 批量重测
manage_config: 配置管理
manage_dashboard: 控制面板
manage_join_applications: 加域申请
Expand Down Expand Up @@ -746,6 +748,7 @@ Registered at: 注册于
Rejudge all submissions: 重测整题
Rejudge problems: 重测题目
Rejudge records: 重测记录
Rejudge request submitted.: 重测请求已提交。
Rejudge: 重测
Related Discussions: 相关讨论
Related: 相关
Expand Down
47 changes: 47 additions & 0 deletions packages/ui-default/pages/manage_bulk_rejudge.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import $ from 'jquery';
import CustomSelectAutoComplete from 'vj/components/autocomplete/CustomSelectAutoComplete';
import ProblemSelectAutoComplete from 'vj/components/autocomplete/ProblemSelectAutoComplete';
import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
import Notification from 'vj/components/notification';
import { NamedPage } from 'vj/misc/Page';
import { delay, i18n, request } from 'vj/utils';

const page = new NamedPage('manage_bulk_rejudge', async () => {
UserSelectAutoComplete.getOrConstruct($('[name="uidOrName"]'), {
clearDefaultValue: false,
});
ProblemSelectAutoComplete.getOrConstruct($('[name="pid"]'), {
clearDefaultValue: false,
});
const prefixes = new Set(Object.keys(window.LANGS).filter((i) => i.includes('.')).map((i) => i.split('.')[0]));
const langs = Object.keys(window.LANGS).filter((i) => !prefixes.has(i)).map((i) => (
{ name: `${i.includes('.') ? `${window.LANGS[i.split('.')[0]].display}/` : ''}${window.LANGS[i].display}`, _id: i }
));
CustomSelectAutoComplete.getOrConstruct($('[name=lang]'), { multi: false, data: langs });

async function post(draft) {
try {
const res = await request.post('', {
pid: $('[name="pid"]').val(),
tid: $('[name="tid"]').val(),
uidOrName: $('[name="uidOrName"]').val(),
lang: $('[name="lang"]').val(),
draft,
});
if (!draft) {
Notification.success(i18n('Rejudge request submitted.'));
await delay(2000);
window.location.reload();
} else {
$('[name="message"]').text(res.message);
}
} catch (error) {
Notification.error(error.message);
}
}

$('[name="preview"]').on('click', () => post(true));
$('[name="submit"]').on('click', () => post(false));
});

export default page;
58 changes: 58 additions & 0 deletions packages/ui-default/templates/manage_bulk_rejudge.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{% import "components/user.html" as user %}
{% extends "manage_base.html" %}
{% block manage_content %}
<div class="section">
<div class="section__header">
<h1 class="section__title" data-heading>{{ _('Bulk Rejudge') }}</h1>
</div>
<div class="section__body">
{{ noscript_note.render() }}
<p name="message">{{ message }}</p>
<div class="row">
<div class="medium-6 columns">
<label class="filter-user">
{{ _('By Username / UID') }}
<input name="uidOrName" type="text" class="textbox" value="{{ filterUidOrName }}" autocomplete="off" data-autofocus>
</label>
</div>
<div class="medium-6 columns">
<label>
{{ _('By Problem') }}
<input name="pid" type="text" class="textbox" value="{{ filterPid }}">
</label>
</div>
</div>
<div class="row">
<div class="medium-6 columns">
<label>
{{ _('By Contest') }}
<input name="tid" type="text" class="textbox" value="{{ filterTid }}">
</label>
</div>
<div class="medium-6 columns">
<label>
{{ _('By Language') }}
<input name="lang" type="text" class="textbox" value="{{ filterLang }}">
</label>
</div>
</div>
<div class="row">
<div class="medium-6 columns">
<label>
{{ _('By Status') }}
</label>
<select class="select" name="status">
<option value="" {% if typeof(filterStatus) != 'number' %}selected{% else %}{% endif %}>{{ _('All Submissions') }}</option>
{%- for k,v in utils.status.STATUS_TEXTS -%}
<option value="{{k}}" {% if filterStatus == k %}selected{% else %}{% endif %}>{{v}}</option>
{%- endfor -%}
</select>
</div>
</div>
<div class="row"><div class="columns">
<button name="preview" class="rounded primary button">{{ _('Preview') }}</button>
<button name="submit" class="rounded button">{{ _('Submit') }}</button>
</div></div>
</div>
</div>
{% endblock %}
2 changes: 1 addition & 1 deletion packages/ui-default/templates/manage_user_import.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h1 class="section__title" data-heading>{{ _('Import User') }}</h1>
name:'users',
nospellcheck: true
}) }}
<div class="row"><div class="column">
<div class="row"><div class="columns">
<button name="preview" class="rounded primary button">{{ _('Preview') }}</button>
<button name="submit" class="rounded button">{{ _('Submit') }}</button>
</div></div>
Expand Down
18 changes: 18 additions & 0 deletions packages/ui-default/templates/record_main.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ <h1 class="section__title">{{ _('Filter') }}</h1>
{% endif %}
</div>
</div>
{% if handler.user.hasPriv(PRIV.PRIV_EDIT_SYSTEM) %}
<form method="post" action="{{ url('manage_bulk_rejudge') }}">
<div class="section">
<div class="section__header">
<h1 class="section__title">{{ _('Bulk Rejudge') }}</h1>
<div class="section__tools">
<input type="hidden" name="pid" value="{{ filterPid }}">
<input type="hidden" name="tid" value="{{ filterTid }}">
<input type="hidden" name="uidOrName" value="{{ filterUidOrName }}">
<input type="hidden" name="lang" value="{{ filterLang }}">
<input type="hidden" name="status" value="{{ filterStatus }}">
<input type="hidden" name="draft" value="true">
<button type="submit" class="rounded primary button">{{ _('Bulk Rejudge') }}</button>
</div>
</div>
</div>
</form>
{% endif %}
{% if statistics %}
<div class="section">
<div class="section__header">
Expand Down
Loading