diff --git a/dvc/render/html.py b/dvc/render/html.py
index ec3cc5f5b..e5b650bef 100644
--- a/dvc/render/html.py
+++ b/dvc/render/html.py
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
PAGE_HTML = """
+ {refresh_tag}
DVC Plot
@@ -30,14 +31,23 @@ class MissingPlaceholderError(DvcException):
class HTML:
PLACEHOLDER = "plot_divs"
PLACEHOLDER_FORMAT_STR = f"{{{PLACEHOLDER}}}"
+ REFRESH_PLACEHOLDER = "refresh_tag"
+ REFRESH_TAG = ''
- def __init__(self, template: str = None):
+ def __init__(
+ self,
+ template: Optional[str] = None,
+ refresh_seconds: Optional[int] = None,
+ ):
template = template or PAGE_HTML
if self.PLACEHOLDER_FORMAT_STR not in template:
raise MissingPlaceholderError(self.PLACEHOLDER_FORMAT_STR)
self.template = template
self.elements: List[str] = []
+ self.refresh_tag = ""
+ if refresh_seconds is not None:
+ self.refresh_tag = self.REFRESH_TAG.format(refresh_seconds)
def with_metrics(self, metrics: Dict[str, Dict]) -> "HTML":
import tabulate
@@ -60,7 +70,10 @@ class HTML:
return self
def embed(self) -> str:
- kwargs = {self.PLACEHOLDER: "\n".join(self.elements)}
+ kwargs = {
+ self.PLACEHOLDER: "\n".join(self.elements),
+ self.REFRESH_PLACEHOLDER: self.refresh_tag,
+ }
return self.template.format(**kwargs)
@@ -69,6 +82,7 @@ def write(
renderers: List["Renderer"],
metrics: Optional[Dict[str, Dict]] = None,
template_path: Optional["StrPath"] = None,
+ refresh_seconds: Optional[int] = None,
):
os.makedirs(path, exist_ok=True)
@@ -78,7 +92,7 @@ def write(
with open(template_path) as fobj:
page_html = fobj.read()
- document = HTML(page_html)
+ document = HTML(page_html, refresh_seconds=refresh_seconds)
if metrics:
document.with_metrics(metrics)
document.with_element("
")
diff --git a/dvc/render/utils.py b/dvc/render/utils.py
index 2b0d56752..c9f227c79 100644
--- a/dvc/render/utils.py
+++ b/dvc/render/utils.py
@@ -63,7 +63,14 @@ def match_renderers(plots_data, templates):
return renderers
-def render(repo, plots_data, metrics=None, path=None, html_template_path=None):
+def render(
+ repo,
+ plots_data,
+ metrics=None,
+ path=None,
+ html_template_path=None,
+ refresh_seconds=None,
+):
# TODO we could probably remove repo usages (here and in VegaRenderer)
renderers = match_renderers(plots_data, repo.plots.templates)
if not html_template_path:
@@ -76,5 +83,9 @@ def render(repo, plots_data, metrics=None, path=None, html_template_path=None):
from dvc.render.html import write
return write(
- path, renderers, metrics=metrics, template_path=html_template_path
+ path,
+ renderers,
+ metrics=metrics,
+ template_path=html_template_path,
+ refresh_seconds=refresh_seconds,
)
diff --git a/dvc/repo/live.py b/dvc/repo/live.py
index df52279fe..f53007e89 100644
--- a/dvc/repo/live.py
+++ b/dvc/repo/live.py
@@ -19,7 +19,9 @@ def create_summary(out):
html_path = out.path_info.fspath + "_dvc_plots"
- index_path = render(out.repo, plots, metrics=metrics, path=html_path)
+ index_path = render(
+ out.repo, plots, metrics=metrics, path=html_path, refresh_seconds=5
+ )
logger.info(f"\nfile://{os.path.abspath(index_path)}")
diff --git a/tests/func/test_live.py b/tests/func/test_live.py
index d259c11d4..9995309f3 100644
--- a/tests/func/test_live.py
+++ b/tests/func/test_live.py
@@ -160,6 +160,9 @@ def test_live_html(tmp_dir, dvc, live_stage, html):
live_stage(html=html, live="logs")
assert (tmp_dir / "logs_dvc_plots" / "index.html").is_file() == html
+ if html:
+ html_text = (tmp_dir / "logs_dvc_plots" / "index.html").read_text()
+ assert 'http-equiv="refresh"' in html_text
@pytest.fixture
diff --git a/tests/unit/render/test_html.py b/tests/unit/render/test_html.py
index 56ebe6323..5b4648a1a 100644
--- a/tests/unit/render/test_html.py
+++ b/tests/unit/render/test_html.py
@@ -19,7 +19,11 @@ CUSTOM_PAGE_HTML = """
@pytest.mark.parametrize(
"template,page_elements,expected_page",
[
- (None, ["content"], PAGE_HTML.format(plot_divs="content")),
+ (
+ None,
+ ["content"],
+ PAGE_HTML.format(plot_divs="content", refresh_tag=""),
+ ),
(
CUSTOM_PAGE_HTML,
["content"],