Skip to main content

Adding Content with Tabs

When you want to organize complex task outputs, visual summaries, or debugging information into distinct navigable views, flyte-sdk provides an interactive multi-tab HTML report system.

Each Flyte report is compiled into a single interactive HTML file (report.html) where tabs are rendered as separate HTML content divisions switched via clickable navigation links.

Organizing Content Across Named Tabs

To split output across different sections, fetch or create a tab using flyte.report.get_tab() and append HTML snippets with Tab.log():

import flyte
from flyte import env

@env.task(report=True)
def evaluate_model() -> None:
# Retrieve or automatically create a tab named 'Summary'
summary_tab = flyte.report.get_tab("Summary")
summary_tab.log("<h3>Model Evaluation Overview</h3>")
summary_tab.log("<p>Status: <strong>Completed</strong></p>")

# Create another tab for metrics
metrics_tab = flyte.report.get_tab("Metrics")
metrics_tab.log("<table><tr><th>Epoch</th><th>Loss</th></tr><tr><td>1</td><td>0.12</td></tr></table>")

Setting report=True on the @env.task decorator instructs the task runner to automatically compile and upload the final report.html to the task's output storage upon completion.

Retrieving vs Creating Tabs

flyte.report.get_tab(name, create_if_missing=True) operates on the active report context:

  • If create_if_missing=True (the default), requesting a tab name that does not exist instantiates a new Tab instance, registers it in Report.tabs, and returns it.
  • If create_if_missing=False, requesting a nonexistent tab raises a ValueError:
import flyte

# This raises ValueError: Tab Metrics does not exist.
tab = flyte.report.get_tab("Metrics", create_if_missing=False)

Appending Content vs Replacing Content

The Tab class provides two primary methods for populating content:

Appending with Tab.log()

Tab.log(content: str) appends an HTML string snippet to the tab's internal content list. When the final report renders, snippets are concatenated with newlines:

import flyte

tab = flyte.report.get_tab("Execution Logs")
tab.log("<div>Step 1: Ingesting dataset...</div>")
tab.log("<div>Step 2: Normalizing features...</div>")
tab.log("<div>Step 3: Training complete.</div>")

Overwriting with Tab.replace()

Tab.replace(content: str) resets the tab's content list to contain only the newly provided HTML string. This is useful when showing progress or replacing preliminary outputs with final summaries:

import flyte

status_tab = flyte.report.get_tab("Status")

# Set initial status
status_tab.replace("<p>Job Status: <em>In Progress</em></p>")

# Overwrite previous status with final output
status_tab.replace("<p>Job Status: <span style='color:green;'>Success</span></p>")

Working with the Default 'main' Tab

Every Report automatically initializes a default tab named "main". Module-level convenience functions allow logging directly to this default tab without calling get_tab():

import flyte
from flyte import env

@env.task(report=True)
def data_pipeline() -> None:
# Logs directly into the default 'main' tab
flyte.report.log("<h2>Main Execution Log</h2>")
flyte.report.log("<p>Processing step 1...</p>")

# Overwrites the contents of the default 'main' tab
flyte.report.replace("<h2>Final Pipeline Results</h2><p>All steps finished.</p>")

Flushing Intermediate Reports

Both flyte.report.log and flyte.report.replace accept an optional do_flush=True parameter. When set, flyte-sdk immediately writes the current report state to remote storage:

import flyte

# Appends to 'main' and immediately flushes report.html to storage
flyte.report.log("<p>Checkpoint reached at epoch 50.</p>", do_flush=True)

# You can also manually flush all tabs at any point:
flyte.report.flush()

Working with Standalone Report Objects

You can instantiate and manage Report and Tab objects directly outside of tasks (for example, in unit tests or local scripts):

from flyte.report import Report

# Create a report instance
report = Report(name="custom-run")

# Access tabs directly from the report instance
overview_tab = report.get_tab("Overview")
overview_tab.log("<h1>Custom Run</h1>")

details_tab = report.get_tab("Details")
details_tab.log("<p>Detailed run metrics go here.</p>")

# Render final HTML string or IPython HTML object
raw_html = report.get_final_report()

Gotchas and Troubleshooting

Content Must Be HTML Snippets (Not Full Documents)

Tab content is injected directly into <div> tags inside the flyte-sdk report template (_template.html). Do not include document-level tags such as <!DOCTYPE html>, <html>, <head>, or <body>. Only supply HTML elements like <div>, <p>, <table>, <img>, or inline styles.

HTML Escaping Behavior

  • Tab Titles: The names given in get_tab(name) are escaped with html.escape(key) when generating navigation items (<li>), preventing broken layout from special characters in titles.
  • Tab Body Content: Tab contents provided to log() and replace() are rendered raw without escaping so that your custom formatting, tables, and scripts are preserved. If including untrusted user inputs, sanitize them before passing them to tab.log() or tab.replace().

Running Outside Task Contexts

Calling flyte.report.current_report() or flyte.report.get_tab() when not in an active task returns a fallback Report("dummy"). Calling flyte.report.flush() outside a task context is a safe no-op.

Enabling Automatic Report Uploads

If a task does not set report=True in its @env.task(...) decorator, the task runner will not automatically upload report.html when the task finishes. If you want reports generated without report=True, you must explicitly invoke flyte.report.flush() before the task exits.