Stomping down the dot regressions

This commit is contained in:
Reid 'arrdem' McKenzie 2023-07-08 21:00:25 -06:00
parent b3d3f9269f
commit 6bc0710479
12 changed files with 71 additions and 22 deletions

View file

@ -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")

View file

@ -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)

View file

@ -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
;

View file

@ -54,6 +54,10 @@ label {
margin-bottom: 20px;
}
.mr-auto {
margin-right: auto;
}
.u-flex1 {
flex: 1;
}

View file

@ -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;
}

View file

@ -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>

View file

@ -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">

View file

@ -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">

View file

@ -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 %}
{# #################################################################################################### #}

View file

@ -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")

View file

@ -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

View file

@ -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,
)