summaryrefslogtreecommitdiffstats
path: root/WebKitTools/QueueStatusServer
diff options
context:
space:
mode:
Diffstat (limited to 'WebKitTools/QueueStatusServer')
-rw-r--r--WebKitTools/QueueStatusServer/app.yaml8
-rw-r--r--WebKitTools/QueueStatusServer/handlers/dashboard.py29
-rw-r--r--WebKitTools/QueueStatusServer/handlers/nextpatch.py24
-rw-r--r--WebKitTools/QueueStatusServer/handlers/queuestatus.py21
-rw-r--r--WebKitTools/QueueStatusServer/handlers/recentstatus.py17
-rw-r--r--WebKitTools/QueueStatusServer/handlers/releasepatch.py62
-rw-r--r--WebKitTools/QueueStatusServer/handlers/statusbubble.py29
-rw-r--r--WebKitTools/QueueStatusServer/handlers/statusbubble_unittest.py62
-rw-r--r--WebKitTools/QueueStatusServer/handlers/submittoews.py64
-rw-r--r--WebKitTools/QueueStatusServer/handlers/updatestatus.py20
-rw-r--r--WebKitTools/QueueStatusServer/handlers/updateworkitems.py19
-rw-r--r--WebKitTools/QueueStatusServer/main.py4
-rw-r--r--WebKitTools/QueueStatusServer/model/activeworkitems.py15
-rw-r--r--WebKitTools/QueueStatusServer/model/activeworkitems_unitest.py52
-rw-r--r--WebKitTools/QueueStatusServer/model/attachment.py32
-rw-r--r--WebKitTools/QueueStatusServer/model/queuepropertymixin.py39
-rw-r--r--WebKitTools/QueueStatusServer/model/queuepropertymixin_unittest.py52
-rw-r--r--WebKitTools/QueueStatusServer/model/queues.py101
-rw-r--r--WebKitTools/QueueStatusServer/model/queues_unittest.py73
-rw-r--r--WebKitTools/QueueStatusServer/model/queuestatus.py8
-rw-r--r--WebKitTools/QueueStatusServer/model/svnrevision.py1
-rw-r--r--WebKitTools/QueueStatusServer/model/workitems.py34
-rw-r--r--WebKitTools/QueueStatusServer/model/workitems_unittest.py53
-rw-r--r--WebKitTools/QueueStatusServer/templates/releasepatch.html3
-rw-r--r--WebKitTools/QueueStatusServer/templates/statusbubble.html5
-rw-r--r--WebKitTools/QueueStatusServer/templates/submittoews.html3
-rw-r--r--WebKitTools/QueueStatusServer/templates/updatestatus.html4
27 files changed, 675 insertions, 159 deletions
diff --git a/WebKitTools/QueueStatusServer/app.yaml b/WebKitTools/QueueStatusServer/app.yaml
index 76d8963..e320eb0 100644
--- a/WebKitTools/QueueStatusServer/app.yaml
+++ b/WebKitTools/QueueStatusServer/app.yaml
@@ -3,13 +3,13 @@ version: 1
runtime: python
api_version: 1
+builtins:
+- datastore_admin: on
+- remote_api: on
+
handlers:
- url: /stylesheets
static_dir: stylesheets
-- url: /remote_api
- script: $PYTHON_LIB/google/appengine/ext/remote_api/handler.py
- login: admin
-
- url: /.*
script: main.py
diff --git a/WebKitTools/QueueStatusServer/handlers/dashboard.py b/WebKitTools/QueueStatusServer/handlers/dashboard.py
index 26de263..660c595 100644
--- a/WebKitTools/QueueStatusServer/handlers/dashboard.py
+++ b/WebKitTools/QueueStatusServer/handlers/dashboard.py
@@ -32,31 +32,16 @@ from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from model.attachment import Attachment
-from model.queues import queues
+from model.queues import Queue
class Dashboard(webapp.RequestHandler):
+ # We may want to sort these?
+ _ordered_queues = Queue.all()
+ _header_names = [queue.short_name() for queue in _ordered_queues]
- # FIXME: This list probably belongs as part of a Queue object in queues.py
- # Arrays are bubble_name, queue_name
- # FIXME: Can this be unified with StatusBubble._queues_to_display?
- _queues_to_display = [
- ["Style", "style-queue"],
- ["Cr-Linux", "chromium-ews"],
- ["Qt", "qt-ews"],
- ["Gtk", "gtk-ews"],
- ["Mac", "mac-ews"],
- ["Win", "win-ews"],
- ["Commit", "commit-queue"],
- ]
- # Split the zipped list into component parts
- _header_names, _ordered_queue_names = zip(*_queues_to_display)
-
- # This asserts that all of the queues listed above are valid queue names.
- assert(reduce(operator.and_, map(lambda name: name in queues, _ordered_queue_names)))
-
- def _build_bubble(self, attachment, queue_name):
- queue_status = attachment.status_for_queue(queue_name)
+ def _build_bubble(self, attachment, queue):
+ queue_status = attachment.status_for_queue(queue)
bubble = {
"status_class": attachment.state_from_queue_status(queue_status) if queue_status else "none",
"status_date": queue_status.date if queue_status else None,
@@ -67,7 +52,7 @@ class Dashboard(webapp.RequestHandler):
row = {
"bug_id": attachment.bug_id(),
"attachment_id": attachment.id,
- "bubbles": [self._build_bubble(attachment, queue_name) for queue_name in self._ordered_queue_names],
+ "bubbles": [self._build_bubble(attachment, queue) for queue in self._ordered_queues],
}
return row
diff --git a/WebKitTools/QueueStatusServer/handlers/nextpatch.py b/WebKitTools/QueueStatusServer/handlers/nextpatch.py
index edb702a..5f6d71d 100644
--- a/WebKitTools/QueueStatusServer/handlers/nextpatch.py
+++ b/WebKitTools/QueueStatusServer/handlers/nextpatch.py
@@ -26,26 +26,24 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from datetime import datetime
+
from google.appengine.ext import db
from google.appengine.ext import webapp
-from model.workitems import WorkItems
-from model.activeworkitems import ActiveWorkItems
-from model import queuestatus
-
-from datetime import datetime, timedelta
+from model.queues import Queue
class NextPatch(webapp.RequestHandler):
- def _get_next_patch_id(self, queue_name):
- work_items = WorkItems.all().filter("queue_name =", queue_name).get()
- if not work_items:
- return None
- active_work_items = ActiveWorkItems.get_or_insert(key_name=queue_name, queue_name=queue_name)
- return db.run_in_transaction(self._assign_patch, active_work_items.key(), work_items.item_ids)
-
+ # FIXME: This should probably be a post, or an explict lock_patch
+ # since GET requests shouldn't really modify the datastore.
def get(self, queue_name):
- patch_id = self._get_next_patch_id(queue_name)
+ queue = Queue.queue_with_name(queue_name)
+ if not queue:
+ self.error(404)
+ return
+ # FIXME: Patch assignment should probably move into Queue.
+ patch_id = db.run_in_transaction(self._assign_patch, queue.active_work_items().key(), queue.work_items().item_ids)
if not patch_id:
self.error(404)
return
diff --git a/WebKitTools/QueueStatusServer/handlers/queuestatus.py b/WebKitTools/QueueStatusServer/handlers/queuestatus.py
index 0259c37..5c31537 100644
--- a/WebKitTools/QueueStatusServer/handlers/queuestatus.py
+++ b/WebKitTools/QueueStatusServer/handlers/queuestatus.py
@@ -29,15 +29,15 @@
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
-from model.queues import queues, display_name_for_queue
-from model.workitems import WorkItems
-from model.activeworkitems import ActiveWorkItems
+from model.queues import Queue
from model import queuestatus
class QueueStatus(webapp.RequestHandler):
- def _rows_for_work_items(self, queued_items, active_items):
+ def _rows_for_work_items(self, queue):
+ queued_items = queue.work_items()
+ active_items = queue.active_work_items()
if not queued_items:
return []
rows = []
@@ -50,14 +50,17 @@ class QueueStatus(webapp.RequestHandler):
return rows
def get(self, queue_name):
- queued_items = WorkItems.all().filter("queue_name =", queue_name).get()
- active_items = ActiveWorkItems.all().filter("queue_name =", queue_name).get()
- statuses = queuestatus.QueueStatus.all().filter("queue_name =", queue_name).order("-date").fetch(15)
+ queue_name = queue_name.lower()
+ queue = Queue.queue_with_name(queue_name)
+ if not queue:
+ self.error(404)
+ return
status_groups = []
last_patch_id = None
synthetic_patch_id_counter = 0
+ statuses = queuestatus.QueueStatus.all().filter("queue_name =", queue.name()).order("-date").fetch(15)
for status in statuses:
patch_id = status.active_patch_id
if not patch_id or last_patch_id != patch_id:
@@ -69,8 +72,8 @@ class QueueStatus(webapp.RequestHandler):
last_patch_id = patch_id
template_values = {
- "display_queue_name": display_name_for_queue(queue_name),
- "work_item_rows": self._rows_for_work_items(queued_items, active_items),
+ "display_queue_name": queue.display_name(),
+ "work_item_rows": self._rows_for_work_items(queue),
"status_groups": status_groups,
}
self.response.out.write(template.render("templates/queuestatus.html", template_values))
diff --git a/WebKitTools/QueueStatusServer/handlers/recentstatus.py b/WebKitTools/QueueStatusServer/handlers/recentstatus.py
index e2b8c2f..fddc93a 100644
--- a/WebKitTools/QueueStatusServer/handlers/recentstatus.py
+++ b/WebKitTools/QueueStatusServer/handlers/recentstatus.py
@@ -31,23 +31,24 @@ import datetime
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
-from model.queues import queues, display_name_for_queue
+from model.queues import Queue
from model.queuestatus import QueueStatus
from model.workitems import WorkItems
class QueueBubble(object):
"""View support class for recentstatus.html"""
- def __init__(self, queue_name):
- self._queue_name = queue_name
- self._work_items = WorkItems.all().filter("queue_name =", queue_name).get()
- self._last_status = QueueStatus.all().filter("queue_name =", queue_name).order("-date").get()
+ def __init__(self, queue):
+ self._queue = queue
+ self._work_items = queue.work_items()
+ self._last_status = QueueStatus.all().filter("queue_name =", queue.name()).order("-date").get()
+ # FIXME: name and display_name should be replaced by a .queue() accessor.
def name(self):
- return self._queue_name
+ return self._queue.name()
def display_name(self):
- return display_name_for_queue(self._queue_name)
+ return self._queue.display_name()
def _last_status_date(self):
if not self._last_status:
@@ -88,6 +89,6 @@ class QueuesOverview(webapp.RequestHandler):
def get(self):
template_values = {
- "queues": [QueueBubble(queue_name) for queue_name in queues],
+ "queues": [QueueBubble(queue) for queue in Queue.all()],
}
self.response.out.write(template.render("templates/recentstatus.html", template_values))
diff --git a/WebKitTools/QueueStatusServer/handlers/releasepatch.py b/WebKitTools/QueueStatusServer/handlers/releasepatch.py
new file mode 100644
index 0000000..0e46e69
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/handlers/releasepatch.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from google.appengine.ext import webapp, db
+from google.appengine.ext.webapp import template
+
+from handlers.updatebase import UpdateBase
+from model.attachment import Attachment
+from model.queues import Queue
+
+
+class ReleasePatch(UpdateBase):
+ def get(self):
+ self.response.out.write(template.render("templates/releasepatch.html", None))
+
+ def post(self):
+ queue_name = self.request.get("queue_name")
+ # FIXME: This queue lookup should be shared between handlers.
+ queue = Queue.queue_with_name(queue_name)
+ if not queue:
+ self.error(404)
+ return
+
+ attachment_id = self._int_from_request("attachment_id")
+ attachment = Attachment(attachment_id)
+ last_status = attachment.status_for_queue(queue)
+
+ # Ideally we should use a transaction for the calls to
+ # WorkItems and ActiveWorkItems.
+
+ # Only remove it from the queue if the last message is not a retry request.
+ # Allow removing it from the queue even if there is no last_status for easier testing.
+ if not last_status or not last_status.is_retry_request():
+ queue.work_items().remove_work_item(attachment_id)
+
+ # Always release the lock on the item.
+ queue.active_work_items().expire_item(attachment_id)
diff --git a/WebKitTools/QueueStatusServer/handlers/statusbubble.py b/WebKitTools/QueueStatusServer/handlers/statusbubble.py
index bfbe958..5690484 100644
--- a/WebKitTools/QueueStatusServer/handlers/statusbubble.py
+++ b/WebKitTools/QueueStatusServer/handlers/statusbubble.py
@@ -33,33 +33,18 @@ from google.appengine.ext.webapp import template
from model.attachment import Attachment
from model.workitems import WorkItems
-from model.queues import queues, name_with_underscores
+from model.queues import Queue
class StatusBubble(webapp.RequestHandler):
- # FIXME: This list probably belongs as part of a Queue object in queues.py
- # Arrays are bubble_name, queue_name
- _queues_to_display = [
- ["style", "style-queue"],
- ["cr-linux", "chromium-ews"],
- ["gtk", "gtk-ews"],
- ["qt", "qt-ews"],
- ["mac", "mac-ews"],
- ["win", "win-ews"],
- ]
+ _queues_to_display = [queue for queue in Queue.all() if queue.is_ews()]
- # This asserts that all of the queues listed above are valid queue names.
- assert(reduce(operator.and_, map(lambda name_pair: name_pair[1] in queues, _queues_to_display)))
-
- def _build_bubble(self, queue_name_pair, attachment):
- bubble_name = queue_name_pair[0]
- queue_name = queue_name_pair[1]
-
- queue_status = attachment.status_for_queue(queue_name)
+ def _build_bubble(self, queue, attachment):
+ queue_status = attachment.status_for_queue(queue)
bubble = {
- "name": bubble_name,
+ "name": queue.short_name().lower(),
"attachment_id": attachment.id,
- "queue_position": attachment.position_in_queue(queue_name),
+ "queue_position": attachment.position_in_queue(queue),
"state": attachment.state_from_queue_status(queue_status) if queue_status else "none",
"status": queue_status,
}
@@ -67,7 +52,7 @@ class StatusBubble(webapp.RequestHandler):
def get(self, attachment_id):
attachment = Attachment(int(attachment_id))
- bubbles = [self._build_bubble(name_pair, attachment) for name_pair in self._queues_to_display]
+ bubbles = [self._build_bubble(queue, attachment) for queue in self._queues_to_display]
template_values = {
"bubbles": bubbles,
}
diff --git a/WebKitTools/QueueStatusServer/handlers/statusbubble_unittest.py b/WebKitTools/QueueStatusServer/handlers/statusbubble_unittest.py
new file mode 100644
index 0000000..3ffbdaf
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/handlers/statusbubble_unittest.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2010 Google, Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Research in Motion Ltd. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+
+from handlers.statusbubble import StatusBubble
+from model.queues import Queue
+
+
+class MockAttachment(object):
+ def __init__(self):
+ self.id = 1
+
+ def status_for_queue(self, queue):
+ return None
+
+ def position_in_queue(self, queue):
+ return 1
+
+
+class StatusBubbleTest(unittest.TestCase):
+ def test_build_bubble(self):
+ bubble = StatusBubble()
+ queue = Queue("mac-ews")
+ attachment = MockAttachment()
+ bubble_dict = bubble._build_bubble(queue, attachment)
+ # FIXME: assertDictEqual (in Python 2.7) would be better to use here.
+ self.assertEqual(bubble_dict["name"], "mac")
+ self.assertEqual(bubble_dict["attachment_id"], 1)
+ self.assertEqual(bubble_dict["queue_position"], 1)
+ self.assertEqual(bubble_dict["state"], "none")
+ self.assertEqual(bubble_dict["status"], None)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/QueueStatusServer/handlers/submittoews.py b/WebKitTools/QueueStatusServer/handlers/submittoews.py
new file mode 100644
index 0000000..3ba4373
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/handlers/submittoews.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from google.appengine.ext import webapp, db
+from google.appengine.ext.webapp import template
+
+from handlers.updatebase import UpdateBase
+from model.attachment import Attachment
+from model.queues import Queue
+
+
+class SubmitToEWS(UpdateBase):
+ def get(self):
+ self.response.out.write(template.render("templates/submittoews.html", None))
+
+ def _should_add_to_ews_queue(self, queue, attachment):
+ # This assert() is here to make sure we're not submitting to the commit-queue.
+ # The commit-queue clients check each patch anyway, but there is not sense
+ # in adding things to the commit-queue when they won't be processed by it.
+ assert(queue.is_ews())
+ latest_status = attachment.status_for_queue(queue)
+ if not latest_status:
+ return True
+ # Only ever re-submit to the EWS if the EWS specifically requested a retry.
+ # This allows us to restart the EWS feeder queue, without all r? patches
+ # being retried as a result of that restart!
+ # In some future version we might add a "force" button to allow the user
+ # to override this restriction.
+ return latest_status.is_retry_request()
+
+ def _add_attachment_to_ews_queues(self, attachment):
+ for queue in Queue.all_ews(): # all_ews() currently includes the style-queue
+ if self._should_add_to_ews_queue(queue, attachment):
+ queue.work_items().add_work_item(attachment.id)
+
+ def post(self):
+ attachment_id = self._int_from_request("attachment_id")
+ attachment = Attachment(attachment_id)
+ self._add_attachment_to_ews_queues(attachment)
diff --git a/WebKitTools/QueueStatusServer/handlers/updatestatus.py b/WebKitTools/QueueStatusServer/handlers/updatestatus.py
index 89858b6..7301101 100644
--- a/WebKitTools/QueueStatusServer/handlers/updatestatus.py
+++ b/WebKitTools/QueueStatusServer/handlers/updatestatus.py
@@ -31,10 +31,10 @@ from google.appengine.ext import webapp, db
from google.appengine.ext.webapp import template
from handlers.updatebase import UpdateBase
-from model.activeworkitems import ActiveWorkItems
from model.attachment import Attachment
from model.queuestatus import QueueStatus
+
class UpdateStatus(UpdateBase):
def get(self):
self.response.out.write(template.render("templates/updatestatus.html", None))
@@ -49,7 +49,9 @@ class UpdateStatus(UpdateBase):
bug_id = self._int_from_request("bug_id")
patch_id = self._int_from_request("patch_id")
queue_name = self.request.get("queue_name")
+ bot_id = self.request.get("bot_id")
queue_status.queue_name = queue_name
+ queue_status.bot_id = bot_id
queue_status.active_bug_id = bug_id
queue_status.active_patch_id = patch_id
queue_status.message = self.request.get("status")
@@ -57,24 +59,8 @@ class UpdateStatus(UpdateBase):
queue_status.results_file = db.Blob(str(results_file))
return queue_status
- @staticmethod
- def _expire_item(key, item_id):
- active_work_items = db.get(key)
- active_work_items.expire_item(item_id)
- active_work_items.put()
-
- # FIXME: An explicit lock_release request would be cleaner than this magical "Retry" status.
- def _update_active_work_items(self, queue_status):
- if queue_status.message != "Retry": # From AbstractQueue._retry_status
- return
- active_items = ActiveWorkItems.all().filter("queue_name =", queue_status.queue_name).get()
- if not active_items:
- return
- return db.run_in_transaction(self._expire_item, active_items.key(), queue_status.active_patch_id)
-
def post(self):
queue_status = self._queue_status_from_request()
queue_status.put()
- self._update_active_work_items(queue_status)
Attachment.dirty(queue_status.active_patch_id)
self.response.out.write(queue_status.key().id())
diff --git a/WebKitTools/QueueStatusServer/handlers/updateworkitems.py b/WebKitTools/QueueStatusServer/handlers/updateworkitems.py
index f91beb4..16a9d49 100644
--- a/WebKitTools/QueueStatusServer/handlers/updateworkitems.py
+++ b/WebKitTools/QueueStatusServer/handlers/updateworkitems.py
@@ -30,7 +30,7 @@ from google.appengine.ext import webapp, db
from google.appengine.ext.webapp import template
from handlers.updatebase import UpdateBase
-from model.queues import queues
+from model.queues import Queue
from model.workitems import WorkItems
from datetime import datetime
@@ -40,16 +40,6 @@ class UpdateWorkItems(UpdateBase):
def get(self):
self.response.out.write(template.render("templates/updateworkitems.html", None))
- def _work_items_for_queue(self, queue_name):
- if queue_name not in queues:
- self.response.out.write("\"%s\" is not in queues %s" % (queue_name, queues))
- return None
- work_items = WorkItems.all().filter("queue_name =", queue_name).get()
- if not work_items:
- work_items = WorkItems()
- work_items.queue_name = queue_name
- return work_items
-
def _parse_work_items_string(self, items_string):
# Our parsing could be much more robust.
item_strings = items_string.split(" ") if items_string else []
@@ -57,10 +47,13 @@ class UpdateWorkItems(UpdateBase):
def _work_items_from_request(self):
queue_name = self.request.get("queue_name")
- work_items = self._work_items_for_queue(queue_name)
- if not work_items:
+ queue = Queue.queue_with_name(queue_name)
+ if not queue:
+ self.response.out.write("\"%s\" is not in queues %s" % (queue_name, Queue.all()))
return None
+
items_string = self.request.get("work_items")
+ work_items = queue.work_items()
work_items.item_ids = self._parse_work_items_string(items_string)
work_items.date = datetime.now()
return work_items
diff --git a/WebKitTools/QueueStatusServer/main.py b/WebKitTools/QueueStatusServer/main.py
index 93227ca..3fbee5c 100644
--- a/WebKitTools/QueueStatusServer/main.py
+++ b/WebKitTools/QueueStatusServer/main.py
@@ -40,8 +40,10 @@ from handlers.patch import Patch
from handlers.patchstatus import PatchStatus
from handlers.queuestatus import QueueStatus
from handlers.recentstatus import QueuesOverview
+from handlers.releasepatch import ReleasePatch
from handlers.showresults import ShowResults
from handlers.statusbubble import StatusBubble
+from handlers.submittoews import SubmitToEWS
from handlers.svnrevision import SVNRevision
from handlers.updatestatus import UpdateStatus
from handlers.updatesvnrevision import UpdateSVNRevision
@@ -56,11 +58,13 @@ routes = [
('/gc', GC),
(r'/patch-status/(.*)/(.*)', PatchStatus),
(r'/patch/(.*)', Patch),
+ (r'/submit-to-ews', SubmitToEWS),
(r'/results/(.*)', ShowResults),
(r'/status-bubble/(.*)', StatusBubble),
(r'/svn-revision/(.*)', SVNRevision),
(r'/queue-status/(.*)', QueueStatus),
(r'/next-patch/(.*)', NextPatch),
+ (r'/release-patch', ReleasePatch),
('/update-status', UpdateStatus),
('/update-work-items', UpdateWorkItems),
('/update-svn-revision', UpdateSVNRevision),
diff --git a/WebKitTools/QueueStatusServer/model/activeworkitems.py b/WebKitTools/QueueStatusServer/model/activeworkitems.py
index a244c7d..ab5d7a6 100644
--- a/WebKitTools/QueueStatusServer/model/activeworkitems.py
+++ b/WebKitTools/QueueStatusServer/model/activeworkitems.py
@@ -31,8 +31,10 @@ from google.appengine.ext import db
from datetime import timedelta, datetime
import time
+from model.queuepropertymixin import QueuePropertyMixin
-class ActiveWorkItems(db.Model):
+
+class ActiveWorkItems(db.Model, QueuePropertyMixin):
queue_name = db.StringProperty()
item_ids = db.ListProperty(int)
item_dates = db.ListProperty(float)
@@ -55,10 +57,19 @@ class ActiveWorkItems(db.Model):
self.item_ids.append(pair[0])
self.item_dates.append(pair[1])
- def expire_item(self, item_id):
+ def _remove_item(self, item_id):
nonexpired_pairs = [pair for pair in self._item_time_pairs() if pair[0] != item_id]
self._set_item_time_pairs(nonexpired_pairs)
+ @staticmethod
+ def _expire_item(key, item_id):
+ active_work_items = db.get(key)
+ active_work_items._remove_item(item_id)
+ active_work_items.put()
+
+ def expire_item(self, item_id):
+ return db.run_in_transaction(self._expire_item, self.key(), item_id)
+
def deactivate_expired(self, now):
one_hour_ago = time.mktime((now - timedelta(minutes=60)).timetuple())
nonexpired_pairs = [pair for pair in self._item_time_pairs() if pair[1] > one_hour_ago]
diff --git a/WebKitTools/QueueStatusServer/model/activeworkitems_unitest.py b/WebKitTools/QueueStatusServer/model/activeworkitems_unitest.py
new file mode 100644
index 0000000..6d915a1
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/model/activeworkitems_unitest.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010 Google, Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Research in Motion Ltd. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+from datetime import datetime
+
+from model.activeworkitems import ActiveWorkItems
+
+
+class ActiveWorkItemsTest(unittest.TestCase):
+ def test_basic(self):
+ items = ActiveWorkItems()
+ queued_items = [1, 2]
+ time = datetime.now()
+ self.assertEqual(items.next_item(queued_items, time), 1)
+ self.assertEqual(items.next_item([1], time), None)
+ self.assertEqual(items.next_item([], time), None)
+
+ self.assertEqual(items.time_for_item(1), time)
+ self.assertEqual(items.time_for_item(2), None)
+
+ items.expire_item(1)
+ self.assertEqual(items.time_for_item(1), None)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/QueueStatusServer/model/attachment.py b/WebKitTools/QueueStatusServer/model/attachment.py
index 9ae59e8..f98f265 100644
--- a/WebKitTools/QueueStatusServer/model/attachment.py
+++ b/WebKitTools/QueueStatusServer/model/attachment.py
@@ -30,7 +30,7 @@ import re
from google.appengine.api import memcache
-from model.queues import queues, name_with_underscores
+from model.queues import Queue
from model.queuestatus import QueueStatus
from model.workitems import WorkItems
@@ -87,13 +87,12 @@ class Attachment(object):
return "pending"
return None
- def position_in_queue(self, queue_name):
- return self._queue_positions().get(queue_name)
+ def position_in_queue(self, queue):
+ return self._queue_positions().get(queue.name())
- def status_for_queue(self, queue_name):
- underscore_queue_name = name_with_underscores(queue_name)
+ def status_for_queue(self, queue):
# summary() is a horrible API and should be killed.
- queue_summary = self.summary().get(underscore_queue_name)
+ queue_summary = self.summary().get(queue.name_with_underscores())
if not queue_summary:
return None
return queue_summary.get("status")
@@ -109,16 +108,8 @@ class Attachment(object):
return self._cached_queue_positions
def _calculate_queue_positions(self):
- queue_positions = {}
- for work_items in WorkItems.all().fetch(limit=len(queues)):
- queue_name = str(work_items.queue_name)
- try:
- position = work_items.item_ids.index(self.id)
- # Display 1-based indecies to the user.
- queue_positions[queue_name] = position + 1
- except ValueError, e:
- queue_positions[queue_name] = None
- return queue_positions
+ all_work_items = WorkItems.all().fetch(limit=len(Queue.all()))
+ return dict([(items.queue.name(), items.display_position_for_attachment(self.id)) for items in all_work_items])
# FIXME: This is controller/view code and does not belong in a model.
def _fetch_summary(self):
@@ -130,11 +121,12 @@ class Attachment(object):
return summary
summary["bug_id"] = first_status.active_bug_id
- for queue in queues:
- summary[queue] = None
- status = QueueStatus.all().filter('queue_name =', queue).filter('active_patch_id =', self.id).order('-date').get()
+ for queue in Queue.all():
+ summary[queue.name_with_underscores()] = None
+ status = QueueStatus.all().filter('queue_name =', queue.name()).filter('active_patch_id =', self.id).order('-date').get()
if status:
- summary[name_with_underscores(queue)] = {
+ # summary() is a horrible API and should be killed.
+ summary[queue.name_with_underscores()] = {
"state": self.state_from_queue_status(status),
"status": status,
}
diff --git a/WebKitTools/QueueStatusServer/model/queuepropertymixin.py b/WebKitTools/QueueStatusServer/model/queuepropertymixin.py
new file mode 100644
index 0000000..a462586
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/model/queuepropertymixin.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2010 Google Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+class QueuePropertyMixin(object):
+ def _queue_getter(self):
+ # Import at runtime to avoid circular imports
+ from model.queues import Queue
+ return Queue.queue_with_name(self.queue_name)
+
+ def _queue_setter(self, queue):
+ self.queue_name = queue.name() if queue else None
+
+ queue = property(_queue_getter, _queue_setter)
diff --git a/WebKitTools/QueueStatusServer/model/queuepropertymixin_unittest.py b/WebKitTools/QueueStatusServer/model/queuepropertymixin_unittest.py
new file mode 100644
index 0000000..9a301fe
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/model/queuepropertymixin_unittest.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010 Google, Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Research in Motion Ltd. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+from model.queuepropertymixin import QueuePropertyMixin
+from model.queues import Queue
+
+
+class ObjectWithQueueName(QueuePropertyMixin):
+ def __init__(self):
+ self.queue_name = None
+
+
+class QueuePropertyMixinTest(unittest.TestCase):
+ def test_queue_property(self):
+ test_object = ObjectWithQueueName()
+ mac_ews = Queue("mac-ews")
+ test_object.queue = mac_ews
+ self.assertEquals(test_object.queue.name(), "mac-ews")
+ self.assertEquals(test_object.queue_name, "mac-ews")
+ test_object.queue = None
+ self.assertEquals(test_object.queue_name, None)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/QueueStatusServer/model/queues.py b/WebKitTools/QueueStatusServer/model/queues.py
index 9658dd4..1d46f89 100644
--- a/WebKitTools/QueueStatusServer/model/queues.py
+++ b/WebKitTools/QueueStatusServer/model/queues.py
@@ -26,39 +26,88 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
import re
+from model.activeworkitems import ActiveWorkItems
+from model.workitems import WorkItems
+
+
+class Queue(object):
+
+ # Eventually the list of queues may be stored in the data store.
+ _all_queue_names = [
+ "commit-queue",
+ "style-queue",
+ "chromium-ews",
+ "qt-ews",
+ "gtk-ews",
+ "mac-ews",
+ "win-ews",
+ "efl-ews",
+ "cr-mac-ews",
+ ]
+
+ def __init__(self, name):
+ assert(name in self._all_queue_names)
+ self._name = name
+
+ @classmethod
+ def queue_with_name(cls, queue_name):
+ if queue_name not in cls._all_queue_names:
+ return None
+ return Queue(queue_name)
+
+ @classmethod
+ def all(cls):
+ return [Queue(name) for name in cls._all_queue_names]
+
+ @classmethod
+ def all_ews(cls):
+ return [queue for queue in cls.all() if queue.is_ews()]
+
+ def name(self):
+ return self._name
+
+ def work_items(self):
+ key_name = "work-items-%s" % (self._name)
+ return WorkItems.get_or_insert(key_name=key_name, queue_name=self._name)
-queues = [
- "commit-queue",
- "style-queue",
- "chromium-ews",
- "qt-ews",
- "gtk-ews",
- "mac-ews",
- "win-ews",
- "efl-ews",
-]
+ # FIXME: active_work_items is a bad name for this lock-table.
+ def active_work_items(self):
+ key_name = "active-work-items-%s" % (self._name)
+ return ActiveWorkItems.get_or_insert(key_name=key_name, queue_name=self._name)
+ def _caplitalize_after_dash(self, string):
+ return "-".join([word[0].upper() + word[1:] for word in string.split("-")])
-# FIXME: We need some sort of Queue object.
-def _title_case(string):
- words = string.split(" ")
- words = map(lambda word: word.capitalize(), words)
- return " ".join(words)
+ # For use in status bubbles or table headers
+ def short_name(self):
+ # HACK: chromium-ews is incorrectly named.
+ short_name = self._name.replace("chromium-ews", "Cr-Linux-ews")
+ short_name = short_name.replace("-ews", "")
+ short_name = short_name.replace("-queue", "")
+ return self._caplitalize_after_dash(short_name.capitalize())
+ def display_name(self):
+ # HACK: chromium-ews is incorrectly named.
+ display_name = self._name.replace("chromium-ews", "cr-linux-ews")
-def display_name_for_queue(queue_name):
- # HACK: chromium-ews is incorrectly named.
- display_name = queue_name.replace("chromium-ews", "cr-linux-ews")
+ display_name = display_name.replace("-", " ")
+ display_name = display_name.replace("cr", "chromium")
+ display_name = display_name.title()
+ display_name = display_name.replace("Ews", "EWS")
+ return display_name
- display_name = display_name.replace("-", " ")
- display_name = display_name.replace("cr", "chromium")
- display_name = _title_case(display_name)
- display_name = display_name.replace("Ews", "EWS")
- return display_name
+ _dash_regexp = re.compile("-")
+ def name_with_underscores(self):
+ return self._dash_regexp.sub("_", self._name)
-def name_with_underscores(dashed_name):
- regexp = re.compile("-")
- return regexp.sub("_", dashed_name)
+ def is_ews(self):
+ # Note: The style-queue is just like an EWS in that it has an EWS
+ # bubble, and it works off of the r? patches. If at some later
+ # point code wants to not treat the style-queue as an EWS
+ # (e.g. expecting is_ews() queues to have build results?)
+ # then we should fix all callers and change this check.
+ return self._name.endswith("-ews") or self._name == "style-queue"
diff --git a/WebKitTools/QueueStatusServer/model/queues_unittest.py b/WebKitTools/QueueStatusServer/model/queues_unittest.py
new file mode 100644
index 0000000..33070a8
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/model/queues_unittest.py
@@ -0,0 +1,73 @@
+# Copyright (C) 2010 Google, Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Research in Motion Ltd. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+
+from model.queues import Queue
+
+
+class QueueTest(unittest.TestCase):
+ def test_is_ews(self):
+ mac_ews = Queue("mac-ews")
+ self.assertTrue(mac_ews.is_ews())
+
+ def test_queue_with_name(self):
+ self.assertEqual(Queue.queue_with_name("bogus"), None)
+ self.assertEqual(Queue.queue_with_name("mac-ews").name(), "mac-ews")
+ self.assertRaises(AssertionError, Queue, ("bogus"))
+
+ def _assert_short_name(self, queue_name, short_name):
+ self.assertEquals(Queue(queue_name).short_name(), short_name)
+
+ def test_short_name(self):
+ self._assert_short_name("mac-ews", "Mac")
+ self._assert_short_name("chromium-ews", "Cr-Linux")
+ self._assert_short_name("commit-queue", "Commit")
+ self._assert_short_name("style-queue", "Style")
+
+ def _assert_display_name(self, queue_name, short_name):
+ self.assertEquals(Queue(queue_name).display_name(), short_name)
+
+ def test_display_name(self):
+ self._assert_display_name("mac-ews", "Mac EWS")
+ self._assert_display_name("chromium-ews", "Chromium Linux EWS")
+ self._assert_display_name("commit-queue", "Commit Queue")
+ self._assert_display_name("style-queue", "Style Queue")
+
+ def _assert_name_with_underscores(self, queue_name, short_name):
+ self.assertEquals(Queue(queue_name).name_with_underscores(), short_name)
+
+ def test_name_with_underscores(self):
+ self._assert_name_with_underscores("mac-ews", "mac_ews")
+ self._assert_name_with_underscores("chromium-ews", "chromium_ews")
+ self._assert_name_with_underscores("commit-queue", "commit_queue")
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/QueueStatusServer/model/queuestatus.py b/WebKitTools/QueueStatusServer/model/queuestatus.py
index 3d7e599..8002f89 100644
--- a/WebKitTools/QueueStatusServer/model/queuestatus.py
+++ b/WebKitTools/QueueStatusServer/model/queuestatus.py
@@ -27,12 +27,18 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from google.appengine.ext import db
+from model.queuepropertymixin import QueuePropertyMixin
-class QueueStatus(db.Model):
+
+class QueueStatus(db.Model, QueuePropertyMixin):
author = db.UserProperty()
queue_name = db.StringProperty()
+ bot_id = db.StringProperty()
active_bug_id = db.IntegerProperty()
active_patch_id = db.IntegerProperty()
message = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)
results_file = db.BlobProperty()
+
+ def is_retry_request(self):
+ return self.message == "Retry" # From AbstractQueue._retry_status
diff --git a/WebKitTools/QueueStatusServer/model/svnrevision.py b/WebKitTools/QueueStatusServer/model/svnrevision.py
index 70ec0cc..f5d3644 100644
--- a/WebKitTools/QueueStatusServer/model/svnrevision.py
+++ b/WebKitTools/QueueStatusServer/model/svnrevision.py
@@ -28,6 +28,7 @@
from google.appengine.ext import db
+
class SVNRevision(db.Model):
number = db.IntegerProperty()
broken_bots = db.StringListProperty(default=[])
diff --git a/WebKitTools/QueueStatusServer/model/workitems.py b/WebKitTools/QueueStatusServer/model/workitems.py
index 3ea59cb..fae6830 100644
--- a/WebKitTools/QueueStatusServer/model/workitems.py
+++ b/WebKitTools/QueueStatusServer/model/workitems.py
@@ -28,8 +28,40 @@
from google.appengine.ext import db
+from model.queuepropertymixin import QueuePropertyMixin
-class WorkItems(db.Model):
+
+class WorkItems(db.Model, QueuePropertyMixin):
queue_name = db.StringProperty()
item_ids = db.ListProperty(int)
date = db.DateTimeProperty(auto_now_add=True)
+
+ def display_position_for_attachment(self, attachment_id):
+ """Returns a 1-based index corresponding to the position
+ of the attachment_id in the queue. If the attachment is
+ not in this queue, this returns None"""
+ if attachment_id in self.item_ids:
+ return self.item_ids.index(attachment_id) + 1
+ return None
+
+ @staticmethod
+ def _unguarded_add(key, attachment_id):
+ work_items = db.get(key)
+ if attachment_id in work_items.item_ids:
+ return
+ work_items.item_ids.append(attachment_id)
+ work_items.put()
+
+ def add_work_item(self, attachment_id):
+ db.run_in_transaction(self._unguarded_add, self.key(), attachment_id)
+
+ @staticmethod
+ def _unguarded_remove(key, attachment_id):
+ work_items = db.get(key)
+ if attachment_id in work_items.item_ids:
+ # We should never have more than one entry for a work item, so we only need remove the first.
+ work_items.item_ids.remove(attachment_id)
+ work_items.put()
+
+ def remove_work_item(self, attachment_id):
+ db.run_in_transaction(self._unguarded_remove, self.key(), attachment_id)
diff --git a/WebKitTools/QueueStatusServer/model/workitems_unittest.py b/WebKitTools/QueueStatusServer/model/workitems_unittest.py
new file mode 100644
index 0000000..d53302e
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/model/workitems_unittest.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2010 Google, Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Research in Motion Ltd. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import unittest
+
+
+from model.workitems import WorkItems
+
+
+class WorkItemsTest(unittest.TestCase):
+ def test_display_position_for_attachment(self):
+ items = WorkItems()
+ items.item_ids = [0, 1, 2]
+ self.assertEquals(items.display_position_for_attachment(0), 1)
+ self.assertEquals(items.display_position_for_attachment(1), 2)
+ self.assertEquals(items.display_position_for_attachment(3), None)
+
+ def test_remove_work_item(self):
+ items = WorkItems()
+ items.item_ids = [0, 1, 2]
+ items.remove_work_item(0)
+ self.assertEqual(items.item_ids, [1, 2])
+ items.remove_work_item(4) # Should not throw
+ self.assertEqual(items.item_ids, [1, 2])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/WebKitTools/QueueStatusServer/templates/releasepatch.html b/WebKitTools/QueueStatusServer/templates/releasepatch.html
new file mode 100644
index 0000000..cbd6d6f
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/templates/releasepatch.html
@@ -0,0 +1,3 @@
+<form name="release_patch" enctype="multipart/form-data" method="post">
+Patch to release: <input name="attachment_id"> from <input name="queue_name"><input type="submit" value="Release locks and remove from queue"></div>
+</form>
diff --git a/WebKitTools/QueueStatusServer/templates/statusbubble.html b/WebKitTools/QueueStatusServer/templates/statusbubble.html
index 3102741..f11544d 100644
--- a/WebKitTools/QueueStatusServer/templates/statusbubble.html
+++ b/WebKitTools/QueueStatusServer/templates/statusbubble.html
@@ -39,6 +39,9 @@ body {
background-color: #E0B0FF;
border: 1px solid #ACA0B3;
}
+.queue_position {
+ font-size: 9px;
+}
</style>
<script>
function statusDetail(patch_id) {
@@ -53,7 +56,7 @@ function statusDetail(patch_id) {
title="{{ bubble.status.date|timesince }} ago"{% endif %}>
{{ bubble.name }}
{% if bubble.queue_position %}
- (#{{ bubble.queue_position }})
+ <span class="queue_position">#{{ bubble.queue_position }}</span>
{% endif %}
</div>
{% endfor %}
diff --git a/WebKitTools/QueueStatusServer/templates/submittoews.html b/WebKitTools/QueueStatusServer/templates/submittoews.html
new file mode 100644
index 0000000..fb9d8aa
--- /dev/null
+++ b/WebKitTools/QueueStatusServer/templates/submittoews.html
@@ -0,0 +1,3 @@
+<form name="submit_to_ews" enctype="multipart/form-data" method="post">
+Patch to submit: <input name="attachment_id"><input type="submit" value="Submit for EWS Processing"></div>
+</form>
diff --git a/WebKitTools/QueueStatusServer/templates/updatestatus.html b/WebKitTools/QueueStatusServer/templates/updatestatus.html
index 9343c60..0f98ba4 100644
--- a/WebKitTools/QueueStatusServer/templates/updatestatus.html
+++ b/WebKitTools/QueueStatusServer/templates/updatestatus.html
@@ -1,6 +1,10 @@
<form name="update_status" enctype="multipart/form-data" method="post">
Update status for a queue: <input name="queue_name">
<div>
+ Bot Id:
+ <input name="bot_id">
+ </div>
+ <div>
Active Bug Id:
<input name="bug_id">
</div>