mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-15 13:16:29 +00:00
Sort jobs by creation in API (#5545)
* Sort jobs by creation in API * Fix tests missing new field * Fix sorting logic around child jobs
This commit is contained in:
parent
da6bdfa795
commit
600bf91c4f
@ -31,9 +31,16 @@ class APIJobs(CoreSysAttributes):
|
||||
raise APINotFound("Job does not exist") from None
|
||||
|
||||
def _list_jobs(self, start: SupervisorJob | None = None) -> list[dict[str, Any]]:
|
||||
"""Return current job tree."""
|
||||
"""Return current job tree.
|
||||
|
||||
Jobs are added to cache as they are created so by default they are in oldest to newest.
|
||||
This is correct ordering for child jobs as it makes logical sense to present those in
|
||||
the order they occurred within the parent. For the list as a whole, sort from newest
|
||||
to oldest as its likely any client is most interested in the newer ones.
|
||||
"""
|
||||
# Initially sort oldest to newest so all child lists end up in correct order
|
||||
jobs_by_parent: dict[str | None, list[SupervisorJob]] = {}
|
||||
for job in self.sys_jobs.jobs:
|
||||
for job in sorted(self.sys_jobs.jobs):
|
||||
if job.internal:
|
||||
continue
|
||||
|
||||
@ -42,11 +49,15 @@ class APIJobs(CoreSysAttributes):
|
||||
else:
|
||||
jobs_by_parent[job.parent_id].append(job)
|
||||
|
||||
# After parent-child organization, sort the root jobs only from newest to oldest
|
||||
job_list: list[dict[str, Any]] = []
|
||||
queue: list[tuple[list[dict[str, Any]], SupervisorJob]] = (
|
||||
[(job_list, start)]
|
||||
if start
|
||||
else [(job_list, job) for job in jobs_by_parent.get(None, [])]
|
||||
else [
|
||||
(job_list, job)
|
||||
for job in sorted(jobs_by_parent.get(None, []), reverse=True)
|
||||
]
|
||||
)
|
||||
|
||||
while queue:
|
||||
|
@ -19,6 +19,7 @@ from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import HassioError, JobNotFound, JobStartException
|
||||
from ..homeassistant.const import WSEvent
|
||||
from ..utils.common import FileConfiguration
|
||||
from ..utils.dt import utcnow
|
||||
from ..utils.sentry import capture_exception
|
||||
from .const import ATTR_IGNORE_CONDITIONS, FILE_CONFIG_JOBS, JobCondition
|
||||
from .validate import SCHEMA_JOBS_CONFIG
|
||||
@ -79,10 +80,12 @@ class SupervisorJobError:
|
||||
return {"type": self.type_.__name__, "message": self.message}
|
||||
|
||||
|
||||
@define
|
||||
@define(order=True)
|
||||
class SupervisorJob:
|
||||
"""Representation of a job running in supervisor."""
|
||||
|
||||
created: datetime = field(init=False, factory=utcnow, on_setattr=frozen)
|
||||
uuid: UUID = field(init=False, factory=lambda: uuid4().hex, on_setattr=frozen)
|
||||
name: str | None = field(default=None, validator=[_invalid_if_started])
|
||||
reference: str | None = field(default=None, on_setattr=_on_change)
|
||||
progress: float = field(
|
||||
@ -94,7 +97,6 @@ class SupervisorJob:
|
||||
stage: str | None = field(
|
||||
default=None, validator=[_invalid_if_done], on_setattr=_on_change
|
||||
)
|
||||
uuid: UUID = field(init=False, factory=lambda: uuid4().hex, on_setattr=frozen)
|
||||
parent_id: UUID | None = field(
|
||||
factory=lambda: _CURRENT_JOB.get(None), on_setattr=frozen
|
||||
)
|
||||
@ -119,6 +121,7 @@ class SupervisorJob:
|
||||
"done": self.done,
|
||||
"parent_id": self.parent_id,
|
||||
"errors": [err.as_dict() for err in self.errors],
|
||||
"created": self.created.isoformat(),
|
||||
}
|
||||
|
||||
def capture_error(self, err: HassioError | None = None) -> None:
|
||||
|
@ -102,6 +102,18 @@ async def test_jobs_tree_representation(api_client: TestClient, coresys: CoreSys
|
||||
result = await resp.json()
|
||||
assert result["data"]["jobs"] == [
|
||||
{
|
||||
"created": ANY,
|
||||
"name": "test_jobs_tree_alt",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": "init",
|
||||
"done": False,
|
||||
"child_jobs": [],
|
||||
"errors": [],
|
||||
},
|
||||
{
|
||||
"created": ANY,
|
||||
"name": "test_jobs_tree_outer",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
@ -111,6 +123,7 @@ async def test_jobs_tree_representation(api_client: TestClient, coresys: CoreSys
|
||||
"errors": [],
|
||||
"child_jobs": [
|
||||
{
|
||||
"created": ANY,
|
||||
"name": "test_jobs_tree_inner",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
@ -122,16 +135,6 @@ async def test_jobs_tree_representation(api_client: TestClient, coresys: CoreSys
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "test_jobs_tree_alt",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": "init",
|
||||
"done": False,
|
||||
"child_jobs": [],
|
||||
"errors": [],
|
||||
},
|
||||
]
|
||||
|
||||
test.event.set()
|
||||
@ -141,6 +144,7 @@ async def test_jobs_tree_representation(api_client: TestClient, coresys: CoreSys
|
||||
result = await resp.json()
|
||||
assert result["data"]["jobs"] == [
|
||||
{
|
||||
"created": ANY,
|
||||
"name": "test_jobs_tree_alt",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
@ -182,6 +186,7 @@ async def test_job_manual_cleanup(api_client: TestClient, coresys: CoreSys):
|
||||
assert resp.status == 200
|
||||
result = await resp.json()
|
||||
assert result["data"] == {
|
||||
"created": ANY,
|
||||
"name": "test_job_manual_cleanup",
|
||||
"reference": None,
|
||||
"uuid": test.job_id,
|
||||
@ -229,3 +234,86 @@ async def test_job_not_found(api_client: TestClient, method: str, url: str):
|
||||
assert resp.status == 404
|
||||
body = await resp.json()
|
||||
assert body["message"] == "Job does not exist"
|
||||
|
||||
|
||||
async def test_jobs_sorted(api_client: TestClient, coresys: CoreSys):
|
||||
"""Test jobs are sorted by datetime in results."""
|
||||
|
||||
class TestClass:
|
||||
"""Test class."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize the test class."""
|
||||
self.coresys = coresys
|
||||
|
||||
@Job(name="test_jobs_sorted_1", cleanup=False)
|
||||
async def test_jobs_sorted_1(self):
|
||||
"""Sorted test method 1."""
|
||||
await self.test_jobs_sorted_inner_1()
|
||||
await self.test_jobs_sorted_inner_2()
|
||||
|
||||
@Job(name="test_jobs_sorted_inner_1", cleanup=False)
|
||||
async def test_jobs_sorted_inner_1(self):
|
||||
"""Sorted test inner method 1."""
|
||||
|
||||
@Job(name="test_jobs_sorted_inner_2", cleanup=False)
|
||||
async def test_jobs_sorted_inner_2(self):
|
||||
"""Sorted test inner method 2."""
|
||||
|
||||
@Job(name="test_jobs_sorted_2", cleanup=False)
|
||||
async def test_jobs_sorted_2(self):
|
||||
"""Sorted test method 2."""
|
||||
|
||||
test = TestClass(coresys)
|
||||
await test.test_jobs_sorted_1()
|
||||
await test.test_jobs_sorted_2()
|
||||
|
||||
resp = await api_client.get("/jobs/info")
|
||||
result = await resp.json()
|
||||
assert result["data"]["jobs"] == [
|
||||
{
|
||||
"created": ANY,
|
||||
"name": "test_jobs_sorted_2",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": None,
|
||||
"done": True,
|
||||
"errors": [],
|
||||
"child_jobs": [],
|
||||
},
|
||||
{
|
||||
"created": ANY,
|
||||
"name": "test_jobs_sorted_1",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": None,
|
||||
"done": True,
|
||||
"errors": [],
|
||||
"child_jobs": [
|
||||
{
|
||||
"created": ANY,
|
||||
"name": "test_jobs_sorted_inner_1",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": None,
|
||||
"done": True,
|
||||
"errors": [],
|
||||
"child_jobs": [],
|
||||
},
|
||||
{
|
||||
"created": ANY,
|
||||
"name": "test_jobs_sorted_inner_2",
|
||||
"reference": None,
|
||||
"uuid": ANY,
|
||||
"progress": 0,
|
||||
"stage": None,
|
||||
"done": True,
|
||||
"errors": [],
|
||||
"child_jobs": [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
@ -1066,6 +1066,7 @@ def _make_backup_message_for_assert(
|
||||
"done": done,
|
||||
"parent_id": None,
|
||||
"errors": [],
|
||||
"created": ANY,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -997,6 +997,7 @@ async def test_internal_jobs_no_notify(coresys: CoreSys):
|
||||
"done": True,
|
||||
"parent_id": None,
|
||||
"errors": [],
|
||||
"created": ANY,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ async def test_notify_on_change(coresys: CoreSys):
|
||||
"done": None,
|
||||
"parent_id": None,
|
||||
"errors": [],
|
||||
"created": ANY,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -125,6 +126,7 @@ async def test_notify_on_change(coresys: CoreSys):
|
||||
"done": None,
|
||||
"parent_id": None,
|
||||
"errors": [],
|
||||
"created": ANY,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -146,6 +148,7 @@ async def test_notify_on_change(coresys: CoreSys):
|
||||
"done": None,
|
||||
"parent_id": None,
|
||||
"errors": [],
|
||||
"created": ANY,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -167,6 +170,7 @@ async def test_notify_on_change(coresys: CoreSys):
|
||||
"done": False,
|
||||
"parent_id": None,
|
||||
"errors": [],
|
||||
"created": ANY,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -193,6 +197,7 @@ async def test_notify_on_change(coresys: CoreSys):
|
||||
"message": "Unknown error, see supervisor logs",
|
||||
}
|
||||
],
|
||||
"created": ANY,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -218,6 +223,7 @@ async def test_notify_on_change(coresys: CoreSys):
|
||||
"message": "Unknown error, see supervisor logs",
|
||||
}
|
||||
],
|
||||
"created": ANY,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user