Merge pull request #19083 from Aradhya-Tripathi/refactor-bg-submissions

fix: misc fixes (background submission)
This commit is contained in:
Ritwik Puri 2023-01-23 13:18:18 +05:30 committed by GitHub
commit 65a76cc0f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 92 deletions

View file

@ -604,6 +604,7 @@
{
"default": "0",
"depends_on": "eval: doc.is_submittable",
"description": "Enabling this will submit documents in background",
"fieldname": "queue_in_background",
"fieldtype": "Check",
"label": "Queue in Background"
@ -707,7 +708,7 @@
"link_fieldname": "reference_doctype"
}
],
"modified": "2022-12-14 09:47:27.315351",
"modified": "2023-01-04 17:23:09.206018",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",
@ -744,4 +745,4 @@
"states": [],
"track_changes": 1,
"translated_doctype": 1
}
}

View file

@ -3,11 +3,17 @@
frappe.ui.form.on("Submission Queue", {
refresh: function (frm) {
if (frm.doc.status === "Queued" && frm.doc.job_id) {
if (frm.doc.status === "Queued" && frappe.boot.user.roles.includes("System Manager")) {
frm.add_custom_button(__("Unlock Reference Document"), () => {
frappe.confirm(__("Are you sure you want to go ahead with this action?"), () => {
frm.call("unlock_doc");
});
frappe.confirm(
`
Are you sure you want to go ahead with this action?
Doing this could unlock other submissions of this document which are in queue (if present)
and could lead to non-ideal conditions.`,
() => {
frm.call("unlock_doc");
}
);
});
}
},

View file

@ -20,8 +20,9 @@
"fields": [
{
"fieldname": "job_id",
"fieldtype": "Data",
"fieldtype": "Link",
"label": "Job Id",
"options": "RQ Job",
"read_only": 1
},
{
@ -80,14 +81,14 @@
},
{
"fieldname": "exception",
"fieldtype": "Text",
"fieldtype": "Long Text",
"label": "Exception",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-11-12 16:48:37.797232",
"modified": "2023-01-23 12:45:53.997708",
"modified_by": "Administrator",
"module": "Core",
"name": "Submission Queue",
@ -102,6 +103,11 @@
"report": 1,
"role": "System Manager",
"share": 1
},
{
"if_owner": 1,
"read": 1,
"role": "All"
}
],
"sort_field": "modified",

View file

@ -4,8 +4,6 @@
from urllib.parse import quote
from rq import get_current_job
from rq.exceptions import NoSuchJobError
from rq.job import Job
import frappe
from frappe import _
@ -13,7 +11,6 @@ from frappe.desk.doctype.notification_log.notification_log import enqueue_create
from frappe.model.document import Document
from frappe.monitor import add_data_to_monitor
from frappe.utils import now, time_diff_in_seconds
from frappe.utils.background_jobs import get_redis_conn
from frappe.utils.data import cint
@ -39,6 +36,7 @@ class SubmissionQueue(Document):
frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))
def insert(self, to_be_queued_doc: Document, action: str):
self.status = "Queued"
self.to_be_queued_doc = to_be_queued_doc
self.action_for_queuing = action
super().insert(ignore_permissions=True)
@ -70,6 +68,7 @@ class SubmissionQueue(Document):
def background_submission(self, to_be_queued_doc: Document, action_for_queuing: str):
# Set the job id for that submission doctype
self.update_job_id(get_current_job().id)
_action = action_for_queuing.lower()
if _action == "update":
_action = "submit"
@ -85,7 +84,7 @@ class SubmissionQueue(Document):
)
values = {"status": "Finished"}
except Exception:
values = {"status": "Failed", "exception": frappe.get_traceback()}
values = {"status": "Failed", "exception": frappe.get_traceback(with_context=True)}
frappe.db.rollback()
values["ended_at"] = now()
@ -96,22 +95,27 @@ class SubmissionQueue(Document):
if submission_status == "Failed":
doctype = self.doctype
docname = self.name
message = _("Submission of {0} {1} with action {2} failed")
message = _("Action {0} failed on {1} {2}. View it {3}")
else:
doctype = self.ref_doctype
docname = self.ref_docname
message = _("Submission of {0} {1} with action {2} completed successfully")
message = _("Action {0} completed successfully on {1} {2}. View it {3}")
message = message.format(
frappe.bold(str(self.ref_doctype)), frappe.bold(self.ref_docname), frappe.bold(action)
message_replacements = (
frappe.bold(action),
frappe.bold(str(self.ref_doctype)),
frappe.bold(str(self.ref_docname)),
)
time_diff = time_diff_in_seconds(now(), self.created_at)
if cint(time_diff) <= 60:
frappe.publish_realtime(
"msgprint",
{
"message": message
+ f". View it <a href='/app/{quote(doctype.lower().replace(' ', '-'))}/{quote(docname)}'><b>here</b></a>",
"message": message.format(
*message_replacements,
f"<a href='/app/{quote(doctype.lower().replace(' ', '-'))}/{quote(docname)}'><b>here</b></a>",
),
"alert": True,
"indicator": "red" if submission_status == "Failed" else "green",
},
@ -122,50 +126,27 @@ class SubmissionQueue(Document):
"type": "Alert",
"document_type": doctype,
"document_name": docname,
"subject": message,
"subject": message.format(*message_replacements, "here"),
}
notify_to = frappe.db.get_value("User", self.enqueued_by, fieldname="email")
enqueue_create_notification([notify_to], notification_doc)
def _unlock_reference_doc(self):
"""
Only execute if self.job_id is defined.
"""
try:
job = Job.fetch(self.job_id, connection=get_redis_conn())
status = job.get_status(refresh=True)
exc = job.exc_info
except NoSuchJobError:
exc = None
status = "failed"
if status in ("queued", "started"):
frappe.msgprint(_("Document in queue for execution!"))
return
self.queued_doc.unlock()
values = (
{"status": "Finished"} if status == "finished" else {"status": "Failed", "exception": exc}
)
frappe.db.set_value(self.doctype, self.name, values, update_modified=False)
frappe.msgprint(_("Document Unlocked"))
@frappe.whitelist()
def unlock_doc(self):
# NOTE: this can lead to some weird unlocking/locking behaviours.
# for example: hitting unlock on a submission could lead to unlocking of another submission
# of the same reference document.
if self.status != "Queued" and not self.job_id:
if self.status != "Queued":
return
self._unlock_reference_doc()
self.queued_doc.unlock()
frappe.msgprint(_("Document Unlocked"))
def queue_submission(doc: Document, action: str, alert: bool = True):
queue = frappe.new_doc("Submission Queue")
queue.state = "Queued"
queue.ref_doctype = doc.doctype
queue.ref_docname = doc.name
queue.insert(doc, action)
@ -185,9 +166,25 @@ def get_latest_submissions(doctype, docname):
# NOTE: not used creation as orderby intentianlly as we have used update_modified=False everywhere
# hence assuming modified will be equal to creation for submission queue documents
dt = "Submission Queue"
filters = {"ref_doctype": doctype, "ref_docname": docname}
return {
"latest_submission": frappe.db.get_value(dt, filters),
"latest_failed_submission": frappe.db.get_value(dt, filters | {"status": "Failed"}),
}
latest_submission = frappe.db.get_value(
"Submission Queue",
filters={"ref_doctype": doctype, "ref_docname": docname},
fieldname=["name", "exception", "status"],
)
out = None
if latest_submission:
out = {
"latest_submission": latest_submission[0],
"exc": format_tb(latest_submission[1]),
"status": latest_submission[2],
}
return out
def format_tb(traceback: str | None = None):
if not traceback:
return
return traceback.strip().split("\n")[-1]

View file

@ -2047,16 +2047,12 @@ frappe.ui.form.Form = class FrappeForm {
this.doc.docstatus === 0
)
) {
if (wrapper.length) {
wrapper.hide();
wrapper.html("");
}
wrapper.length && wrapper.remove();
return;
}
if (!wrapper.length) {
wrapper = $('<div class="submission-queue-banner form-message yellow">');
wrapper = $('<div class="submission-queue-banner form-message">');
this.layout.wrapper.prepend(wrapper);
}
@ -2066,49 +2062,40 @@ frappe.ui.form.Form = class FrappeForm {
args: { doctype: this.doctype, docname: this.docname },
})
.then((r) => {
if (r.message.latest_submission) {
if (r.message?.latest_submission) {
// if we are here that means some submission(s) were queued and are in queued/failed state
let col_width = 4;
let failed_link = "";
let submission_label = __("Previous Submission");
let secondary = "";
let div_class = "col-md-12";
if (r.message.latest_failed_submission) {
if (r.message.latest_failed_submission !== r.message.latest_submission) {
col_width = 3;
failed_link = `<div class="col-md-3">
<a href='/app/submission-queue/${r.message.latest_failed_submission}'>${__(
"Previous Falied Submission"
)}</a>
</div>`;
} else {
submission_label = __("Previous Falied Submission");
}
if (r.message.exc) {
secondary = `: <span>${r.message.exc}</span>`;
} else {
div_class = "col-md-6";
secondary = `
</div>
<div class="col-md-6">
<a href='/app/submission-queue?ref_doctype=${encodeURIComponent(
this.doctype
)}&ref_docname=${encodeURIComponent(this.docname)}'>${__(
"All Submissions"
)}</a>
`;
}
let html = `
<div class="row">
<div class="col-md-${col_width}">
<strong>${__("Submission Status:")}</strong>
<div class="row">
<div class="${div_class}">
<a href='/app/submission-queue/${r.message.latest_submission}'>${submission_label} (${r.message.status})</a>${secondary}
</div>
</div>
<div class="col-md-${col_width}">
<a href='/app/submission-queue/${r.message.latest_submission}'>${submission_label}</a>
</div>
${failed_link}
<div class="col-md-${col_width}">
<a href='/app/submission-queue?ref_doctype=${encodeURIComponent(
this.doctype
)}&ref_docname=${encodeURIComponent(this.docname)}'>${__(
"All Submissions"
)}</a>
</div>
</div>
`;
`;
wrapper.show();
wrapper.removeClass("red").removeClass("yellow");
wrapper.addClass(r.message.status == "Failed" ? "red" : "yellow");
wrapper.html(html);
} else {
wrapper.hide();
wrapper.html("");
wrapper.remove();
}
});
}

View file

@ -163,7 +163,7 @@ $input-height: 28px !default;
--bg-green: var(--dark-green-50);
--bg-yellow: var(--yellow-50);
--bg-orange: var(--orange-50);
--bg-red: var(--red-50);
--bg-red: var(--red-100);
--bg-gray: var(--gray-200);
--bg-grey: var(--gray-200);
--bg-light-gray: var(--gray-100);