Skip to content

Commit d4cb50f

Browse files
pritamsoni-hsrBorda
authored andcommitted
feat: option to add custom meta tags to the UI container (#14915)
(cherry picked from commit 2721a2f)
1 parent c1c233c commit d4cb50f

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed

requirements/app/base.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ croniter>=1.3.0, <1.4.0 # strict; TODO: for now until we find something more ro
88
traitlets>=5.3.0, <=5.4.0
99
arrow>=1.2.0, <=1.2.2
1010
lightning-utilities==0.3.*
11+
beautifulsoup4<=4.8.2

src/lightning_app/core/app.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
DEBUG_ENABLED,
1919
FLOW_DURATION_SAMPLES,
2020
FLOW_DURATION_THRESHOLD,
21+
FRONTEND_DIR,
2122
STATE_ACCUMULATE_WAIT,
2223
)
2324
from lightning_app.core.queues import BaseQueue, SingleProcessQueue
2425
from lightning_app.frontend import Frontend
2526
from lightning_app.storage import Drive, Path
2627
from lightning_app.storage.path import storage_root_dir
28+
from lightning_app.utilities import frontend
2729
from lightning_app.utilities.app_helpers import _delta_to_app_state_delta, _LightningAppRef, Logger
2830
from lightning_app.utilities.commands.base import _process_requests
2931
from lightning_app.utilities.component import _convert_paths_after_init, _validate_root_flow
@@ -46,6 +48,7 @@ def __init__(
4648
self,
4749
root: "lightning_app.LightningFlow",
4850
debug: bool = False,
51+
info: frontend.AppInfo = None,
4952
):
5053
"""The Lightning App, or App in short runs a tree of one or more components that interact to create end-to-end
5154
applications. There are two kinds of components: :class:`~lightning_app.core.flow.LightningFlow` and
@@ -62,6 +65,8 @@ def __init__(
6265
It must define a `run()` method that the app can call.
6366
debug: Whether to activate the Lightning Logger debug mode.
6467
This can be helpful when reporting bugs on Lightning repo.
68+
info: Provide additional info about the app which will be used to update html title,
69+
description and image meta tags and specify any additional tags as list of html strings.
6570
6671
.. doctest::
6772
@@ -133,6 +138,10 @@ def __init__(
133138

134139
logger.debug(f"ENV: {os.environ}")
135140

141+
# update index.html,
142+
# this should happen once for all apps before the ui server starts running.
143+
frontend.update_index_file_with_info(FRONTEND_DIR, info=info)
144+
136145
def get_component_by_name(self, component_name: str):
137146
"""Returns the instance corresponding to the given component name."""
138147
from lightning_app.structures import Dict, List
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from dataclasses import dataclass
2+
from typing import List, Optional
3+
4+
from bs4 import BeautifulSoup
5+
6+
7+
@dataclass
8+
class AppInfo:
9+
title: Optional[str] = None
10+
favicon: Optional[str] = None
11+
description: Optional[str] = None
12+
image: Optional[str] = None
13+
# ensure the meta tags are correct or the UI might fail to load.
14+
meta_tags: Optional[List[str]] = None
15+
16+
17+
def update_index_file_with_info(ui_root: str, info: AppInfo = None) -> None:
18+
import shutil
19+
from pathlib import Path
20+
21+
entry_file = Path(ui_root) / "index.html"
22+
original_file = Path(ui_root) / "index.original.html"
23+
24+
if not original_file.exists():
25+
shutil.copyfile(entry_file, original_file) # keep backup
26+
else:
27+
# revert index.html in case it was modified after creating original.html
28+
shutil.copyfile(original_file, entry_file)
29+
30+
if not info:
31+
return
32+
33+
original = ""
34+
35+
with original_file.open() as f:
36+
original = f.read()
37+
38+
with entry_file.open("w") as f:
39+
f.write(_get_updated_content(original=original, info=info))
40+
41+
42+
def _get_updated_content(original: str, info: AppInfo) -> str:
43+
soup = BeautifulSoup(original, "html.parser")
44+
45+
# replace favicon
46+
if info.favicon:
47+
soup.find("link", {"rel": "icon"}).attrs["href"] = info.favicon
48+
49+
if info.title is not None:
50+
soup.find("title").string = info.title
51+
52+
if info.description:
53+
soup.find("meta", {"name": "description"}).attrs["content"] = info.description
54+
55+
if info.image:
56+
soup.find("meta", {"property": "og:image"}).attrs["content"] = info.image
57+
58+
if info.meta_tags:
59+
soup.find("head").append(*[BeautifulSoup(meta, "html.parser") for meta in info.meta_tags])
60+
61+
return str(soup)

0 commit comments

Comments
 (0)