Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions dvc/command/live.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from dvc.command import completion
from dvc.command.base import CmdBase, fix_subparsers
from dvc.utils.html import write

logger = logging.getLogger(__name__)

Expand All @@ -16,7 +15,7 @@ def _run(self, target, revs=None):
metrics, plots = self.repo.live.show(target=target, revs=revs)

html_path = self.args.target + ".html"
write(html_path, plots, metrics)
self.repo.plots.write_html(html_path, plots, metrics)

logger.info(f"\nfile://{os.path.abspath(html_path)}")

Expand Down
12 changes: 10 additions & 2 deletions dvc/command/plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from dvc.command.base import CmdBase, append_doc_link, fix_subparsers
from dvc.exceptions import DvcException
from dvc.utils import format_link
from dvc.utils.html import write

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -44,7 +43,10 @@ def run(self):

rel: str = self.args.out or "plots.html"
path = (Path.cwd() / rel).resolve()
write(path, plots)
self.repo.plots.write_html(
path, plots=plots, html_template_path=self.args.html_template
)

except DvcException:
logger.exception("")
return 1
Expand Down Expand Up @@ -243,3 +245,9 @@ def _add_output_arguments(parser):
default=False,
help="Open plot file directly in the browser.",
)
parser.add_argument(
"--html-template",
Comment on lines +248 to +249
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, should it be just --html?

default=None,
help="Custom HTML template for VEGA visualization.",
metavar="<path>",
)
1 change: 1 addition & 0 deletions dvc/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,5 @@ class RelPath(str):
# enabled by default. It's of no use, kept for backward compatibility.
Optional("parametrization", default=True): Bool
},
"plots": {"html_template": str},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be just plots.html? To avoid overloading the term "template" (also associated with Vega customizations).

}
5 changes: 2 additions & 3 deletions dvc/repo/live.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@


def create_summary(out):
from dvc.utils.html import write

assert out.live and out.live["html"]

metrics, plots = out.repo.live.show(str(out.path_info))

html_path = out.path_info.with_suffix(".html")
write(html_path, plots, metrics)

out.repo.plots.write_html(html_path, plots, metrics)
logger.info(f"\nfile://{os.path.abspath(html_path)}")


Expand Down
24 changes: 23 additions & 1 deletion dvc/repo/plots/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from typing import TYPE_CHECKING, Dict, List
import os
from typing import TYPE_CHECKING, Dict, List, Optional

from funcy import cached_property, first, project

Expand All @@ -9,6 +10,7 @@
NoMetricsFoundError,
NoMetricsParsedError,
)
from dvc.types import StrPath
from dvc.utils import relpath

if TYPE_CHECKING:
Expand Down Expand Up @@ -219,6 +221,26 @@ def templates(self):

return PlotTemplates(self.repo.dvc_dir)

def write_html(
self,
path: StrPath,
plots: Dict[str, Dict],
metrics: Optional[Dict[str, Dict]] = None,
html_template_path: Optional[StrPath] = None,
):
if not html_template_path:
html_template_path = self.repo.config.get("plots", {}).get(
"html_template", None
)
if html_template_path and not os.path.isabs(html_template_path):
html_template_path = os.path.join(
self.repo.dvc_dir, html_template_path
)

from dvc.utils.html import write

write(path, plots, metrics, html_template_path)


def _is_plot(out: "BaseOutput") -> bool:
return bool(out.plot) or bool(out.live)
Expand Down
37 changes: 31 additions & 6 deletions dvc/utils/html.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from typing import Dict, List, Optional

from dvc.exceptions import DvcException
from dvc.types import StrPath

PAGE_HTML = """<!DOCTYPE html>
<html>
<head>
Expand All @@ -9,7 +12,7 @@
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6.5.1"></script>
</head>
<body>
{divs}
{plot_divs}
</body>
</html>"""

Expand All @@ -20,9 +23,22 @@
</script>"""


class MissingPlaceholderError(DvcException):
def __init__(self, placeholder):
super().__init__(f"HTML template has to contain '{placeholder}'.")


class HTML:
def __init__(self):
self.elements = []
PLACEHOLDER = "plot_divs"
Comment thread
jorgeorpinel marked this conversation as resolved.
PLACEHOLDER_FORMAT_STR = f"{{{PLACEHOLDER}}}"

def __init__(self, template: str = 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] = []

def with_metrics(self, metrics: Dict[str, Dict]) -> "HTML":
import tabulate
Expand Down Expand Up @@ -54,13 +70,22 @@ def with_element(self, html: str) -> "HTML":
return self

def embed(self) -> str:
return PAGE_HTML.format(divs="\n".join(self.elements))
kwargs = {self.PLACEHOLDER: "\n".join(self.elements)}
return self.template.format(**kwargs)


def write(
path, plots: Dict[str, Dict], metrics: Optional[Dict[str, Dict]] = None
path: StrPath,
plots: Dict[str, Dict],
metrics: Optional[Dict[str, Dict]] = None,
template_path: Optional[StrPath] = None,
):
document = HTML()
page_html = None
if template_path:
with open(template_path, "r") as fobj:
page_html = fobj.read()

document = HTML(page_html)
if metrics:
document.with_metrics(metrics)
document.with_element("<br>")
Expand Down
43 changes: 43 additions & 0 deletions tests/func/utils/test_html.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest

from dvc.utils.html import HTML, PAGE_HTML, MissingPlaceholderError

CUSTOM_PAGE_HTML = """<!DOCTYPE html>
<html>
<head>
<title>TITLE</title>
<script type="text/javascript" src="vega"></script>
<script type="text/javascript" src="vega-lite"></script>
<script type="text/javascript" src="vega-embed"></script>
</head>
<body>
{plot_divs}
</body>
</html>"""


@pytest.mark.parametrize(
"template,page_elements,expected_page",
[
(None, ["content"], PAGE_HTML.format(plot_divs="content")),
(
CUSTOM_PAGE_HTML,
["content"],
CUSTOM_PAGE_HTML.format(plot_divs="content"),
),
],
)
def test_html(tmp_dir, template, page_elements, expected_page):
page = HTML(template)
page.elements = page_elements

result = page.embed()

assert result == expected_page


def test_no_placeholder(tmp_dir):
template = "<head></head><body></body>"

with pytest.raises(MissingPlaceholderError):
HTML(template)