Skip to content

Commit

Permalink
Implement GraphRenderConfig
Browse files Browse the repository at this point in the history
* Pydantic model for serialization
* Built from GraphRenderOptions plus default values for missing fields

CMK-14895

Change-Id: I9114557c8e99d512ff1c641a541aa0bf841dce7e
  • Loading branch information
jherbel committed Oct 25, 2023
1 parent 25cc4ab commit e953050
Show file tree
Hide file tree
Showing 13 changed files with 454 additions and 450 deletions.
3 changes: 2 additions & 1 deletion cmk/gui/dashboard/dashlet/dashlets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ..registry import DashletRegistry
from .custom_url import URLDashlet
from .failed_notifications import FailedNotificationsDashlet
from .graph import TemplateGraphDashlet
from .graph import default_dashlet_graph_render_options, TemplateGraphDashlet
from .logo import MKLogoDashlet
from .static_text import StaticTextDashlet, StaticTextDashletConfig
from .stats import EventStatsDashlet, HostStatsDashlet, ServiceStatsDashlet, StatsDashletConfig
Expand All @@ -27,6 +27,7 @@
"StatsDashletConfig",
"LinkedViewDashletConfig",
"copy_view_into_dashlet",
"default_dashlet_graph_render_options",
]


Expand Down
25 changes: 21 additions & 4 deletions cmk/gui/dashboard/dashlet/dashlets/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from cmk.gui.exceptions import MKMissingDataError, MKUserError
from cmk.gui.graphing._graph_recipe_builder import build_graph_recipes
from cmk.gui.graphing._graph_specification import GraphSpecification, TemplateGraphSpecification
from cmk.gui.graphing._html_render import default_dashlet_graph_render_options, GraphDestinations
from cmk.gui.graphing._html_render import GraphDestinations
from cmk.gui.graphing._utils import (
graph_templates_internal,
metric_info,
Expand All @@ -29,7 +29,7 @@
from cmk.gui.graphing._valuespecs import vs_graph_render_options
from cmk.gui.htmllib.html import html
from cmk.gui.i18n import _
from cmk.gui.type_defs import Choices, SingleInfos, VisualContext
from cmk.gui.type_defs import Choices, GraphRenderOptions, SingleInfos, SizePT, VisualContext
from cmk.gui.utils.autocompleter_config import ContextAutocompleterConfig
from cmk.gui.valuespec import (
Dictionary,
Expand Down Expand Up @@ -164,7 +164,7 @@ def _vs_graph_render_options() -> DictionaryEntry:
return (
"graph_render_options",
vs_graph_render_options(
default_values=default_dashlet_graph_render_options,
default_values=default_dashlet_graph_render_options(),
exclude=[
"show_time_range_previews",
"title_format",
Expand Down Expand Up @@ -289,7 +289,11 @@ def _reload_js(self) -> str:
self._dashlet_id,
self._graph_specification.json(),
json.dumps(
self._dashlet_spec.get("graph_render_options", default_dashlet_graph_render_options)
default_dashlet_graph_render_options()
# Something is wrong with the typing here. self._dashlet_spec is a subclass of
# ABCGraphDashlet, so self._dashlet_spec.get("graph_render_options", {}) should be
# a dict ...
| self._dashlet_spec.get("graph_render_options", {}) # type: ignore[operator]
),
json.dumps(self._dashlet_spec["timerange"]),
)
Expand Down Expand Up @@ -401,3 +405,16 @@ def _get_additional_macros(self) -> Mapping[str, str]:
@classmethod
def get_additional_title_macros(cls) -> Iterable[str]:
yield "$SITE$"


def default_dashlet_graph_render_options() -> GraphRenderOptions:
return GraphRenderOptions(
font_size=SizePT(8),
show_graph_time=False,
show_margin=False,
show_legend=False,
show_title=False,
show_controls=False,
resizable=False,
show_time_range_previews=False,
)
4 changes: 2 additions & 2 deletions cmk/gui/dashboard/visual_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from cmk.utils.exceptions import MKGeneralException

from cmk.gui.graphing._html_render import default_dashlet_graph_render_options
from cmk.gui.http import response
from cmk.gui.i18n import _
from cmk.gui.logged_in import user
Expand All @@ -19,6 +18,7 @@
from cmk.gui.visuals.type import VisualType

from .dashlet import copy_view_into_dashlet, dashlet_registry, DashletConfig, ViewDashletConfig
from .dashlet.dashlets import default_dashlet_graph_render_options
from .store import add_dashlet, get_permitted_dashboards, load_dashboard_with_cloning
from .type_defs import ABCGraphDashletConfig

Expand Down Expand Up @@ -112,7 +112,7 @@ def add_visual_handler( # pylint: disable=too-many-branches
elif specification[0] == "combined":
add_type = "combined_graph"
parameters = copy.deepcopy(specification[1])
parameters["graph_render_options"] = default_dashlet_graph_render_options
parameters["graph_render_options"] = default_dashlet_graph_render_options()
context = parameters.pop("context", {})
# FIXME: mypy doesn't know if the parameter is well-formed, but we promise it is!
assert isinstance(context, dict)
Expand Down
90 changes: 5 additions & 85 deletions cmk/gui/graphing/_artwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
from cmk.gui.i18n import _
from cmk.gui.logged_in import user
from cmk.gui.time_series import TimeSeries, TimeSeriesValue, Timestamp
from cmk.gui.type_defs import GraphRenderOptions, SizePT, UnitInfo, UnitRenderFunc
from cmk.gui.utils.theme import theme
from cmk.gui.type_defs import UnitInfo, UnitRenderFunc

from ._graph_specification import (
CombinedSingleMetricSpec,
Expand Down Expand Up @@ -114,83 +113,6 @@ class GraphArtwork(BaseModel):
display_id: str


# .--Default Render Options----------------------------------------------.
# | ____ _ |
# | | _ \ ___ _ __ __| | ___ _ __ |
# | | |_) / _ \ '_ \ / _` |/ _ \ '__| |
# | | _ < __/ | | | (_| | __/ | |
# | |_| \_\___|_| |_|\__,_|\___|_| |
# | |
# | ___ _ _ |
# | / _ \ _ __ | |_(_) ___ _ __ ___ |
# | | | | | '_ \| __| |/ _ \| '_ \/ __| |
# | | |_| | |_) | |_| | (_) | | | \__ \ |
# | \___/| .__/ \__|_|\___/|_| |_|___/ |
# | |_| |
# '----------------------------------------------------------------------'


def get_default_graph_render_options() -> GraphRenderOptions:
return GraphRenderOptions(
font_size=SizePT(8.0),
resizable=True,
show_controls=True,
show_pin=True,
show_legend=True,
show_graph_time=True,
show_vertical_axis=True,
vertical_axis_width="fixed",
show_time_axis=True,
show_title=True,
title_format=("plain",),
show_margin=True,
preview=False,
interaction=True,
editing=False,
fixed_timerange=False,
show_time_range_previews=True,
background_color="default",
foreground_color="default",
canvas_color="default",
)


def _graph_colors(theme_id: str) -> GraphRenderOptions:
return {
"modern-dark": GraphRenderOptions(
background_color=None,
foreground_color="#ffffff",
canvas_color=None,
),
"pdf": GraphRenderOptions(
background_color="#f8f4f0",
foreground_color="#000000",
canvas_color="#ffffff",
),
}.get(
theme_id,
GraphRenderOptions(
background_color=None,
foreground_color="#000000",
canvas_color=None,
),
)


def add_default_render_options(
graph_render_options: GraphRenderOptions, render_unthemed: bool = False
) -> GraphRenderOptions:
options = get_default_graph_render_options()
options.update(graph_render_options)
options.setdefault("size", user.load_file("graph_size", (70, 16)))

# Users can't modify graph colors. Only defaults are allowed
theme_colors = _graph_colors(theme.get() if not render_unthemed else "pdf")
options.update(theme_colors)

return options


# .
# .--Create graph artwork------------------------------------------------.
# | _ _ _ |
Expand All @@ -210,15 +132,13 @@ def add_default_render_options(
def compute_graph_artwork(
graph_recipe: GraphRecipe,
graph_data_range: GraphDataRange,
graph_render_options: GraphRenderOptions,
size: tuple[int, int],
resolve_combined_single_metric_spec: Callable[
[CombinedSingleMetricSpec], Sequence[GraphMetric]
],
*,
graph_display_id: str = "",
) -> GraphArtwork:
graph_render_options = add_default_render_options(graph_render_options)

curves = list(
compute_graph_artwork_curves(
graph_recipe,
Expand All @@ -230,7 +150,7 @@ def compute_graph_artwork(
pin_time = _load_graph_pin()
_compute_scalars(graph_recipe, curves, pin_time)
layouted_curves, mirrored = _layout_graph_curves(curves) # do stacking, mirroring
width, height = graph_render_options["size"]
width, height = size

try:
start_time, end_time, step = curves[0]["rrddata"].twindow
Expand All @@ -240,8 +160,8 @@ def compute_graph_artwork(
return GraphArtwork(
# Labelling, size, layout
title=graph_recipe.title,
width=int(width), # in widths of lower case 'x'
height=height,
width=(width := size[0]), # in widths of lower case 'x'
height=(height := size[1]),
mirrored=mirrored,
# Actual data and axes
curves=layouted_curves,
Expand Down
42 changes: 19 additions & 23 deletions cmk/gui/graphing/_graph_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,19 @@
from cmk.gui.http import request, response
from cmk.gui.i18n import _
from cmk.gui.log import logger
from cmk.gui.logged_in import user
from cmk.gui.session import SuperUserContext
from cmk.gui.type_defs import GraphRenderOptions, SizePT

from ._artwork import (
add_default_render_options,
compute_graph_artwork,
compute_graph_artwork_curves,
GraphArtwork,
)
from ._artwork import compute_graph_artwork, compute_graph_artwork_curves, GraphArtwork
from ._graph_pdf import (
compute_pdf_graph_data_range,
get_mm_per_ex,
graph_legend_height,
render_graph_pdf,
)
from ._graph_recipe_builder import build_graph_recipes
from ._graph_render_config import GraphRenderConfigImage
from ._graph_specification import (
CombinedSingleMetricSpec,
GraphMetric,
Expand Down Expand Up @@ -100,9 +97,12 @@ def _answer_graph_image_request(
end_time = int(time.time())
start_time = end_time - (25 * 3600)

graph_render_options = graph_image_render_options()
graph_render_config = GraphRenderConfigImage.from_render_options_and_context(
graph_image_render_options(),
user,
)

graph_data_range = graph_image_data_range(graph_render_options, start_time, end_time)
graph_data_range = graph_image_data_range(graph_render_config, start_time, end_time)
graph_recipes = build_graph_recipes(
TemplateGraphSpecification(
site=livestatus.SiteId(site) if site else None,
Expand All @@ -119,10 +119,10 @@ def _answer_graph_image_request(
graph_artwork = compute_graph_artwork(
graph_recipe,
graph_data_range,
graph_render_options,
graph_render_config.size,
resolve_combined_single_metric_spec,
)
graph_png = render_graph_image(graph_artwork, graph_data_range, graph_render_options)
graph_png = render_graph_image(graph_artwork, graph_data_range, graph_render_config)

graphs.append(base64.b64encode(graph_png).decode("ascii"))

Expand All @@ -135,10 +135,10 @@ def _answer_graph_image_request(


def graph_image_data_range(
graph_render_options: GraphRenderOptions, start_time: int, end_time: int
graph_render_config: GraphRenderConfigImage, start_time: int, end_time: int
) -> GraphDataRange:
mm_per_ex = get_mm_per_ex(graph_render_options["font_size"])
width_mm = graph_render_options["size"][0] * mm_per_ex
mm_per_ex = get_mm_per_ex(graph_render_config.font_size)
width_mm = graph_render_config.size[0] * mm_per_ex
return compute_pdf_graph_data_range(width_mm, start_time, end_time)


Expand All @@ -155,10 +155,6 @@ def graph_image_render_options(api_request: dict[str, Any] | None = None) -> Gra
show_title=True,
border_width=0.05,
)

# Populate missing keys
graph_render_options = add_default_render_options(graph_render_options, render_unthemed=True)

# Enforce settings optionally setable via request
if api_request and api_request.get("render_options"):
graph_render_options.update(api_request["render_options"])
Expand All @@ -169,18 +165,18 @@ def graph_image_render_options(api_request: dict[str, Any] | None = None) -> Gra
def render_graph_image(
graph_artwork: GraphArtwork,
graph_data_range: GraphDataRange,
graph_render_options: GraphRenderOptions,
graph_render_config: GraphRenderConfigImage,
) -> bytes:
width_ex, height_ex = graph_render_options["size"]
mm_per_ex = get_mm_per_ex(graph_render_options["font_size"])
width_ex, height_ex = graph_render_config.size
mm_per_ex = get_mm_per_ex(graph_render_config.font_size)

legend_height = graph_legend_height(graph_artwork, graph_render_options)
legend_height = graph_legend_height(graph_artwork, graph_render_config)
image_height = (height_ex * mm_per_ex) + legend_height

# TODO: Better use reporting.get_report_instance()
doc = pdf.Document(
font_family="Helvetica",
font_size=graph_render_options["font_size"],
font_size=graph_render_config.font_size,
lineheight=1.2,
pagesize=(width_ex * mm_per_ex, image_height),
margins=(0, 0, 0, 0),
Expand All @@ -199,7 +195,7 @@ def render_graph_image(
instance,
graph_artwork,
graph_data_range,
graph_render_options,
graph_render_config,
pos_left=0.0,
pos_top=0.0,
total_width=(width_ex * mm_per_ex),
Expand Down
Loading

0 comments on commit e953050

Please sign in to comment.