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