prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  [Feature Request] Clean failed jobs from queue
  Currently, there is no feature that allows to clean failed jobs from the job queue. Therefore, failed jobs will accumulate. It should be possible to clear failed jobs.
  
  Discussion reference: https://discuss.erpnext.com/t/cleaning-failed-background-jobs/37886
  
  ![grafik](https://user-images.githubusercontent.com/26505250/42841847-a71cfc7e-8a0b-11e8-9275-c183bae973c7.png)
  
  Observed in Frappe 10.1.x
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `frappe/core/page/background_jobs/background_jobs.py`
  Content:
  ```
  1 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2 # MIT License. See license.txt
  3 
  4 from __future__ import unicode_literals
  5 import frappe
  6 
  7 from rq import Queue, Worker
  8 from frappe.utils.background_jobs import get_redis_conn
  9 from frappe.utils import format_datetime, cint, convert_utc_to_user_timezone
  10 from frappe.utils.scheduler import is_scheduler_inactive
  11 from frappe import _
  12 
  13 colors = {
  14 	'queued': 'orange',
  15 	'failed': 'red',
  16 	'started': 'blue',
  17 	'finished': 'green'
  18 }
  19 
  20 @frappe.whitelist()
  21 def get_info(show_failed=False):
  22 	conn = get_redis_conn()
  23 	queues = Queue.all(conn)
  24 	workers = Worker.all(conn)
  25 	jobs = []
  26 
  27 	def add_job(j, name):
  28 		if j.kwargs.get('site')==frappe.local.site:
  29 			jobs.append({
  30 				'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \
  31 					or j.kwargs.get('kwargs', {}).get('job_type') \
  32 					or str(j.kwargs.get('job_name')),
  33 				'status': j.get_status(), 'queue': name,
  34 				'creation': format_datetime(convert_utc_to_user_timezone(j.created_at)),
  35 				'color': colors[j.get_status()]
  36 			})
  37 			if j.exc_info:
  38 				jobs[-1]['exc_info'] = j.exc_info
  39 
  40 	for w in workers:
  41 		j = w.get_current_job()
  42 		if j:
  43 			add_job(j, w.name)
  44 
  45 	for q in queues:
  46 		if q.name != 'failed':
  47 			for j in q.get_jobs(): add_job(j, q.name)
  48 
  49 	if cint(show_failed):
  50 		for q in queues:
  51 			if q.name == 'failed':
  52 				for j in q.get_jobs()[:10]: add_job(j, q.name)
  53 
  54 	return jobs
  55 
  56 @frappe.whitelist()
  57 def get_scheduler_status():
  58 	if is_scheduler_inactive():
  59 		return [_("Inactive"), "red"]
  60 	return [_("Active"), "green"]
  61
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/frappe/core/page/background_jobs/background_jobs.py b/frappe/core/page/background_jobs/background_jobs.py
  --- a/frappe/core/page/background_jobs/background_jobs.py
  +++ b/frappe/core/page/background_jobs/background_jobs.py
  @@ -1,58 +1,88 @@
   # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
   # MIT License. See license.txt
   
  -from __future__ import unicode_literals
  -import frappe
  +import json
  +from typing import TYPE_CHECKING, Dict, List
   
   from rq import Queue, Worker
  +
  +import frappe
  +from frappe import _
  +from frappe.utils import convert_utc_to_user_timezone, format_datetime
   from frappe.utils.background_jobs import get_redis_conn
  -from frappe.utils import format_datetime, cint, convert_utc_to_user_timezone
   from frappe.utils.scheduler import is_scheduler_inactive
  -from frappe import _
   
  -colors = {
  +if TYPE_CHECKING:
  +	from rq.job import Job
  +
  +JOB_COLORS = {
   	'queued': 'orange',
   	'failed': 'red',
   	'started': 'blue',
   	'finished': 'green'
   }
   
  +
   @frappe.whitelist()
  -def get_info(show_failed=False):
  +def get_info(show_failed=False) -> List[Dict]:
  +	if isinstance(show_failed, str):
  +		show_failed = json.loads(show_failed)
  +
   	conn = get_redis_conn()
   	queues = Queue.all(conn)
   	workers = Worker.all(conn)
   	jobs = []
   
  -	def add_job(j, name):
  -		if j.kwargs.get('site')==frappe.local.site:
  -			jobs.append({
  -				'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \
  -					or j.kwargs.get('kwargs', {}).get('job_type') \
  -					or str(j.kwargs.get('job_name')),
  -				'status': j.get_status(), 'queue': name,
  -				'creation': format_datetime(convert_utc_to_user_timezone(j.created_at)),
  -				'color': colors[j.get_status()]
  -			})
  -			if j.exc_info:
  -				jobs[-1]['exc_info'] = j.exc_info
  -
  -	for w in workers:
  -		j = w.get_current_job()
  -		if j:
  -			add_job(j, w.name)
  -
  -	for q in queues:
  -		if q.name != 'failed':
  -			for j in q.get_jobs(): add_job(j, q.name)
  -
  -	if cint(show_failed):
  -		for q in queues:
  -			if q.name == 'failed':
  -				for j in q.get_jobs()[:10]: add_job(j, q.name)
  +	def add_job(job: 'Job', name: str) -> None:
  +		if job.kwargs.get('site') == frappe.local.site:
  +			job_info = {
  +				'job_name': job.kwargs.get('kwargs', {}).get('playbook_method')
  +					or job.kwargs.get('kwargs', {}).get('job_type')
  +					or str(job.kwargs.get('job_name')),
  +				'status': job.get_status(),
  +				'queue': name,
  +				'creation': format_datetime(convert_utc_to_user_timezone(job.created_at)),
  +				'color': JOB_COLORS[job.get_status()]
  +			}
  +
  +			if job.exc_info:
  +				job_info['exc_info'] = job.exc_info
  +
  +			jobs.append(job_info)
  +
  +	# show worker jobs
  +	for worker in workers:
  +		job = worker.get_current_job()
  +		if job:
  +			add_job(job, worker.name)
  +
  +	for queue in queues:
  +		# show active queued jobs
  +		if queue.name != 'failed':
  +			for job in queue.jobs:
  +				add_job(job, queue.name)
  +
  +		# show failed jobs, if requested
  +		if show_failed:
  +			fail_registry = queue.failed_job_registry
  +			for job_id in fail_registry.get_job_ids():
  +				job = queue.fetch_job(job_id)
  +				add_job(job, queue.name)
   
   	return jobs
   
  +
  +@frappe.whitelist()
  +def remove_failed_jobs():
  +	conn = get_redis_conn()
  +	queues = Queue.all(conn)
  +	for queue in queues:
  +		fail_registry = queue.failed_job_registry
  +		for job_id in fail_registry.get_job_ids():
  +			job = queue.fetch_job(job_id)
  +			fail_registry.remove(job, delete_job=True)
  +
  +
   @frappe.whitelist()
   def get_scheduler_status():
   	if is_scheduler_inactive():
  
verification_info: {"golden_diff": "diff --git a/frappe/core/page/background_jobs/background_jobs.py b/frappe/core/page/background_jobs/background_jobs.py\n--- a/frappe/core/page/background_jobs/background_jobs.py\n+++ b/frappe/core/page/background_jobs/background_jobs.py\n@@ -1,58 +1,88 @@\n # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n # MIT License. See license.txt\n \n-from __future__ import unicode_literals\n-import frappe\n+import json\n+from typing import TYPE_CHECKING, Dict, List\n \n from rq import Queue, Worker\n+\n+import frappe\n+from frappe import _\n+from frappe.utils import convert_utc_to_user_timezone, format_datetime\n from frappe.utils.background_jobs import get_redis_conn\n-from frappe.utils import format_datetime, cint, convert_utc_to_user_timezone\n from frappe.utils.scheduler import is_scheduler_inactive\n-from frappe import _\n \n-colors = {\n+if TYPE_CHECKING:\n+\tfrom rq.job import Job\n+\n+JOB_COLORS = {\n \t'queued': 'orange',\n \t'failed': 'red',\n \t'started': 'blue',\n \t'finished': 'green'\n }\n \n+\n @frappe.whitelist()\n-def get_info(show_failed=False):\n+def get_info(show_failed=False) -> List[Dict]:\n+\tif isinstance(show_failed, str):\n+\t\tshow_failed = json.loads(show_failed)\n+\n \tconn = get_redis_conn()\n \tqueues = Queue.all(conn)\n \tworkers = Worker.all(conn)\n \tjobs = []\n \n-\tdef add_job(j, name):\n-\t\tif j.kwargs.get('site')==frappe.local.site:\n-\t\t\tjobs.append({\n-\t\t\t\t'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \\\n-\t\t\t\t\tor j.kwargs.get('kwargs', {}).get('job_type') \\\n-\t\t\t\t\tor str(j.kwargs.get('job_name')),\n-\t\t\t\t'status': j.get_status(), 'queue': name,\n-\t\t\t\t'creation': format_datetime(convert_utc_to_user_timezone(j.created_at)),\n-\t\t\t\t'color': colors[j.get_status()]\n-\t\t\t})\n-\t\t\tif j.exc_info:\n-\t\t\t\tjobs[-1]['exc_info'] = j.exc_info\n-\n-\tfor w in workers:\n-\t\tj = w.get_current_job()\n-\t\tif j:\n-\t\t\tadd_job(j, w.name)\n-\n-\tfor q in queues:\n-\t\tif q.name != 'failed':\n-\t\t\tfor j in q.get_jobs(): add_job(j, q.name)\n-\n-\tif cint(show_failed):\n-\t\tfor q in queues:\n-\t\t\tif q.name == 'failed':\n-\t\t\t\tfor j in q.get_jobs()[:10]: add_job(j, q.name)\n+\tdef add_job(job: 'Job', name: str) -> None:\n+\t\tif job.kwargs.get('site') == frappe.local.site:\n+\t\t\tjob_info = {\n+\t\t\t\t'job_name': job.kwargs.get('kwargs', {}).get('playbook_method')\n+\t\t\t\t\tor job.kwargs.get('kwargs', {}).get('job_type')\n+\t\t\t\t\tor str(job.kwargs.get('job_name')),\n+\t\t\t\t'status': job.get_status(),\n+\t\t\t\t'queue': name,\n+\t\t\t\t'creation': format_datetime(convert_utc_to_user_timezone(job.created_at)),\n+\t\t\t\t'color': JOB_COLORS[job.get_status()]\n+\t\t\t}\n+\n+\t\t\tif job.exc_info:\n+\t\t\t\tjob_info['exc_info'] = job.exc_info\n+\n+\t\t\tjobs.append(job_info)\n+\n+\t# show worker jobs\n+\tfor worker in workers:\n+\t\tjob = worker.get_current_job()\n+\t\tif job:\n+\t\t\tadd_job(job, worker.name)\n+\n+\tfor queue in queues:\n+\t\t# show active queued jobs\n+\t\tif queue.name != 'failed':\n+\t\t\tfor job in queue.jobs:\n+\t\t\t\tadd_job(job, queue.name)\n+\n+\t\t# show failed jobs, if requested\n+\t\tif show_failed:\n+\t\t\tfail_registry = queue.failed_job_registry\n+\t\t\tfor job_id in fail_registry.get_job_ids():\n+\t\t\t\tjob = queue.fetch_job(job_id)\n+\t\t\t\tadd_job(job, queue.name)\n \n \treturn jobs\n \n+\n+@frappe.whitelist()\n+def remove_failed_jobs():\n+\tconn = get_redis_conn()\n+\tqueues = Queue.all(conn)\n+\tfor queue in queues:\n+\t\tfail_registry = queue.failed_job_registry\n+\t\tfor job_id in fail_registry.get_job_ids():\n+\t\t\tjob = queue.fetch_job(job_id)\n+\t\t\tfail_registry.remove(job, delete_job=True)\n+\n+\n @frappe.whitelist()\n def get_scheduler_status():\n \tif is_scheduler_inactive():\n", "issue": "[Feature Request] Clean failed jobs from queue\nCurrently, there is no feature that allows to clean failed jobs from the job queue. Therefore, failed jobs will accumulate. It should be possible to clear failed jobs.\r\n\r\nDiscussion reference: https://discuss.erpnext.com/t/cleaning-failed-background-jobs/37886\r\n\r\n![grafik](https://user-images.githubusercontent.com/26505250/42841847-a71cfc7e-8a0b-11e8-9275-c183bae973c7.png)\r\n\r\nObserved in Frappe 10.1.x\n", "before_files": [{"content": "# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n# MIT License. See license.txt\n\nfrom __future__ import unicode_literals\nimport frappe\n\nfrom rq import Queue, Worker\nfrom frappe.utils.background_jobs import get_redis_conn\nfrom frappe.utils import format_datetime, cint, convert_utc_to_user_timezone\nfrom frappe.utils.scheduler import is_scheduler_inactive\nfrom frappe import _\n\ncolors = {\n\t'queued': 'orange',\n\t'failed': 'red',\n\t'started': 'blue',\n\t'finished': 'green'\n}\n\n@frappe.whitelist()\ndef get_info(show_failed=False):\n\tconn = get_redis_conn()\n\tqueues = Queue.all(conn)\n\tworkers = Worker.all(conn)\n\tjobs = []\n\n\tdef add_job(j, name):\n\t\tif j.kwargs.get('site')==frappe.local.site:\n\t\t\tjobs.append({\n\t\t\t\t'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \\\n\t\t\t\t\tor j.kwargs.get('kwargs', {}).get('job_type') \\\n\t\t\t\t\tor str(j.kwargs.get('job_name')),\n\t\t\t\t'status': j.get_status(), 'queue': name,\n\t\t\t\t'creation': format_datetime(convert_utc_to_user_timezone(j.created_at)),\n\t\t\t\t'color': colors[j.get_status()]\n\t\t\t})\n\t\t\tif j.exc_info:\n\t\t\t\tjobs[-1]['exc_info'] = j.exc_info\n\n\tfor w in workers:\n\t\tj = w.get_current_job()\n\t\tif j:\n\t\t\tadd_job(j, w.name)\n\n\tfor q in queues:\n\t\tif q.name != 'failed':\n\t\t\tfor j in q.get_jobs(): add_job(j, q.name)\n\n\tif cint(show_failed):\n\t\tfor q in queues:\n\t\t\tif q.name == 'failed':\n\t\t\t\tfor j in q.get_jobs()[:10]: add_job(j, q.name)\n\n\treturn jobs\n\n@frappe.whitelist()\ndef get_scheduler_status():\n\tif is_scheduler_inactive():\n\t\treturn [_(\"Inactive\"), \"red\"]\n\treturn [_(\"Active\"), \"green\"]\n", "path": "frappe/core/page/background_jobs/background_jobs.py"}], "after_files": [{"content": "# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors\n# MIT License. See license.txt\n\nimport json\nfrom typing import TYPE_CHECKING, Dict, List\n\nfrom rq import Queue, Worker\n\nimport frappe\nfrom frappe import _\nfrom frappe.utils import convert_utc_to_user_timezone, format_datetime\nfrom frappe.utils.background_jobs import get_redis_conn\nfrom frappe.utils.scheduler import is_scheduler_inactive\n\nif TYPE_CHECKING:\n\tfrom rq.job import Job\n\nJOB_COLORS = {\n\t'queued': 'orange',\n\t'failed': 'red',\n\t'started': 'blue',\n\t'finished': 'green'\n}\n\n\n@frappe.whitelist()\ndef get_info(show_failed=False) -> List[Dict]:\n\tif isinstance(show_failed, str):\n\t\tshow_failed = json.loads(show_failed)\n\n\tconn = get_redis_conn()\n\tqueues = Queue.all(conn)\n\tworkers = Worker.all(conn)\n\tjobs = []\n\n\tdef add_job(job: 'Job', name: str) -> None:\n\t\tif job.kwargs.get('site') == frappe.local.site:\n\t\t\tjob_info = {\n\t\t\t\t'job_name': job.kwargs.get('kwargs', {}).get('playbook_method')\n\t\t\t\t\tor job.kwargs.get('kwargs', {}).get('job_type')\n\t\t\t\t\tor str(job.kwargs.get('job_name')),\n\t\t\t\t'status': job.get_status(),\n\t\t\t\t'queue': name,\n\t\t\t\t'creation': format_datetime(convert_utc_to_user_timezone(job.created_at)),\n\t\t\t\t'color': JOB_COLORS[job.get_status()]\n\t\t\t}\n\n\t\t\tif job.exc_info:\n\t\t\t\tjob_info['exc_info'] = job.exc_info\n\n\t\t\tjobs.append(job_info)\n\n\t# show worker jobs\n\tfor worker in workers:\n\t\tjob = worker.get_current_job()\n\t\tif job:\n\t\t\tadd_job(job, worker.name)\n\n\tfor queue in queues:\n\t\t# show active queued jobs\n\t\tif queue.name != 'failed':\n\t\t\tfor job in queue.jobs:\n\t\t\t\tadd_job(job, queue.name)\n\n\t\t# show failed jobs, if requested\n\t\tif show_failed:\n\t\t\tfail_registry = queue.failed_job_registry\n\t\t\tfor job_id in fail_registry.get_job_ids():\n\t\t\t\tjob = queue.fetch_job(job_id)\n\t\t\t\tadd_job(job, queue.name)\n\n\treturn jobs\n\n\n@frappe.whitelist()\ndef remove_failed_jobs():\n\tconn = get_redis_conn()\n\tqueues = Queue.all(conn)\n\tfor queue in queues:\n\t\tfail_registry = queue.failed_job_registry\n\t\tfor job_id in fail_registry.get_job_ids():\n\t\t\tjob = queue.fetch_job(job_id)\n\t\t\tfail_registry.remove(job, delete_job=True)\n\n\n@frappe.whitelist()\ndef get_scheduler_status():\n\tif is_scheduler_inactive():\n\t\treturn [_(\"Inactive\"), \"red\"]\n\treturn [_(\"Active\"), \"green\"]\n", "path": "frappe/core/page/background_jobs/background_jobs.py"}]}