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

Adds Sort Order #477 #493

Merged
merged 6 commits into from
Aug 5, 2024
Merged
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
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
Loading