[ADD] migration state: how long the migration took
From the first write to the LAST one. The progression file is rewritten after every move, so its update date is the end — or the present moment while the migration is still running. Both dates were already shown; what was missing was the one number one actually wants. The statistics screen already computed this. It is reused rather than rewritten: two formulas would give two durations for the same migration depending on which screen one opens. --- FR --- [ADD] état de migration : combien de temps elle a duré Du premier écrit au DERNIER. Le fichier de progression est réécrit après chaque geste, donc sa date de mise à jour est la fin — ou l'instant présent tant que la migration tourne. Les deux dates étaient déjà affichées ; il manquait le seul chiffre qu'on cherche. L'écran de statistiques portait déjà ce calcul. Il est réutilisé plutôt que réécrit : deux formules donneraient deux durées pour la même migration selon l'écran qu'on ouvre. Assisted-by: Claude Opus 5
This commit is contained in:
parent
c0c5144680
commit
e981500c94
4 changed files with 87 additions and 2 deletions
|
|
@ -288,9 +288,28 @@ def failures(dct):
|
||||||
return list(reversed(events(dct, kind="command")))
|
return list(reversed(events(dct, kind="command")))
|
||||||
|
|
||||||
|
|
||||||
|
def elapsed(dct):
|
||||||
|
"""Combien de temps la migration a duré, ou dure encore.
|
||||||
|
|
||||||
|
Du premier écrit à la DERNIÈRE écriture du journal : la progression est
|
||||||
|
réécrite après chaque geste, donc sa date de mise à jour EST la fin —
|
||||||
|
ou l'instant présent si la migration tourne toujours.
|
||||||
|
|
||||||
|
On délègue à `migration_stats.fmt_delay`, qui porte déjà ce calcul pour
|
||||||
|
l'écran de statistiques. Deux formules donneraient deux durées pour la
|
||||||
|
même migration selon l'écran qu'on ouvre.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from script.todo.migration_stats import fmt_delay
|
||||||
|
except Exception:
|
||||||
|
return "?"
|
||||||
|
return fmt_delay(dct.get("date_create"), dct.get("date_update"))
|
||||||
|
|
||||||
|
|
||||||
def overview(dct):
|
def overview(dct):
|
||||||
"""L'en-tête : de quelle migration parle-t-on."""
|
"""L'en-tête : de quelle migration parle-t-on."""
|
||||||
return {
|
return {
|
||||||
|
"elapsed": elapsed(dct),
|
||||||
"file": os.path.basename(dct.get("migration_file") or "?"),
|
"file": os.path.basename(dct.get("migration_file") or "?"),
|
||||||
"database": dct.get("config_database_name") or "?",
|
"database": dct.get("config_database_name") or "?",
|
||||||
"target": dct.get("target_odoo_version") or "?",
|
"target": dct.get("target_odoo_version") or "?",
|
||||||
|
|
@ -340,7 +359,8 @@ def render_text(dct, limit_cmd=12, colour=None):
|
||||||
f" {t('database')} : {info['database']}",
|
f" {t('database')} : {info['database']}",
|
||||||
f" {t('image')} : {info['file']}",
|
f" {t('image')} : {info['file']}",
|
||||||
f" {t('started')} : {info['started']}",
|
f" {t('started')} : {info['started']}",
|
||||||
f" {t('last written')} : {info['updated']}",
|
f" {t('finished')} : {info['updated']}",
|
||||||
|
f" {t('duration')} : {info['elapsed']}",
|
||||||
f" {t('current step')} : {info['step']}",
|
f" {t('current step')} : {info['step']}",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ def head_text(dct):
|
||||||
return (
|
return (
|
||||||
f"{info['database']} · {info['file']}\n"
|
f"{info['database']} · {info['file']}\n"
|
||||||
f"{t('current step')} : {info['step']}\n"
|
f"{t('current step')} : {info['step']}\n"
|
||||||
f"{t('last written')} : {info['updated']}"
|
f"{t('finished')} : {info['updated']}"
|
||||||
|
f" · {t('duration')} {info['elapsed']}"
|
||||||
f" · {len(status.failures(dct))} {t('failed')}"
|
f" · {len(status.failures(dct))} {t('failed')}"
|
||||||
f" · {len(casse)}/{len(lst_test)} {t('Test results')}"
|
f" · {len(casse)}/{len(lst_test)} {t('Test results')}"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5667,6 +5667,14 @@ TRANSLATIONS = {
|
||||||
"fr": "Rétrécir",
|
"fr": "Rétrécir",
|
||||||
"en": "Narrower",
|
"en": "Narrower",
|
||||||
},
|
},
|
||||||
|
"finished": {
|
||||||
|
"fr": "terminée",
|
||||||
|
"en": "finished",
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"fr": "durée",
|
||||||
|
"en": "duration",
|
||||||
|
},
|
||||||
"Clean the database before testing the pages?": {
|
"Clean the database before testing the pages?": {
|
||||||
"fr": "Nettoyer la base avant de tester les pages ?",
|
"fr": "Nettoyer la base avant de tester les pages ?",
|
||||||
"en": "Clean the database before testing the pages?",
|
"en": "Clean the database before testing the pages?",
|
||||||
|
|
|
||||||
|
|
@ -958,6 +958,62 @@ class TestTheFullScreenColoursToo(Base):
|
||||||
self.assertIn("colour=True", source)
|
self.assertIn("colour=True", source)
|
||||||
|
|
||||||
|
|
||||||
|
class TestHowLongItTook(Base):
|
||||||
|
"""Du premier écrit à la dernière écriture du journal.
|
||||||
|
|
||||||
|
La progression est réécrite après chaque geste, donc sa date de mise à
|
||||||
|
jour EST la fin — ou l'instant présent si la migration tourne encore.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def dct(self, debut, fin):
|
||||||
|
return progression(date_create=debut, date_update=fin)
|
||||||
|
|
||||||
|
def test_it_counts_from_the_start_to_the_last_write(self):
|
||||||
|
info = status.overview(
|
||||||
|
self.dct("2026-08-19 09:06:27", "2026-08-19 10:19:18")
|
||||||
|
)
|
||||||
|
self.assertEqual(info["elapsed"], "1 h 12 min")
|
||||||
|
|
||||||
|
def test_a_short_run_is_given_in_minutes(self):
|
||||||
|
info = status.overview(
|
||||||
|
self.dct("2026-08-19 09:06:00", "2026-08-19 09:41:00")
|
||||||
|
)
|
||||||
|
self.assertEqual(info["elapsed"], "35 min")
|
||||||
|
|
||||||
|
def test_a_long_run_is_given_in_days(self):
|
||||||
|
info = status.overview(
|
||||||
|
self.dct("2026-08-17 09:00:00", "2026-08-19 11:30:00")
|
||||||
|
)
|
||||||
|
self.assertIn("j", info["elapsed"])
|
||||||
|
|
||||||
|
def test_a_missing_date_never_invents_a_duration(self):
|
||||||
|
self.assertEqual(status.overview(self.dct(None, None))["elapsed"], "?")
|
||||||
|
|
||||||
|
def test_the_report_shows_it(self):
|
||||||
|
texte = status.render_text(
|
||||||
|
self.dct("2026-08-19 09:06:27", "2026-08-19 10:19:18"),
|
||||||
|
colour=False,
|
||||||
|
)
|
||||||
|
self.assertIn("1 h 12 min", texte)
|
||||||
|
|
||||||
|
def test_the_full_screen_shows_it_too(self):
|
||||||
|
texte = tui.head_text(
|
||||||
|
self.dct("2026-08-19 09:06:27", "2026-08-19 10:19:18")
|
||||||
|
)
|
||||||
|
self.assertIn("1 h 12 min", texte)
|
||||||
|
|
||||||
|
def test_it_is_computed_in_ONE_place(self):
|
||||||
|
# L'écran de statistiques porte déjà ce calcul. Deux formules
|
||||||
|
# donneraient deux durées pour la même migration selon l'écran
|
||||||
|
# qu'on ouvre.
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
source = inspect.getsource(status.elapsed)
|
||||||
|
self.assertIn(
|
||||||
|
"from script.todo.migration_stats import fmt_delay", source
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestSeparatingCommandsFromLogs(Base):
|
class TestSeparatingCommandsFromLogs(Base):
|
||||||
"""Les deux mélangés dans un même panneau se confondent.
|
"""Les deux mélangés dans un même panneau se confondent.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue