Stomping down the dot regressions
This commit is contained in:
parent
b3d3f9269f
commit
6bc0710479
12 changed files with 71 additions and 22 deletions
|
@ -1,10 +1,11 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from pathlib import Path
|
||||
from attrs import define
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import re
|
||||
from attrs import define
|
||||
|
||||
|
||||
OPTION_PATTERN = re.compile("; (?P<key>[a-z0-9_]+) = (?P<value>.*?)\n")
|
||||
|
||||
|
|
|
@ -12,9 +12,12 @@ from time import sleep
|
|||
from types import GeneratorType, new_class
|
||||
from typing import Optional
|
||||
|
||||
from aiosql.aiosql import _make_driver_adapter as get_adapter
|
||||
from aiosql.query_loader import QueryLoader
|
||||
from aiosql.aiosql import (
|
||||
_make_driver_adapter as get_adapter,
|
||||
)
|
||||
from aiosql.queries import Queries
|
||||
from aiosql.query_loader import QueryLoader
|
||||
|
||||
|
||||
_sqlite = get_adapter("sqlite3")
|
||||
_loader = QueryLoader(_sqlite, None)
|
||||
|
@ -211,6 +214,10 @@ class Db(Queries):
|
|||
|
||||
super().refresh_key(kid=kid, expiration=(datetime.now() + ttl).isoformat())
|
||||
|
||||
def start_job(self, *, jid: int):
|
||||
super().start_job(jid=jid)
|
||||
super().update_job_status(jid=jid, status="running")
|
||||
|
||||
def finish_job(self, *, jid: int, state: str, message: Optional[str] = None):
|
||||
super().finish_job(jid=jid, state=state, message=message)
|
||||
|
||||
|
|
|
@ -79,6 +79,10 @@ SELECT
|
|||
, fa.nozzle_diameter
|
||||
, fa.filament_id
|
||||
, (SELECT name FROM filament WHERE id = fa.filament_id) AS filament_name
|
||||
, (SELECT name FROM job_statuses WHERE id = j.status_id) AS status
|
||||
, j.started_at
|
||||
, j.cancelled_at
|
||||
, j.finished_at
|
||||
FROM jobs j
|
||||
INNER JOIN files f
|
||||
ON j.file_id = f.id
|
||||
|
@ -86,6 +90,7 @@ LEFT JOIN file_analysis fa
|
|||
ON fa.file_id = f.id
|
||||
WHERE
|
||||
finished_at IS NULL
|
||||
AND cancelled_at IS NULL
|
||||
AND (:uid IS NULL OR j.user_id = :uid)
|
||||
AND f.id IS NOT NULL
|
||||
;
|
||||
|
@ -103,12 +108,17 @@ LIMIT 1
|
|||
|
||||
-- name: list-job-history
|
||||
SELECT
|
||||
*
|
||||
j.*
|
||||
, fa.id as analysis_id
|
||||
, (SELECT name FROM job_statuses WHERE id = j.status_id) AS `status`
|
||||
FROM jobs j
|
||||
INNER JOIN files f
|
||||
ON j.file_id = f.id
|
||||
LEFT JOIN file_analysis fa
|
||||
ON fa.file_id = f.id
|
||||
WHERE
|
||||
finished_at IS NOT NULL
|
||||
AND (:uid IS NULL OR user_id = :uid)
|
||||
AND (:uid IS NULL OR j.user_id = :uid)
|
||||
ORDER BY
|
||||
datetime(finished_at) DESC
|
||||
LIMIT 25
|
||||
|
@ -201,3 +211,10 @@ WHERE
|
|||
user_id = :uid
|
||||
AND file_id = :fid
|
||||
;
|
||||
|
||||
-- name: update-job-status!
|
||||
UPDATE jobs
|
||||
SET status_id = (SELECT id FROM job_statuses WHERE name = :status)
|
||||
WHERE
|
||||
id = :jid
|
||||
;
|
||||
|
|
|
@ -54,6 +54,10 @@ label {
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mr-auto {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.u-flex1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
background-color: $secondary_green;
|
||||
}
|
||||
|
||||
.dot.analyzing {
|
||||
background-color: $black;
|
||||
}
|
||||
|
||||
.dot.queued {
|
||||
background-color: $secondary_blue;
|
||||
}
|
||||
|
@ -43,7 +47,7 @@
|
|||
background-color: $red;
|
||||
}
|
||||
|
||||
.dot--basic {
|
||||
.dot--active {
|
||||
animation: blink 2s infinite;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
<div class="file row u-flex">
|
||||
<div class="details six columns u-flex u-flex-wrap">
|
||||
<span class="file-name">{{ file.filename }}</span>
|
||||
</div>
|
||||
<div class="six columns u-flex u-flex-wrap">
|
||||
<span class="file-sucesses"><label>Successes</label>{{ file.print_successes }}</span>
|
||||
<span class="file-failures"><label>Failures</label>{{ file.print_failures }}</span>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{% for job in jobs %}
|
||||
<div class="job row u-flex">
|
||||
<div class="details six columns u-flex u-flex-wrap">
|
||||
<div class="job-filename u-flex">
|
||||
<div class="job-filename u-flex mr-auto">
|
||||
<label for="filename">File</label>
|
||||
<span name="filename">{{ctx.db.fetch_file(ctx.uid, job.file_id).filename or "it's a secret"}}</span>
|
||||
</div>
|
||||
|
@ -25,7 +25,7 @@
|
|||
<div class="two columns">
|
||||
<div class="job-status u-flex">
|
||||
<label for="state">Status</label>
|
||||
<div class="dot {{ macros.job_state(job) }} tooltip bottom" data-text="{{ macros.job_state(job) }}" style="--dot-size: 1em;"> </div>
|
||||
<div class="dot {{ macros.job_state(job) }} {{ macros.job_active(job) }} tooltip bottom" data-text="{{ macros.job_state(job) }}" style="--dot-size: 1em;"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls four columns u-flex u-ml-auto">
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{% if job.started_at %}
|
||||
<div class="job-runtime u-flex">
|
||||
<label for="runtime">Runtime</label>
|
||||
<span name="Runtime">{{ (datetime.now() - datetime.fromisoformat(job.started_at)) }}</span>
|
||||
<span name="Runtime">{{ (datetime.utcnow() - datetime.fromisoformat(job.started_at)) }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="job-constraint u-flex">
|
||||
|
@ -34,7 +34,7 @@
|
|||
<div class="two columns">
|
||||
<div class="job-status u-flex">
|
||||
<label for="state">Status</label>
|
||||
<div class="dot {{ macros.job_state(job) }} tooltip bottom" data-text="{{ macros.job_state(job) }}" style="--dot-size: 1em;"> </div>
|
||||
<div class="dot {{ macros.job_state(job) }} {{ macros.job_active(job) }} tooltip bottom" data-text="{{ macros.job_state(job) }}" style="--dot-size: 1em;"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls u-flex u-ml-auto">
|
||||
|
|
|
@ -33,10 +33,19 @@
|
|||
{% endmacro %}
|
||||
|
||||
{% macro job_state(job) %}
|
||||
{{ 'queued' if (not job.finished_at and not job.printer_id and not job.cancelled_at) else
|
||||
'running' if (not job.finished_at and job.printer_id and not job.cancelled_at) else
|
||||
'cancelling' if (not job.finished_at and job.cancelled_at) else
|
||||
job.status }}
|
||||
{{ job.status if job.finished_at else
|
||||
'cancelling' if job.cancelled_at else
|
||||
'analyzing' if not job.analysis_id else
|
||||
'queued' if (not job.printer_id and not job.started_at) else
|
||||
'running' if job.printer_id else
|
||||
job.status
|
||||
}}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro job_active(job) %}
|
||||
{{
|
||||
'dot--active' if not job.finished_at else ''
|
||||
}}
|
||||
{% endmacro %}
|
||||
|
||||
{# #################################################################################################### #}
|
||||
|
|
|
@ -7,10 +7,11 @@ Supporting the core app with asynchronous maintenance tasks.
|
|||
Mostly related to monitoring and managing Printer state.
|
||||
"""
|
||||
|
||||
import os
|
||||
from contextlib import closing
|
||||
from datetime import datetime, timedelta
|
||||
from functools import cache
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from pprint import pformat
|
||||
from typing import Callable
|
||||
|
@ -18,9 +19,8 @@ from urllib import parse as urlparse
|
|||
|
||||
from cherrypy.process.plugins import Monitor
|
||||
from fastmail import FastMailSMTP
|
||||
from flask import Flask as App, render_template
|
||||
from gcode import analyze_gcode_file
|
||||
from flask import Flask as App
|
||||
from flask import render_template
|
||||
from octorest import OctoRest as _OR
|
||||
from requests import Response
|
||||
from requests.exceptions import (
|
||||
|
@ -256,8 +256,12 @@ def pull_jobs(app: App, db: Db) -> None:
|
|||
printer_state = {"disconnected": True, "error": True}
|
||||
|
||||
if job_state.get("progress", {}).get("completion", 0.0) == 100.0:
|
||||
log.info(f"Job {job.id} has succeeded")
|
||||
db.finish_job(jid=job.id, state="success")
|
||||
# Debounce jobs which JUST started from immediately completing due to a data race
|
||||
start_date = datetime.fromisoformat(job.started_at)
|
||||
runtime = datetime.utcnow() - start_date
|
||||
if runtime >= timedelta(seconds=15): # 3 polling cycles
|
||||
log.info(f"Job {job.id} has succeeded")
|
||||
db.finish_job(jid=job.id, state="success")
|
||||
|
||||
elif printer_state.get("error"):
|
||||
log.warn(f"Job {job.id} has failed")
|
||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import pytest
|
||||
from tentacles.db import Db
|
||||
|
||||
|
||||
# FIXME: Should this be an autouse fixture? Maybe. Doesn't buy much tho.
|
||||
logging.addLevelName(logging.DEBUG - 5, "TRACE")
|
||||
logging.TRACE = logging.DEBUG - 5
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
import re
|
||||
|
||||
from gcode import (
|
||||
parse_prusa_config_str,
|
||||
OPTION_PATTERN,
|
||||
analyze_gcode_str,
|
||||
GcodeAnalysis,
|
||||
OPTION_PATTERN,
|
||||
parse_prusa_config_str,
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue