Skip to content

Commit c18aedb

Browse files
authored
Merge pull request #625 from loganharbour/ready_jobs
Add ready jobs page
2 parents 804a01a + 14b01da commit c18aedb

File tree

7 files changed

+143
-64
lines changed

7 files changed

+143
-64
lines changed

ci/client/ReadyJobs.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,16 @@ def get_ready_jobs():
2929
'recipe__build_user')
3030
.order_by(F('prioritized').desc(nulls_last=True), '-recipe__priority', 'created'))
3131

32-
ready_jobs = []
33-
current_push_event_branches = set()
32+
first_jobs = []
33+
later_jobs = []
3434
for job in jobs.all():
35-
if job.event.cause == models.Event.PUSH and job.event.auto_cancel_event_except_current():
36-
current_push_event_branches.add(job.event.base.branch)
37-
else:
38-
ready_jobs.append(job)
35+
# Delay push jobs that are not prioritized and have no set priority
36+
delay = job.event.cause == models.Event.PUSH and \
37+
job.prioritized is None and job.recipe.priority == 0
3938

40-
if current_push_event_branches:
41-
jobs = jobs.filter(event__base__branch__in=current_push_event_branches).order_by('created', '-recipe__priority')
42-
for job in jobs.all():
43-
ready_jobs.append(job)
39+
if delay:
40+
later_jobs.append(job)
41+
else:
42+
first_jobs.append(job)
4443

45-
return ready_jobs
44+
return first_jobs + later_jobs

ci/client/tests/test_views.py

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -117,59 +117,6 @@ def test_get_job_order(self):
117117
data = response.json()
118118
self.assertEqual(data['job_id'], expected_jobs[i].pk)
119119

120-
def test_ready_jobs_with_current_event(self):
121-
"""
122-
If a branch is specified with "auto_cancel_push_events_except_current" then
123-
jobs on the "current" event get priority over subsequent events. Basically
124-
the normal sort of (-priority, created) gets changed to (created, -priority)
125-
for those jobs.
126-
"""
127-
url = reverse('ci:client:get_job')
128-
user = utils.get_test_user()
129-
130-
recipes = []
131-
for i in range(3):
132-
recipe = utils.create_recipe(name=f'recipe{i}', user=user)
133-
recipe.priority = int(30 - 10 * i)
134-
recipe.save()
135-
recipes.append(recipe)
136-
137-
e0 = utils.create_event(user=user, cause=models.Event.PUSH)
138-
e1 = utils.create_event(user=user, cause=models.Event.PUSH, commit1=12345)
139-
140-
jobs = []
141-
for i in range(3):
142-
jobs.append(utils.create_job(recipe=recipes[i], event=e0, user=user))
143-
for i in range(3):
144-
jobs.append(utils.create_job(recipe=recipes[i], event=e1, user=user))
145-
146-
for job in jobs:
147-
job.active = True
148-
job.ready = True
149-
job.save()
150-
151-
clients = []
152-
for i in range(len(jobs)):
153-
client = utils.create_client(name=f'client{i}')
154-
clients.append(client)
155-
156-
url = reverse('ci:client:get_job')
157-
post_data = {'build_keys': [user.build_key],
158-
'build_configs': [jobs[0].config.name]}
159-
160-
repo_name = "%s/%s" % (e0.base.branch.repository.user.name, e0.base.branch.repository.name)
161-
branch_name = e0.base.branch.name
162-
repo_settings={repo_name: {"branch_settings": {branch_name: {"auto_cancel_push_events_except_current": True}}}}
163-
with self.settings(INSTALLED_GITSERVERS=[utils.github_config(repo_settings=repo_settings)]):
164-
self.set_counts()
165-
for i in range(len(jobs)):
166-
post_data['client_name'] = clients[i].name
167-
response = self.client_post_json(url, post_data)
168-
self.compare_counts(active_branches=1)
169-
self.assertEqual(response.status_code, 200)
170-
data = response.json()
171-
self.assertEqual(data['job_id'], jobs[i].pk)
172-
173120
def json_post_request(self, data):
174121
jdata = json.dumps(data)
175122
return self.factory.post('/', jdata, content_type='application/json')

ci/templates/ci/base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<li><a href="{% url 'ci:event_list' %}">Events</a></li>
9393
<li><a href="{% url 'ci:pullrequest_list' %}">Pull Requests</a></li>
9494
<li><a href="{% url 'ci:client_list' %}">Job Clients</a></li>
95+
<li><a href="{% url 'ci:ready_jobs' %}">Ready Jobs</a></li>
9596
<li><a href="{% url 'ci:scheduled' %}">Scheduled Events</a></li>
9697
<li><a href="{% url 'ci:cronjobs' %}">Cron Recipes</a></li>
9798
<li><a href="{% url 'ci:job_info_search' %}">Filter Job by modules</a></li>

ci/templates/ci/ready_jobs.html

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
{% extends "ci/base.html" %}
2+
{% load tz %}
3+
{% load humanize %}
4+
{% comment %}
5+
Copyright 2016-2025 Battelle Energy Alliance, LLC
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
{% endcomment %}
19+
{% load humanize %}
20+
{% block title %}Civet: Ready jobs{% endblock %}
21+
{% block content %}
22+
<div class="center">
23+
{% if num_jobs %}
24+
<h2>{{ num_jobs }} ready jobs</h2>
25+
{% else %}
26+
<h2>Ready jobs</h2>
27+
{% endif %}
28+
</div>
29+
30+
{% if not allowed %}
31+
You are not allowed to view the ready jobs.
32+
<br/>Please sign in and try again.
33+
{% elif num_jobs %}
34+
{% timezone "US/Mountain" %}
35+
{% for config, jobs in jobs_by_config.items %}
36+
<table class="table table-hover table-bordered table-condensed">
37+
<thead>
38+
<tr>
39+
<th colspan="6" style="text-align:center;">Build config {{ config }}</tr>
40+
</tr>
41+
<tr>
42+
<th>Created</th>
43+
<th>Repo</th>
44+
<th>Event</th>
45+
<th>Job</th>
46+
<th>Priority</th>
47+
<th>Prioritized</th>
48+
</tr>
49+
</thead>
50+
<tbody>
51+
{% for job in jobs %}
52+
<tr>
53+
<td>{{ job.created|date:"m/d/y h:i:s" }}, {{ job.created|naturaltime }}</td>
54+
<td>{{ job.recipe.repository.name }}</td>
55+
{% if job.event.pull_request %}
56+
<td><a href="{% url "ci:view_event" job.event.pk %}">{{ job.event.pull_request }}</a></td>
57+
{% else %}
58+
<td><a href="{% url "ci:view_event" job.event.pk %}">{{ job.event.description }}</a></td>
59+
{% endif %}
60+
<td><a href="{% url "ci:view_job" job.pk %}">{{ job.recipe.display_name }}</a></td>
61+
<td>{{ job.recipe.priority }}</td>
62+
{% if job.prioritized %}
63+
<td>{{ job.prioritized|date:"m/d/y h:i:s" }}, {{ job.prioritized|naturaltime }}</td>
64+
{% else %}
65+
<td>No</td>
66+
{% endif %}
67+
</tr>
68+
{% endfor %}
69+
</tbody>
70+
</table>
71+
{% endfor %}
72+
{% endtimezone %}
73+
{% else %}
74+
No ready jobs
75+
{% endif %}
76+
{% endblock %}

ci/tests/test_views.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,43 @@ def test_prioritize_event(self, mock_perms):
729729
j.refresh_from_db()
730730
self.assertIsNotNone(j.prioritized)
731731

732+
@patch.object(Permissions, 'is_allowed_to_see_clients')
733+
@override_settings(PERMISSION_CACHE_TIMEOUT=0)
734+
def test_ready_jobs(self, mock_perms):
735+
# not allowed
736+
mock_perms.return_value = False
737+
response = self.client.get(reverse('ci:ready_jobs'))
738+
self.assertEqual(response.status_code, 200)
739+
self.assertIn('You are not allowed to view the ready jobs', response.content.decode("utf-8"))
740+
741+
# no ready jobs
742+
mock_perms.return_value = True
743+
response = self.client.get(reverse('ci:ready_jobs'))
744+
self.assertEqual(response.status_code, 200)
745+
self.assertIn('No ready jobs', response.content.decode("utf-8"))
746+
747+
j0, j1, j2, j3 = utils.create_test_jobs()
748+
client = utils.create_client()
749+
for j in [j0, j1, j2, j3]:
750+
j.client = client
751+
j.ready = True
752+
j.event.complete = False
753+
j.save()
754+
755+
repo = j0.recipe.repository
756+
repo.active = True
757+
repo.save()
758+
759+
mock_perms.return_value = True
760+
response = self.client.get(reverse('ci:ready_jobs'))
761+
self.assertEqual(response.status_code, 200)
762+
content = response.content.decode("utf-8")
763+
self.assertIn('4 ready jobs', content)
764+
for j in [j0, j1, j2, j3]:
765+
self.assertIn(j.recipe.repository.name, content)
766+
self.assertIn(j.event.description, content)
767+
self.assertIn(j.recipe.display_name, content)
768+
732769
@patch.object(Permissions, 'is_collaborator')
733770
@override_settings(PERMISSION_CACHE_TIMEOUT=0)
734771
def test_cancel_event(self, mock_collab):

ci/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
re_path(r'^recipe_crons/(?P<recipe_id>[0-9]+)/$', views.recipe_crons, name='recipe_crons'),
3939
re_path(r'^manual_cron/(?P<recipe_id>[0-9]+)/$', views.manual_cron, name='manual_cron'),
4040
re_path(r'^cronjobs/$', views.cronjobs, name='cronjobs'),
41+
re_path(r'^ready_jobs/$', views.ready_jobs, name='ready_jobs'),
4142
re_path(r'^manual_branch/(?P<build_key>[0-9]+)/(?P<branch_id>[0-9]+)/$',
4243
views.manual_branch, name='manual_branch'),
4344
re_path(r'^manual_branch/(?P<build_key>[0-9]+)/(?P<branch_id>[0-9]+)/(?P<label>[A-Za-z0-9_.-]+)/$',

ci/views.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import tarfile
3030
from io import BytesIO
3131
from ci import RepositoryStatus, EventsStatus, Permissions, PullRequestEvent, ManualEvent, TimeUtils
32+
from ci.client.ReadyJobs import get_ready_jobs
3233
from django.utils.html import escape
3334
from django.utils.text import get_valid_filename
3435
from django.views.decorators.cache import never_cache
@@ -37,6 +38,7 @@
3738
from datetime import datetime
3839
from croniter import croniter
3940
import pytz
41+
from collections import defaultdict
4042

4143
import logging, traceback
4244
logger = logging.getLogger('ci')
@@ -599,6 +601,22 @@ def cronjobs(request):
599601
data = {'recipes': recipe_list, 'allowed': True, 'update_interval': settings.HOME_PAGE_UPDATE_INTERVAL, }
600602
return render(request, 'ci/cronjobs.html', data)
601603

604+
def ready_jobs(request):
605+
allowed = Permissions.is_allowed_to_see_clients(request.session)
606+
if not allowed:
607+
return render(request, 'ci/ready_jobs.html', {'allowed': False, 'jobs_by_config': None, 'num_jobs': None})
608+
609+
num_jobs = 0
610+
jobs_by_config = defaultdict(list)
611+
for job in get_ready_jobs():
612+
jobs_by_config[str(job.config)].append(job)
613+
num_jobs += 1
614+
615+
data = {'allowed': True,
616+
'jobs_by_config': dict(jobs_by_config),
617+
'num_jobs': num_jobs}
618+
return render(request, 'ci/ready_jobs.html', data)
619+
602620
def clients_info():
603621
"""
604622
Gets the information on all the currently active clients.

0 commit comments

Comments
 (0)