Skip to content

Commit

Permalink
Merge pull request #493 from 1nF0rmed/1nFOrmed/add-job-sorting
Browse files Browse the repository at this point in the history
Adds Sort Order #477
  • Loading branch information
cjlapao authored Aug 5, 2024
2 parents 66c83f5 + 79db166 commit 1628a43
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 24 deletions.
2 changes: 1 addition & 1 deletion rq_dashboard/static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ span.loading {
width: auto;
}

#select-registry, #select-per-page, #select-queue {
#select-registry, #select-per-page, #select-queue, #select-sort-order {
width: auto;
}

Expand Down
10 changes: 8 additions & 2 deletions rq_dashboard/templates/rq_dashboard/jobs.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
<option {% if registry_name == 'failed' %} selected {% endif %}>failed</option>
<option {% if registry_name == 'canceled' %} selected {% endif %}>canceled</option>
</select>
<div class="input-group-prepend input-group-append">
<span class="input-group-text">Sort Order:</span>
</div>
<select class="custom-select" id="select-sort-order" title="Job Sort Order">
<option {% if order == 'asc' %} selected {% endif %} value="asc">Ascending</option>
<option {% if order == 'dsc' %} selected {% endif %} value="dsc">Descending</option>
</select>
</div>

<p class="intro">
Expand All @@ -43,8 +50,7 @@
<a href="{{ url_for('rq_dashboard.requeue_all', queue_name=queue.name) }}" id="requeue-all-btn" class="btn btn-outline-warning btn-sm"
style="float: right; margin-right: 8px;">Requeue All</a>
{% endif %}
This list below contains all the queued jobs on queue <strong>{{ queue.name }}</strong>, sorted by
age (oldest on top).</p>
This list below contains all the queued jobs on queue <strong>{{ queue.name }}</strong>.</p>

<div class="table-responsive">
<table id="jobs" class="table table-bordered">
Expand Down
12 changes: 6 additions & 6 deletions rq_dashboard/templates/rq_dashboard/scripts/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ document.querySelectorAll('.ellipsify').forEach(function (elem) {
}
});

var url_for_jobs_data = function(queue_name, registry_name, per_page, page) {
var url = {{ rq_url_prefix|tojson|safe }} + {{ current_instance|tojson|safe }} + '/data/jobs/' + encodeURIComponent(queue_name) + '/' + encodeURIComponent(registry_name) + '/' + encodeURIComponent(per_page) + '/' + encodeURIComponent(page) + '.json';
var url_for_jobs_data = function(queue_name, registry_name, per_page, order, page) {
var url = {{ rq_url_prefix|tojson|safe }} + {{ current_instance|tojson|safe }} + '/data/jobs/' + encodeURIComponent(queue_name) + '/' + encodeURIComponent(registry_name) + '/' + encodeURIComponent(per_page) + '/'+ encodeURIComponent(order) + '/' + encodeURIComponent(page) + '.json';
return url;
};

var url_for_jobs_view = function(queue_name, registry_name, per_page, page) {
var url = {{ rq_url_prefix|tojson|safe }} + {{ current_instance|tojson|safe }} + '/view/jobs/' + encodeURIComponent(queue_name) + '/' + encodeURIComponent(registry_name) + '/' + encodeURIComponent(per_page) + '/' + encodeURIComponent(page);
var url_for_jobs_view = function(queue_name, registry_name, per_page, order, page) {
var url = {{ rq_url_prefix|tojson|safe }} + {{ current_instance|tojson|safe }} + '/view/jobs/' + encodeURIComponent(queue_name) + '/' + encodeURIComponent(registry_name) + '/' + encodeURIComponent(per_page) + '/'+ encodeURIComponent(order) + '/' + encodeURIComponent(page);
return url;
};

Expand Down Expand Up @@ -74,8 +74,8 @@ var api = {
});
},

getJobs: function(queue_name, registry_name, per_page, page, cb) {
$.getJSON(url_for_jobs_data(queue_name, registry_name, per_page, page), function(data) {
getJobs: function(queue_name, registry_name, per_page, order, page, cb) {
$.getJSON(url_for_jobs_data(queue_name, registry_name, per_page, order, page), function(data) {
var jobs = data.jobs;
var pagination = data.pagination;
cb(jobs, pagination);
Expand Down
19 changes: 15 additions & 4 deletions rq_dashboard/templates/rq_dashboard/scripts/jobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
$placeholderEl.show();

// Fetch the available jobs on the queue
api.getJobs({{ queue.name|tojson|safe }}, {{registry_name|tojson|safe}}, {{ per_page|tojson|safe}}, {{ page|tojson|safe }}, function(jobs, pagination, err) {
api.getJobs({{ queue.name|tojson|safe }}, {{registry_name|tojson|safe}}, {{ per_page|tojson|safe}}, {{ order|tojson|safe }}, {{ page|tojson|safe }}, function(jobs, pagination, err) {
// Return immediately in case of error
if (err) {
return done();
Expand Down Expand Up @@ -210,7 +210,7 @@
if (!queue_name) {
queue_name = 'default'
}
var url = url_for_jobs_view(queue_name, $('#select-registry').val(), $('#select-per-page').val(), 1)
var url = url_for_jobs_view(queue_name, $('#select-registry').val(), $('#select-per-page').val(), $('#select-sort-order').val(), 1)
$(location).attr('href', url);
});
});
Expand All @@ -221,7 +221,18 @@
if (!queue_name) {
queue_name = 'default'
}
var url = url_for_jobs_view(queue_name, $('#select-registry').val(), $('#select-per-page').val(), 1)
var url = url_for_jobs_view(queue_name, $('#select-registry').val(), $('#select-per-page').val(), $('#select-sort-order').val(), 1)
$(location).attr('href', url);
});
});

$('#select-sort-order').change(function() {
$(document).ready( function() {
queue_name = $('#select-queue').val();
if (!queue_name) {
queue_name = 'default'
}
var url = url_for_jobs_view(queue_name, $('#select-registry').val(), $('#select-per-page').val(), $('#select-sort-order').val(), 1)
$(location).attr('href', url);
});
});
Expand All @@ -232,7 +243,7 @@
if (!queue_name) {
queue_name = 'default'
}
var url = url_for_jobs_view(queue_name, $('#select-registry').val(), $('#select-per-page').val(), 1)
var url = url_for_jobs_view(queue_name, $('#select-registry').val(), $('#select-per-page').val(), $('#select-sort-order').val(), 1)
$(location).attr('href', url);
});
});
Expand Down
40 changes: 33 additions & 7 deletions rq_dashboard/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def serialize_queues(instance_number, queues):
queue_name=q.name,
registry_name="queued",
per_page="8",
order="asc",
page="1",
),
failed_job_registry_count=FailedJobRegistry(q.name).count,
Expand All @@ -143,6 +144,7 @@ def serialize_queues(instance_number, queues):
queue_name=q.name,
registry_name="failed",
per_page="8",
order="asc",
page="1",
),
started_job_registry_count=StartedJobRegistry(q.name).count,
Expand All @@ -152,6 +154,7 @@ def serialize_queues(instance_number, queues):
queue_name=q.name,
registry_name="started",
per_page="8",
order="asc",
page="1",
),
deferred_job_registry_count=DeferredJobRegistry(q.name).count,
Expand All @@ -161,6 +164,7 @@ def serialize_queues(instance_number, queues):
queue_name=q.name,
registry_name="deferred",
per_page="8",
order="asc",
page="1",
),
finished_job_registry_count=FinishedJobRegistry(q.name).count,
Expand All @@ -170,6 +174,7 @@ def serialize_queues(instance_number, queues):
queue_name=q.name,
registry_name="finished",
per_page="8",
order="asc",
page="1",
),
canceled_job_registry_count=CanceledJobRegistry(q.name).count,
Expand All @@ -179,6 +184,7 @@ def serialize_queues(instance_number, queues):
queue_name=q.name,
registry_name="canceled",
per_page="8",
order="asc",
page="1",
),
scheduled_job_registry_count=ScheduledJobRegistry(q.name).count,
Expand All @@ -188,6 +194,7 @@ def serialize_queues(instance_number, queues):
queue_name=q.name,
registry_name="scheduled",
per_page="8",
order="asc",
page="1",
),
)
Expand Down Expand Up @@ -248,7 +255,7 @@ def favicon():
)


def get_queue_registry_jobs_count(queue_name, registry_name, offset, per_page):
def get_queue_registry_jobs_count(queue_name, registry_name, offset, per_page, order):
queue = Queue(queue_name, serializer=config.serializer)
if registry_name != "queued":
if per_page >= 0:
Expand All @@ -270,7 +277,18 @@ def get_queue_registry_jobs_count(queue_name, registry_name, offset, per_page):
current_queue = queue
total_items = current_queue.count

job_ids = current_queue.get_job_ids(offset, per_page)

if order == 'dsc':
end = total_items - offset
start = max(0, end - per_page)
else:
start = offset
end = start + per_page

job_ids = current_queue.get_job_ids(start, end)
if order == 'dsc':
job_ids.reverse()

current_queue_jobs = [queue.fetch_job(job_id) for job_id in job_ids]
jobs = [serialize_job(job) for job in current_queue_jobs if job]

Expand Down Expand Up @@ -341,13 +359,14 @@ def workers_overview(instance_number):
"queue_name": None,
"registry_name": "queued",
"per_page": "8",
"order": "asc",
"page": "1",
},
)
@blueprint.route(
"/<int:instance_number>/view/jobs/<queue_name>/<registry_name>/<int:per_page>/<int:page>"
"/<int:instance_number>/view/jobs/<queue_name>/<registry_name>/<int:per_page>/<order>/<int:page>"
)
def jobs_overview(instance_number, queue_name, registry_name, per_page, page):
def jobs_overview(instance_number, queue_name, registry_name, per_page, order, page):
if queue_name is None:
queue = Queue(serializer=config.serializer)
else:
Expand All @@ -360,6 +379,7 @@ def jobs_overview(instance_number, queue_name, registry_name, per_page, page):
queues=Queue.all(),
queue=queue,
per_page=per_page,
order=order,
page=page,
registry_name=registry_name,
rq_url_prefix=url_for(".queues_overview"),
Expand Down Expand Up @@ -482,15 +502,16 @@ def list_queues(instance_number):


@blueprint.route(
"/<int:instance_number>/data/jobs/<queue_name>/<registry_name>/<per_page>/<page>.json"
"/<int:instance_number>/data/jobs/<queue_name>/<registry_name>/<per_page>/<order>/<page>.json"
)
@jsonify
def list_jobs(instance_number, queue_name, registry_name, per_page, page):
def list_jobs(instance_number, queue_name, registry_name, per_page, order, page):
current_page = int(page)
per_page = int(per_page)
offset = (current_page - 1) * per_page

total_items, jobs = get_queue_registry_jobs_count(
queue_name, registry_name, offset, per_page
queue_name, registry_name, offset, per_page, order
)

pages_numbers_in_window = pagination_window(total_items, current_page, per_page)
Expand All @@ -503,6 +524,7 @@ def list_jobs(instance_number, queue_name, registry_name, per_page, page):
queue_name=queue_name,
registry_name=registry_name,
per_page=per_page,
order=order,
page=p,
),
)
Expand All @@ -519,6 +541,7 @@ def list_jobs(instance_number, queue_name, registry_name, per_page, page):
queue_name=queue_name,
registry_name=registry_name,
per_page=per_page,
order=order,
page=(current_page - 1),
)
)
Expand All @@ -532,6 +555,7 @@ def list_jobs(instance_number, queue_name, registry_name, per_page, page):
queue_name=queue_name,
registry_name=registry_name,
per_page=per_page,
order=order,
page=(current_page + 1),
)
)
Expand All @@ -543,6 +567,7 @@ def list_jobs(instance_number, queue_name, registry_name, per_page, page):
queue_name=queue_name,
registry_name=registry_name,
per_page=per_page,
order=order,
page=1,
)
)
Expand All @@ -553,6 +578,7 @@ def list_jobs(instance_number, queue_name, registry_name, per_page, page):
queue_name=queue_name,
registry_name=registry_name,
per_page=per_page,
order=order,
page=last_page,
)
)
Expand Down
31 changes: 27 additions & 4 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import time
import unittest

import redis
Expand All @@ -21,12 +22,14 @@ def get_redis_client(self):
def setUp(self):
self.app = make_flask_app(None, None, None, '')
self.app.testing = True
self.app.config['RQ_DASHBOARD_REDIS_URL'] = 'redis://127.0.0.1'
self.app.config['RQ_DASHBOARD_REDIS_URL'] = ['redis://127.0.0.1']
self.app.redis_conn = self.get_redis_client()
push_connection(self.get_redis_client())
self.client = self.app.test_client()

def tearDown(self):
q = Queue(connection=self.app.redis_conn)
q.empty()
pop_connection()

def test_dashboard_ok(self):
Expand All @@ -51,9 +54,9 @@ def test_workers_list_json(self):
def test_queued_jobs_list(self):
response_dashboard = self.client.get('/0/view/jobs')
self.assertEqual(response_dashboard.status_code, HTTP_OK)
response_queued = self.client.get('/0/view/jobs/default/queued/8/1')
response_queued = self.client.get('/0/view/jobs/default/queued/8/asc/1')
self.assertEqual(response_queued.status_code, HTTP_OK)
response = self.client.get('/0/data/jobs/default/queued/8/1.json')
response = self.client.get('/0/data/jobs/default/queued/8/asc/1.json')
self.assertEqual(response.status_code, HTTP_OK)
data = json.loads(response.data.decode('utf8'))
self.assertIsInstance(data, dict)
Expand All @@ -79,7 +82,7 @@ def some_work():
self.assertEqual(response_del.status_code, HTTP_OK)

def test_registry_jobs_list(self):
response = self.client.get('/0/data/jobs/default/failed/8/1.json')
response = self.client.get('/0/data/jobs/default/failed/8/asc/1.json')
self.assertEqual(response.status_code, HTTP_OK)
data = json.loads(response.data.decode('utf8'))
self.assertIsInstance(data, dict)
Expand Down Expand Up @@ -120,6 +123,26 @@ def test_instance_escaping(self):
),
[expected_redis_instance, expected_redis_instance, expected_redis_instance],
)

def test_job_sort_order(self):
def some_work():
return
q = Queue(connection=self.app.redis_conn)
job_ids = []
for _ in range(3):
job = q.enqueue(some_work)
job_ids.append(job.id)
time.sleep(2)

response_asc = self.client.get('/0/data/jobs/default/queued/3/asc/1.json')
self.assertEqual(response_asc.status_code, HTTP_OK)
data_asc = json.loads(response_asc.data.decode('utf8'))
self.assertEqual(job_ids, [job['id'] for job in data_asc['jobs']])

response_dsc = self.client.get('/0/data/jobs/default/queued/10/dsc/1.json')
self.assertEqual(response_dsc.status_code, HTTP_OK)
data_dsc = json.loads(response_dsc.data.decode('utf8'))
self.assertEqual(job_ids[::-1], [job['id'] for job in data_dsc['jobs']])


__all__ = [
Expand Down

0 comments on commit 1628a43

Please sign in to comment.