<commit_before>"""
The render job watch panel allows you to 
   1. setup filters to automatically load jobs. (defaults to loading your jobs)
   2. individually add jobs you want to watch.
"""
import os
import logging
from functools import partial

import plow.client
from plow.client import JobState

from plow.gui.manifest import QtCore, QtGui
from plow.gui.panels import Panel
from plow.gui.common.widgets import CheckableListBox, BooleanCheckBox, SpinSliderWidget, ManagedListWidget
from plow.gui.common.job import JobProgressBar, JobSelectionDialog, JobStateWidget, jobContextMenu
from plow.gui import constants 
from plow.gui.util import formatMaxValue, formatDateTime, formatDuration
from plow.gui.event import EventManager


LOGGER = logging.getLogger(__name__)


JOBID_ROLE = QtCore.Qt.UserRole
JOB_ROLE = QtCore.Qt.UserRole + 1

class RenderJobWatchPanel(Panel):

    def __init__(self, name="Render Watch", parent=None):
        Panel.__init__(self, name, "Render Watch", parent)

        # Setup the default configuration
        self.setAttr("loadMine", True)
        self.setAttr("projects", [])
        self.setAttr("allProjects", True)
        self.setAttr("refreshSeconds", 5)
        self.setAttr("users", [])

        self.setWidget(RenderJobWatchWidget(self.attrs, self))
        self.setWindowTitle(name)

    def init(self):
        # TODO
        # sweep button (remove finished)
        # refresh button
        # seperator
        # kill button (multi-select)
        # comment button (multi-select)
        # 
        self.titleBarWidget().addAction(
            QtGui.QIcon(":/load.png"), "Load", self.openLoadDialog)

        self.titleBarWidget().addAction(
            QtGui.QIcon(":/sweep.png"), "Remove Finished Jobs", self.removeFinishedJobs)

    def openLoadDialog(self):
        dialog = JobSelectionDialog()
        if dialog.exec_():
            widget = self.widget()
            for job in dialog.getSelectedJobs():
                widget.addJob(job) 

    def _openPanelSettingsDialog(self):
        d = RenderJobWatchSettingsDialog(self.attrs)
        if d.exec_():
            self.attrs.update(d.getAttrs())
            self.setRefreshTime(self.attrs["refreshSeconds"])

    def removeFinishedJobs(self):
         self.widget().removeFinishedJobs()

    def refresh(self):
        self.widget().refresh()


class RenderJobWatchWidget(QtGui.QWidget):

    Header = ["Job", "State", "Run", "Wait", "Min", "Max", "Duration", "Progress"]
    Width = [400, 75, 60, 60, 60, 60, 100, 250]

    def __init__(self, attrs, parent=None):
        QtGui.QWidget.__init__(self, parent)
        layout = QtGui.QVBoxLayout(self)
        layout.setContentsMargins(4,0,4,4)

        self.attrs = attrs
        self.__jobs = { }

        self.__tree = tree = QtGui.QTreeWidget(self)
        tree.setHeaderLabels(self.Header)
        tree.setColumnCount(len(self.Header))
        tree.setUniformRowHeights(True)
        tree.viewport().setFocusPolicy(QtCore.Qt.NoFocus)

        for i, v in enumerate(self.Width):
            tree.setColumnWidth(i, v) 

        def treeMousePress(event):
            item = self.__tree.itemAt(event.pos())
            if not item:
                self.__tree.clearSelection()
            QtGui.QTreeWidget.mousePressEvent(self.__tree, event)

        tree.mousePressEvent = treeMousePress

        layout.addWidget(tree)

        # connections
        tree.itemDoubleClicked.connect(self.__itemDoubleClicked)
        tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        tree.customContextMenuRequested.connect(self.__showContextMenu)

    def refresh(self):
        self.__updateExistingJobs()
        self.__findNewJobs()

    def addJob(self, job):
        if self.__jobs.has_key(job.id):
            return False

        item = QtGui.QTreeWidgetItem([
            job.name,
            "",
            "%02d" % job.totals.running,
            "%02d" % job.totals.waiting,
            "%02d" % job.minCores,
            formatMaxValue(job.maxCores),
            formatDuration(job.startTime, job.stopTime)
        ])

        self.__jobs[job.id] = item

        item.setToolTip(6, "Started: %s\nStopped:%s" % 
            (formatDateTime(job.startTime), formatDateTime(job.stopTime)))

        item.setData(0, JOBID_ROLE, job.id)
        item.setData(0, JOB_ROLE, job)

        self.__tree.addTopLevelItem(item)

        progress = JobProgressBar(job.totals, self.__tree)
        self.__tree.setItemWidget(item, len(self.Header)-1, progress);

        self.__setJobStateAndColor(item)
        # state = JobStateWidget(job.state, job.totals.dead, self)
        # self.__tree.setItemWidget(item, 1, state)

        return True

    def updateJob(self, job):
        item = self.__jobs[job.id]
        item.setData(0, JOB_ROLE, job)

        item.setText(2, "%02d" % job.totals.running)
        item.setText(3, "%02d" % job.totals.waiting)
        item.setText(4, "%02d" % job.minCores)
        item.setText(5, formatMaxValue(job.maxCores))
        item.setText(6, formatDuration(job.startTime, job.stopTime))
        item.setToolTip(6, "Started: %s\nStopped:%s" % 
            (formatDateTime(job.startTime), formatDateTime(job.stopTime)))

        self.__tree.itemWidget(item, len(self.Header)-1).setTotals(job.totals)
        # self.__tree.itemWidget(item, 1).setState(job.state, job.totals.dead)
        self.__setJobStateAndColor(item)

    def removeFinishedJobs(self):
        finished = []
        for item in self.__jobs.itervalues():
            if item.data(0, JOB_ROLE).state == JobState.FINISHED:
                finished.append(item)
        [self.removeJobItem(item) for item in finished]

    def removeJobItem(self, item):
        jobid = str(item.data(0, JOBID_ROLE))
        try:
            del self.__jobs[jobid]
        except Exception, e:
            print e
        idx = self.__tree.indexOfTopLevelItem(item)
        self.__tree.takeTopLevelItem(idx)

    def __updateJobs(self, jobs):
        for job in jobs:
            if not self.__jobs.has_key(job.id):
                self.addJob(job)
            else:
                self.updateJob(job)

    def __updateExistingJobs(self):
        FINISHED = JobState.FINISHED
        req = { }
        req["matchingOnly"] = True
        req["jobIds"] = [jobId for jobId, item in self.__jobs.iteritems() 
            if item.data(0, JOB_ROLE).state != FINISHED]
        self.__updateJobs(plow.client.get_jobs(**req))
<commit_msg>refs issue #40 - Continuing to update finished jobs for 60 seconds beyond their stop time<commit_after>"""
The render job watch panel allows you to 
   1. setup filters to automatically load jobs. (defaults to loading your jobs)
   2. individually add jobs you want to watch.
"""
import os
import logging
from functools import partial

import plow.client
from plow.client import JobState

from plow.gui.manifest import QtCore, QtGui
from plow.gui.panels import Panel
from plow.gui.common.widgets import CheckableListBox, BooleanCheckBox, SpinSliderWidget, ManagedListWidget
from plow.gui.common.job import JobProgressBar, JobSelectionDialog, JobStateWidget, jobContextMenu
from plow.gui import constants 
from plow.gui.util import formatMaxValue, formatDateTime, formatDuration
from plow.gui.event import EventManager


LOGGER = logging.getLogger(__name__)


JOBID_ROLE = QtCore.Qt.UserRole
JOB_ROLE = QtCore.Qt.UserRole + 1

class RenderJobWatchPanel(Panel):

    def __init__(self, name="Render Watch", parent=None):
        Panel.__init__(self, name, "Render Watch", parent)

        # Setup the default configuration
        self.setAttr("loadMine", True)
        self.setAttr("projects", [])
        self.setAttr("allProjects", True)
        self.setAttr("refreshSeconds", 5)
        self.setAttr("users", [])

        self.setWidget(RenderJobWatchWidget(self.attrs, self))
        self.setWindowTitle(name)

    def init(self):
        # TODO
        # sweep button (remove finished)
        # refresh button
        # seperator
        # kill button (multi-select)
        # comment button (multi-select)
        # 
        self.titleBarWidget().addAction(
            QtGui.QIcon(":/load.png"), "Load", self.openLoadDialog)

        self.titleBarWidget().addAction(
            QtGui.QIcon(":/sweep.png"), "Remove Finished Jobs", self.removeFinishedJobs)

    def openLoadDialog(self):
        dialog = JobSelectionDialog()
        if dialog.exec_():
            widget = self.widget()
            for job in dialog.getSelectedJobs():
                widget.addJob(job) 

    def _openPanelSettingsDialog(self):
        d = RenderJobWatchSettingsDialog(self.attrs)
        if d.exec_():
            self.attrs.update(d.getAttrs())
            self.setRefreshTime(self.attrs["refreshSeconds"])

    def removeFinishedJobs(self):
         self.widget().removeFinishedJobs()

    def refresh(self):
        self.widget().refresh()


class RenderJobWatchWidget(QtGui.QWidget):

    Header = ["Job", "State", "Run", "Wait", "Min", "Max", "Duration", "Progress"]
    Width = [400, 75, 60, 60, 60, 60, 100, 250]

    def __init__(self, attrs, parent=None):
        QtGui.QWidget.__init__(self, parent)
        layout = QtGui.QVBoxLayout(self)
        layout.setContentsMargins(4,0,4,4)

        self.attrs = attrs
        self.__jobs = { }

        self.__tree = tree = QtGui.QTreeWidget(self)
        tree.setHeaderLabels(self.Header)
        tree.setColumnCount(len(self.Header))
        tree.setUniformRowHeights(True)
        tree.viewport().setFocusPolicy(QtCore.Qt.NoFocus)

        for i, v in enumerate(self.Width):
            tree.setColumnWidth(i, v) 

        def treeMousePress(event):
            item = self.__tree.itemAt(event.pos())
            if not item:
                self.__tree.clearSelection()
            QtGui.QTreeWidget.mousePressEvent(self.__tree, event)

        tree.mousePressEvent = treeMousePress

        layout.addWidget(tree)

        # connections
        tree.itemDoubleClicked.connect(self.__itemDoubleClicked)
        tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        tree.customContextMenuRequested.connect(self.__showContextMenu)

    def refresh(self):
        self.__updateExistingJobs()
        self.__findNewJobs()

    def addJob(self, job):
        if self.__jobs.has_key(job.id):
            return False

        item = QtGui.QTreeWidgetItem([
            job.name,
            "",
            "%02d" % job.totals.running,
            "%02d" % job.totals.waiting,
            "%02d" % job.minCores,
            formatMaxValue(job.maxCores),
            formatDuration(job.startTime, job.stopTime)
        ])

        self.__jobs[job.id] = item

        item.setToolTip(6, "Started: %s\nStopped:%s" % 
            (formatDateTime(job.startTime), formatDateTime(job.stopTime)))

        item.setData(0, JOBID_ROLE, job.id)
        item.setData(0, JOB_ROLE, job)

        self.__tree.addTopLevelItem(item)

        progress = JobProgressBar(job.totals, self.__tree)
        self.__tree.setItemWidget(item, len(self.Header)-1, progress);

        self.__setJobStateAndColor(item)
        # state = JobStateWidget(job.state, job.totals.dead, self)
        # self.__tree.setItemWidget(item, 1, state)

        return True

    def updateJob(self, job):
        item = self.__jobs[job.id]
        item.setData(0, JOB_ROLE, job)

        item.setText(2, "%02d" % job.totals.running)
        item.setText(3, "%02d" % job.totals.waiting)
        item.setText(4, "%02d" % job.minCores)
        item.setText(5, formatMaxValue(job.maxCores))
        item.setText(6, formatDuration(job.startTime, job.stopTime))
        item.setToolTip(6, "Started: %s\nStopped:%s" % 
            (formatDateTime(job.startTime), formatDateTime(job.stopTime)))

        self.__tree.itemWidget(item, len(self.Header)-1).setTotals(job.totals)
        # self.__tree.itemWidget(item, 1).setState(job.state, job.totals.dead)
        self.__setJobStateAndColor(item)

    def removeFinishedJobs(self):
        finished = []
        for item in self.__jobs.itervalues():
            if item.data(0, JOB_ROLE).state == JobState.FINISHED:
                finished.append(item)
        [self.removeJobItem(item) for item in finished]

    def removeJobItem(self, item):
        jobid = str(item.data(0, JOBID_ROLE))
        try:
            del self.__jobs[jobid]
        except Exception, e:
            print e
        idx = self.__tree.indexOfTopLevelItem(item)
        self.__tree.takeTopLevelItem(idx)

    def __updateJobs(self, jobs):
        for job in jobs:
            if not self.__jobs.has_key(job.id):
                self.addJob(job)
            else:
                self.updateJob(job)

    def __updateExistingJobs(self):
        FINISHED = JobState.FINISHED
        now = QtCore.QDateTime.currentDateTimeUtc()
        toDateTime = QtCore.QDateTime.fromMSecsSinceEpoch

        req = {"matchingOnly": True}

        jobs = []
        for jobId, item in self.__jobs.iteritems():
            job = item.data(0, JOB_ROLE)

            # we will continue to pull updates on finished jobs for
            # 60 seconds longer
            if job.state == FINISHED:
                sec = toDateTime(job.stopTime).secsTo(now)
                if sec > 60:              
                    continue

            jobs.append(jobId)

        req["jobIds"] = jobs
        self.__updateJobs(plow.client.get_jobs(**req))

{'fraction_non_alphanumeric': 0.11257735033176769, 'fraction_numerical': 0.008946544397226572, 'mean_word_length': 2.4923197084092683, 'pattern_counts': {'":': 5, '<': 3, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3850356', 'n_tokens_mistral': 4231, 'n_tokens_neox': 4031, 'n_words': 805}