rework jobs package 1/2

This commit is contained in:
Inga Kirschnick 2023-03-09 14:55:52 +01:00
parent 5f27ce2801
commit d6fcaa97bf
10 changed files with 134 additions and 94 deletions

View File

@ -25,11 +25,11 @@ def delete_tesseract_model(tesseract_ocr_pipeline_model_id):
args=(current_app._get_current_object(), topm.id) args=(current_app._get_current_object(), topm.id)
) )
thread.start() thread.start()
resonse_data = { response_data = {
'message': \ 'message': \
f'Tesseract OCR Pipeline Model "{topm.title}" marked for deletion' f'Tesseract OCR Pipeline Model "{topm.title}" marked for deletion'
} }
response = jsonify(resonse_data) response = jsonify(response_data)
response.status_code = 202 response.status_code = 202
return response return response
@ -39,6 +39,8 @@ def delete_tesseract_model(tesseract_ocr_pipeline_model_id):
@permission_required('CONTRIBUTE') @permission_required('CONTRIBUTE')
@content_negotiation(consumes='application/json', produces='application/json') @content_negotiation(consumes='application/json', produces='application/json')
def update_tesseract_ocr_pipeline_model_is_public(tesseract_ocr_pipeline_model_id): def update_tesseract_ocr_pipeline_model_is_public(tesseract_ocr_pipeline_model_id):
# body: jsonify({'is_public': True})
# body: jsonify(False)
is_public = request.json is_public = request.json
if not isinstance(is_public, bool): if not isinstance(is_public, bool):
abort(400) abort(400)

View File

@ -2,4 +2,4 @@ from flask import Blueprint
bp = Blueprint('jobs', __name__) bp = Blueprint('jobs', __name__)
from . import routes from . import routes, json_routes

69
app/jobs/json_routes.py Normal file
View File

@ -0,0 +1,69 @@
from flask import (abort, current_app, jsonify)
from flask_login import current_user, login_required
from threading import Thread
import os
from app import db
from app.decorators import admin_required, content_negotiation
from app.models import Job, JobInput, JobResult, JobStatus
from . import bp
@bp.route('/<hashid:job_id>', methods=['DELETE'])
@login_required
@content_negotiation(produces='application/json')
def delete_job(job_id):
def _delete_job(app, job_id):
with app.app_context():
job = Job.query.get(job_id)
job.delete()
db.session.commit()
job = Job.query.get_or_404(job_id)
if not (job.user == current_user or current_user.is_administrator()):
abort(403)
thread = Thread(
target=_delete_job,
args=(current_app._get_current_object(), job_id)
)
thread.start()
response_data = {
'message': \
f'Job "{job.title}" marked for deletion'
}
response = jsonify(response_data)
response.status_code = 202
return response
@bp.route('/<hashid:job_id>/log')
@login_required
@admin_required
def job_log(job_id):
job = Job.query.get_or_404(job_id)
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
response = {'errors': {'message': 'Job status is not completed or failed'}}
return response, 409
with open(os.path.join(job.path, 'pipeline_data', 'logs', 'pyflow_log.txt')) as log_file:
log = log_file.read()
return log, 200, {'Content-Type': 'text/plain; charset=utf-8'}
@bp.route('/<hashid:job_id>/restart', methods=['POST'])
@login_required
def restart_job(job_id):
def _restart_job(app, job_id):
with app.app_context():
job = Job.query.get(job_id)
job.restart()
db.session.commit()
job = Job.query.get_or_404(job_id)
if not (job.user == current_user or current_user.is_administrator()):
abort(403)
if job.status == JobStatus.FAILED:
response = {'errors': {'message': 'Job status is not "failed"'}}
return response, 409
thread = Thread(
target=_restart_job,
args=(current_app._get_current_object(), job_id)
)
thread.start()
return {}, 202

View File

@ -26,62 +26,6 @@ def job(job_id):
) )
@bp.route('/<hashid:job_id>', methods=['DELETE'])
@login_required
def delete_job(job_id):
def _delete_job(app, job_id):
with app.app_context():
job = Job.query.get(job_id)
job.delete()
db.session.commit()
job = Job.query.get_or_404(job_id)
if not (job.user == current_user or current_user.is_administrator()):
abort(403)
thread = Thread(
target=_delete_job,
args=(current_app._get_current_object(), job_id)
)
thread.start()
return {}, 202
@bp.route('/<hashid:job_id>/log')
@login_required
@admin_required
def job_log(job_id):
job = Job.query.get_or_404(job_id)
if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
response = {'errors': {'message': 'Job status is not completed or failed'}}
return response, 409
with open(os.path.join(job.path, 'pipeline_data', 'logs', 'pyflow_log.txt')) as log_file:
log = log_file.read()
return log, 200, {'Content-Type': 'text/plain; charset=utf-8'}
@bp.route('/<hashid:job_id>/restart', methods=['POST'])
@login_required
def restart_job(job_id):
def _restart_job(app, job_id):
with app.app_context():
job = Job.query.get(job_id)
job.restart()
db.session.commit()
job = Job.query.get_or_404(job_id)
if not (job.user == current_user or current_user.is_administrator()):
abort(403)
if job.status == JobStatus.FAILED:
response = {'errors': {'message': 'Job status is not "failed"'}}
return response, 409
thread = Thread(
target=_restart_job,
args=(current_app._get_current_object(), job_id)
)
thread.start()
return {}, 202
@bp.route('/<hashid:job_id>/inputs/<hashid:job_input_id>/download') @bp.route('/<hashid:job_id>/inputs/<hashid:job_input_id>/download')
@login_required @login_required
def download_job_input(job_id, job_input_id): def download_job_input(job_id, job_input_id):

View File

@ -1,4 +1,4 @@
Requests = {}; let Requests = {};
Requests.JSONfetch = (input, init={}) => { Requests.JSONfetch = (input, init={}) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -0,0 +1,23 @@
/*****************************************************************************
* Jobs *
* Fetch requests for /jobs routes *
*****************************************************************************/
Requests.jobs = {};
Requests.jobs.entity = {};
Requests.jobs.entity.delete = (jobId) => {
let input = `/jobs/${jobId}`;
let init = {
method: 'DELETE'
};
return Requests.JSONfetch(input, init);
}
Requests.jobs.entity.log = (jobId) => {
let input = `/jobs/${jobId}/log`;
let init = {
method: 'GET'
};
return Requests.JSONfetch(input, init);
}

View File

@ -2,11 +2,6 @@ class JobDisplay extends ResourceDisplay {
constructor(displayElement) { constructor(displayElement) {
super(displayElement); super(displayElement);
this.jobId = this.displayElement.dataset.jobId; this.jobId = this.displayElement.dataset.jobId;
this.displayElement
.querySelector('.action-button[data-action="delete-request"]')
.addEventListener('click', (event) => {
Utils.deleteJobRequest(this.userId, this.jobId);
});
this.displayElement this.displayElement
.querySelector('.action-button[data-action="get-log-request"]') .querySelector('.action-button[data-action="get-log-request"]')
.addEventListener('click', (event) => { .addEventListener('click', (event) => {

View File

@ -493,32 +493,6 @@ class Utils {
job = {}; job = {};
} }
let modalElement = Utils.HTMLToElement(
`
<div class="modal">
<div class="modal-content">
<h4>Confirm Job deletion</h4>
<p>Do you really want to delete the Job <b>${job?.title}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="action-button btn modal-close red waves-effect waves-light" data-action="confirm">Delete</a>
</div>
</div>
`
);
document.querySelector('#modals').appendChild(modalElement);
let modal = M.Modal.init(
modalElement,
{
dismissible: false,
onCloseEnd: () => {
modal.destroy();
modalElement.remove();
}
}
);
let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]'); let confirmElement = modalElement.querySelector('.action-button[data-action="confirm"]');
confirmElement.addEventListener('click', (event) => { confirmElement.addEventListener('click', (event) => {
let jobTitle = job?.title; let jobTitle = job?.title;

View File

@ -61,7 +61,8 @@
'js/Requests/contributions/contributions.js', 'js/Requests/contributions/contributions.js',
'js/Requests/contributions/spacy_nlp_pipeline_models.js', 'js/Requests/contributions/spacy_nlp_pipeline_models.js',
'js/Requests/contributions/tesseract_ocr_pipeline_models.js', 'js/Requests/contributions/tesseract_ocr_pipeline_models.js',
'js/Requests/Corpora.js' 'js/Requests/Corpora.js',
'js/Requests/jobs/jobs.js'
%} %}
<script src="{{ ASSET_URL }}"></script> <script src="{{ ASSET_URL }}"></script>
{%- endassets %} {%- endassets %}

View File

@ -79,10 +79,10 @@
</div> </div>
<div class="card-action right-align"> <div class="card-action right-align">
{% if current_user.is_administrator() %} {% if current_user.is_administrator() %}
<a class="action-button btn disabled waves-effect waves-light" data-action="get-log-request"><i class="material-icons left">text_snippet</i>Log</a> <a class="btn disabled waves-effect waves-light modal-trigger" id="log-job-modal"><i class="material-icons left">text_snippet</i>Log</a>
{% endif %} {% endif %}
<a class="action-button btn disabled waves-effect waves-light" data-action="restart-request"><i class="material-icons left">repeat</i>Restart</a> <a class="action-button btn disabled waves-effect waves-light" data-action="restart-request"><i class="material-icons left">repeat</i>Restart</a>
<a class="action-button btn red waves-effect waves-light" data-action="delete-request"><i class="material-icons left">delete</i>Delete</a> <a class="btn red waves-effect waves-light modal-trigger" href="#delete-job-modal"><i class="material-icons left">delete</i>Delete</a>
</div> </div>
</div> </div>
</div> </div>
@ -112,10 +112,42 @@
</div> </div>
{% endblock page_content %} {% endblock page_content %}
{% block modals %}
{{ super() }}
<div class="modal" id="delete-job-modal">
<div class="modal-content">
<h4>Confirm Job deletion</h4>
<p>Do you really want to delete the Job <b>{{job.title}}</b>? All files will be permanently deleted!</p>
</div>
<div class="modal-footer">
<a class="action-button btn modal-close waves-effect waves-light" data-action="cancel">Cancel</a>
<a class="btn modal-close red waves-effect waves-light" id="delete-job-request">Delete</a>
</div>
</div>
<div class="modal" id="log-job-modal">
<div class="modal-content">
<h4>Job logs</h4>
<pre><code>${text}</code></pre>
</div>
<div class="modal-footer">
<a class="btn modal-close waves-effect waves-light">Close</a>
</div>
</div>
{% endblock modals %}
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script> <script>
let jobDisplay = new JobDisplay(document.querySelector('#job-display')); let jobDisplay = new JobDisplay(document.querySelector('#job-display'));
let deleteJobRequestElement = document.querySelector('#delete-job-request');
let logJobModalElement = document.querySelector('#log-job-modal');
deleteJobRequestElement.addEventListener('click', (event) => {
Requests.jobs.entity.delete({{ job.hashid|tojson }});
});
logJobModalElement.addEventListener('click', (event) => {
Requests.jobs.entity.log({{ job.hashid|tojson }});
});
</script> </script>
{% endblock scripts %} {% endblock scripts %}