def __call__(self): # N.B. form = self.request.form analyst = self.request.get('analyst', '') template = self.request.get('template', '') instrument = self.request.get('instrument', '') if not analyst: message = _("Analyst must be specified.") self.context.plone_utils.addPortalMessage(message, 'info') self.request.RESPONSE.redirect(self.context.absolute_url()) return rc = getToolByName(self.context, REFERENCE_CATALOG) wf = getToolByName(self.context, "portal_workflow") pm = getToolByName(self.context, "portal_membership") ws = _createObjectByType("Worksheet", self.context, tmpID()) ws.processForm() # Set analyst and instrument ws.setAnalyst(analyst) if instrument: ws.setInstrument(instrument) # Set the default layout for results display ws.setResultsLayout(self.context.bika_setup.getWorksheetLayout()) # overwrite saved context UID for event subscribers self.request['context_uid'] = ws.UID() # if no template was specified, redirect to blank worksheet if not template: ws.processForm() self.request.RESPONSE.redirect(ws.absolute_url() + "/add_analyses") return wst = rc.lookupObject(template) ws.applyWorksheetTemplate(wst) if ws.getLayout(): self.request.RESPONSE.redirect(ws.absolute_url() + "/manage_results") elif api.is_queued(ws): msg = _("Analyses for {} have been queued".format(_api.get_id(ws))) self.context.plone_utils.addPortalMessage(msg) self.request.RESPONSE.redirect(_api.get_url(ws.aq_parent)) else: msg = _("No analyses were added") self.context.plone_utils.addPortalMessage(msg) self.request.RESPONSE.redirect(ws.absolute_url() + "/add_analyses")
def folder_item(self, obj, item, index): # Don't do anything if senaite.queue is not enabled if not api.is_queue_enabled(): return if api.is_queued(obj): item["disabled"] = True icon = get_queue_image("queued.gif", width="55px") item["replace"]["state_title"] = _("Queued") item["replace"]["getProgressPercentage"] = icon return item
class IQueueControlPanel(Interface): """Control panel Settings """ server = schema.TextLine( title=_(u"Queue server"), description=_( "URL of the zeo client that will act as the queue server. This is, " "the zeo client others will rely on regarding tasks addition, " "retrieval and removal. An empty value or a non-reachable queue " "server disables the asynchronous processing of tasks. In such " "case, system will behave as if senaite.queue was not installed" ), default=u"http://localhost:8080/senaite", constraint=valid_url_constraint, required=False, ) default = schema.Int( title=_(u"Number of objects to process per task"), description=_( "Default number of objects to process in a single request when the " "task contains multiple items. The items from a task are processed " "in chunks, and remaining are re-queued for later. For instance, " "when a user selects multiple analyses for their assignment to a " "worksheet, only one task is generated. If the value defined is 5, " "the analyses will be assigned in chunks of this size, and the " "system will keep generating tasks for the remaining analyses " "all them are finally assigned. Higher values increment the chance " "of transaction commit conflicts, while lower values tend to slow " "down the completion of the whole task. " "A value of 0 disables queueing if tasks functionality at all. " "Default value: {}".format(DEFAULT_OBJ_TASK) ), min=0, max=20, default=DEFAULT_OBJ_TASK, required=True, ) max_retries = schema.Int( title=_(u"Maximum retries"), description=_( "Number of times a task will be re-queued before being considered " "as failed. A value of 0 disables the re-queue of failing tasks. " "Default value: 3" ), min=0, max=10, default=3, required=True, ) min_seconds_task = schema.Int( title=_(u"Minimum seconds"), description=_( "Minimum number of seconds to book per task. If the task is " "performed very rapidly, it will have priority over a transaction " "done from userland. In case of conflict, the transaction from " "userland will fail and will be retried up to 3 times. This " "setting makes the thread that handles the task to take some time " "to complete, thus preventing threads from userland to be delayed " "or fail. Default value: 3" ), min=3, max=30, default=3, required=True, ) max_seconds_unlock = schema.Int( title=_(u"Maximum seconds"), description=_( "Number of seconds to wait for a task to finish before being " "re-queued or considered as failed. System will keep retrying the " "task until the value set in 'Maximum retries' is reached, at" "which point the task will be definitely considered as failed and " "no further actions will take place. " "Minimum value: 30, Default value: 120" ), min=30, max=1800, default=120, required=True, ) auth_key = schema.TextLine( title=_(u"Auth secret key"), description=_( "This secret key is used by senaite.queue to generate an encrypted " "token (symmetric encryption) for the authentication of requests " "sent by queue clients and workers to the Queue's server API. " "Must be 32 url-safe base64-encoded bytes" ), default=safe_unicode(base64.urlsafe_b64encode(os.urandom(32))), constraint=auth_key_constraint, required=True, )
class QueueControlPanelForm(RegistryEditForm): schema = IQueueControlPanel schema_prefix = "senaite.queue" label = _("SENAITE QUEUE Settings")
def __init__(self, context, request): super(TasksListingView, self).__init__(context, request) # Query is ignored in `folderitems` method and only there to override # the default settings self.catalog = "uid_catalog" self.contentFilter = {"UID": api.get_uid(context)} # Set the view name with `@@` prefix to get the right API URL self.__name__ = "@@queue_tasks" self.pagesize = 20 self.show_select_column = True self.show_search = False self.show_table_footer = True self.show_workflow_action_buttons = True self.show_categories = False self.expand_all_categories = True self.categories = [] self.title = _("Queue monitor") self.sort_on = "priority" self.sort_order = "ascending" self.columns = collections.OrderedDict((("task_short_uid", { "title": _("Task UID"), "sortable": False, }), ("priority", { "title": _("Priority"), "sortable": True, }), ("created", { "title": _("Created"), "sortable": True, }), ("name", { "title": _("Name"), "sortable": True, }), ("context_path", { "title": _("Context"), "sortable": True, }), ("username", { "title": _("Username"), "sortable": True, }), ("status", { "title": _("Status"), "sortable": True, }))) url = api.get_url(self.context) url = "{}/workflow_action?action=".format(url) self.review_states = [{ "id": "default", "title": _("Active tasks"), "contentFilter": {}, "columns": self.columns.keys(), "transitions": [], "custom_transitions": [{ "id": "queue_requeue", "title": _("Requeue"), "url": "{}{}".format(url, "queue_requeue") }, { "id": "queue_remove", "title": _("Remove"), "url": "{}{}".format(url, "queue_remove") }], "confirm_transitions": [ "queue_remove", ] }, { "id": "failed", "title": _("Failed tasks"), "contentFilter": {}, "columns": self.columns.keys(), "transitions": [], "custom_transitions": [{ "id": "queue_requeue", "title": _("Requeue"), "url": "{}{}".format(url, "queue_requeue") }, { "id": "queue_remove", "title": _("Remove"), "url": "{}{}".format(url, "queue_remove") }], "confirm_transitions": [ "queue_requeue", ] }, { "id": "all", "title": _("All tasks +ghosts"), "contentFilter": {}, "columns": self.columns.keys(), "transitions": [], "custom_transitions": [{ "id": "queue_requeue", "title": _("Requeue"), "url": "{}{}".format(url, "queue_requeue") }, { "id": "queue_remove", "title": _("Remove"), "url": "{}{}".format(url, "queue_remove") }], "confirm_transitions": [ "queue_remove", ] }]
def folderitems(self): states_map = { "running": "state-published", "failed": "state-retracted", "queued": "state-active", "ghost": "state-unassigned", } # flag for manual sorting self.manual_sort_on = self.get_sort_on() # Get the items status = ["running", "queued"] if self.review_state.get("id") == "failed": status = ["failed"] elif self.review_state.get("id") == "all": status = ["running", "queued", "failed", "ghost"] items = map(self.make_item, qapi.get_queue().get_tasks(status=status)) # Infere the priorities site_url = api.get_url(api.get_portal()) api_url = "{}/@@API/v1/@@API/senaite/v1/queue_server".format(site_url) idx = 1 for item in items: if item["status"] not in ["queued", "running"]: priority = 0 else: priority = idx idx += 1 created = datetime.fromtimestamp(int(item["created"])).isoformat() context_link = get_link(item["context_path"], item["context_path"]) task_link = "{}/{}".format(api_url, item["uid"]) params = {"class": "text-monospace"} task_link = get_link(task_link, item["task_short_uid"], **params) status_msg = _(item["status"]) css_class = states_map.get(item["status"]) if item.get("ghost"): css_class = "{} {}".format(css_class, states_map["ghost"]) status_msg = "{} ({})".format(status_msg, _("ghost")) item.update({ "priority": str(priority).zfill(4), "state_class": css_class, "replace": { "status": status_msg, "context_path": context_link, "task_short_uid": task_link, "created": created, } }) # Sort the items sort_on = self.manual_sort_on in self.columns.keys() or "priority" reverse = self.get_sort_order() == "ascending" items = sorted(items, key=itemgetter(sort_on), reverse=reverse) # Pagination self.total = len(items) limit_from = self.get_limit_from() if limit_from and len(items) > limit_from: return items[limit_from:self.pagesize + limit_from] return items[:self.pagesize]