Beispiel #1
0
    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")
Beispiel #2
0
    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")
Beispiel #5
0
    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",
            ]
        }]
Beispiel #6
0
    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]