From dfaf96ceffdc24d1ed99b91f4c1bb0a4d03ac014 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Mon, 25 Sep 2023 16:16:05 +0200 Subject: [PATCH 01/19] feat: set the view of the version manager for apps --- sepal_ui/message/en/locale.json | 7 ++- sepal_ui/sepalwidgets/app.py | 85 +++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/sepal_ui/message/en/locale.json b/sepal_ui/message/en/locale.json index abdea912..4687f238 100644 --- a/sepal_ui/message/en/locale.json +++ b/sepal_ui/message/en/locale.json @@ -23,7 +23,12 @@ "navdrawer": { "code": "Source code", "wiki": "Wiki", - "bug": "Bug report" + "bug": "Bug report", + "changelog": { + "version": "Version: {}", + "title": "Changelog", + "close_btn": "Close" + } }, "asset_select": { "types": { diff --git a/sepal_ui/sepalwidgets/app.py b/sepal_ui/sepalwidgets/app.py index 59f9f397..e585797d 100644 --- a/sepal_ui/sepalwidgets/app.py +++ b/sepal_ui/sepalwidgets/app.py @@ -32,6 +32,7 @@ from sepal_ui.scripts import utils as su from sepal_ui.sepalwidgets.alert import Banner from sepal_ui.sepalwidgets.sepalwidget import SepalWidget +from sepal_ui.sepalwidgets.widget import Markdown from sepal_ui.translator import Translator __all__ = [ @@ -46,7 +47,6 @@ class LocaleSelect(v.Menu, SepalWidget): - COUNTRIES: pd.DataFrame = pd.read_parquet( Path(__file__).parents[1] / "data" / "locale.parquet" ) @@ -140,7 +140,6 @@ def _get_country_items(self, locales: list) -> List[str]: country_list = [] filtered_countries = self.COUNTRIES[self.COUNTRIES.code.isin(locales)] for r in filtered_countries.itertuples(index=False): - attr = {**self.ATTR, "src": self.FLAG.format(r.flag), "alt": r.name} children = [ @@ -177,7 +176,6 @@ def _on_locale_select(self, change: dict) -> None: class ThemeSelect(v.Btn, SepalWidget): - THEME_ICONS: dict = {"dark": "fa-solid fa-moon", "light": "fa-solid fa-sun"} "the dictionnry of icons to use for each theme (used as keys)" @@ -232,7 +230,6 @@ def toggle_theme(self, *args) -> None: class AppBar(v.AppBar, SepalWidget): - toogle_button: Optional[v.Btn] "The btn to display or hide the drawer to the user" @@ -297,7 +294,6 @@ def set_title(self, title: str) -> Self: class DrawerItem(v.ListItem, SepalWidget): - rt: Optional[ResizeTrigger] = None "The trigger to resize maps and other javascript object when jumping from a tile to another" @@ -415,7 +411,6 @@ def display_tile(self, tiles: List[v.Card]) -> Self: return self def _on_click(self, *args) -> Self: - for tile in self.tiles: show = self._metadata["card_id"] == tile._metadata["mount_id"] tile.viz = show @@ -433,7 +428,6 @@ def _on_click(self, *args) -> Self: class NavDrawer(v.NavigationDrawer, SepalWidget): - items: List[DrawerItem] = [] "the list of all the drawerItem to display in the drawer" @@ -458,12 +452,15 @@ def __init__( """ self.items = items + v_slots = [] + code_link = [] if code: item_code = DrawerItem( ms.widgets.navdrawer.code, icon="fa-regular fa-file-code", href=code ) code_link.append(item_code) + if wiki: item_wiki = DrawerItem( ms.widgets.navdrawer.wiki, icon="fa-solid fa-book-open", href=wiki @@ -475,6 +472,10 @@ def __init__( ) code_link.append(item_bug) + version_card = VersionCard(code) + if version_card: + v_slots = [{"name": "append", "children": [version_card]}] + children = [ v.List(dense=True, children=self.items), v.Divider(), @@ -486,6 +487,7 @@ def __init__( kwargs["app"] = True kwargs.setdefault("color", color.darker) kwargs["children"] = children + kwargs.setdefault("v_slots", v_slots) # call the constructor super().__init__(**kwargs) @@ -544,7 +546,6 @@ def __init__(self, text: str = "", **kwargs) -> None: class App(v.App, SepalWidget): - tiles: List[v.Card] = [] "the tiles of the app" @@ -732,7 +733,6 @@ def _remove_banner(self, change: dict) -> None: Adapt the banner display so that the first one is the only one shown displaying the number of other banner in the queue """ if change["new"] is False: - # extract the banner from the app children children, banner_list = [], [] for e in self.content.children.copy(): @@ -752,3 +752,70 @@ def _remove_banner(self, change: dict) -> None: self.content.children = banner_list + children return + + +def VersionCard(github_url: str) -> Optional[v.Card]: + """Returns a card with the current version of the app and a changelog dialog. + + Args: + github_url: the url of the github repository of the app + """ + app_version = su.get_app_version(github_url) + if not app_version: + return None + + release_text, changelog_text = su.get_changelog(github_url) + + content = [] + + if release_text: + content.append(Markdown(release_text)) + + if changelog_text: + content.append(v.Divider()) + content.append(Markdown(changelog_text)) + + btn_close = v.Btn( + color="primary", + children=[ms.widgets.navdrawer.changelog.close_btn], + on_event=[ + ( + "click", + lambda *_: setattr(w_changelog, "v_model", False), + ) + ], + ) + + w_changelog = v.Dialog( + v_model=False, + max_width=750, + children=[ + v.Card( + children=[ + v.CardTitle( + class_="headline", + children=[ms.widgets.navdrawer.changelog.title], + ), + v.CardText(children=content), + v.CardActions(children=[v.Spacer(), btn_close]), + ] + ) + ], + ) + + w_version = v.Card( + class_="secondary text-center", + children=[ + v.CardText( + children=[ + ms.widgets.navdrawer.changelog.version.format(app_version), + w_changelog, + ] + ), + ], + ) + + w_version.on_event("click", lambda *_: setattr(w_changelog, "v_model", True)) + btn_close.on_event("click", lambda *_: setattr(w_changelog, "v_model", False)) + + return w_version From 359688edb14b1ac6c4a33eb3f5846966d3bf6078 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Mon, 25 Sep 2023 16:19:32 +0200 Subject: [PATCH 02/19] feat: define methods to process and retrieve the changelog from remote repo --- sepal_ui/scripts/utils.py | 93 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index 34925cb8..a7a8d204 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -7,12 +7,13 @@ import string import warnings from pathlib import Path -from typing import Any, Sequence, Union +from typing import Any, List, Sequence, Tuple, Union from urllib.parse import urlparse import ee import httplib2 import ipyvuetify as v +import requests from anyascii import anyascii from deprecated.sphinx import deprecated, versionadded from matplotlib import colors as c @@ -128,10 +129,8 @@ def init_ee() -> None: """ # only do the initialization if the credential are missing if not ee.data._credentials: - # if the credentials token is asved in the environment use it if "EARTHENGINE_TOKEN" in os.environ: - # write the token to the appropriate folder ee_token = os.environ["EARTHENGINE_TOKEN"] credential_folder_path = Path.home() / ".config" / "earthengine" @@ -183,7 +182,6 @@ def to_colors( out_color = "#000000" # default black color if isinstance(in_color, tuple) and len(in_color) == 3: - # rescale color if necessary if all(isinstance(item, int) for item in in_color): in_color = [c / 255.0 for c in in_color] @@ -191,7 +189,6 @@ def to_colors( return transform(in_color) else: - # try to guess the color system try: return transform(in_color) @@ -375,6 +372,92 @@ def check_input(input_: Any, msg: str = ms.utils.check_input.error) -> bool: return init +def parse_github_url(github_url: str) -> List[Tuple[str, str]]: + """Parse a github url to get the owner and the repo name. + + Args: + github_url: the url of the github repository + + Returns: + a tuple with the owner and the repo name + """ + # Deconstruct the url and get the repo_owner and repo_name + parsed_url = urlparse(github_url) + repo_owner, repo_name = parsed_url.path.strip("/").split("/") + + return repo_owner, repo_name + + +def get_app_version(github_url: str, branch="release") -> str: + """Get the current version of the a github project using the __version__.py file in the root. + + Args: + repo_url: the url of the repository where the app is hosted + + Returns: + the version of the repository + """ + repo_owner, repo_name = parse_github_url(github_url) + + version_url = f"https://raw.githubusercontent.com/{repo_owner}/{repo_name}/{branch}/__version__.py" + + response = requests.get(version_url) + if response.status_code == 200: + lines = response.text.split("\n") + for line in lines: + if "__version__" in line: + return line.split("=")[-1].strip().strip("\"'") + + return None + + +def get_changelog(github_url: str, branch: str = "release") -> str: + """Check if the repository contains a changelog file and/or a release and return its content. + + Returns: + str: the content of the release and/or changelog file + """ + repo_owner, repo_name = parse_github_url(github_url) + release_text, changelog_text = "", "" + + release_url = ( + f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" + ) + + changelog_url = ( + f"https://github.com/{repo_owner}/{repo_name}/blob/{branch}/CHANGELOG.md" + ) + + response = requests.get(release_url) + if response.status_code == 200: + release_text = response.json().get("body", "") + url_pattern = r"https://github\.com/[^ ]+/pull/\d+" + + def wrap_url_in_a_tag(match): + url = match.group(0) + return f'{url}' + + # Replace URLs with tags + release_text = re.sub(url_pattern, wrap_url_in_a_tag, release_text) + + response = requests.get(changelog_url) + if response.status_code == 200: + changelog_text = response.json()["payload"]["blob"]["richText"] + + # remove_html_tags_but_keep_text + for tag in ["a", "article", "svg"]: + # Regular expression pattern to capture text between specified HTML tags + # Using a non-greedy approach to capture content + pattern = f"<{tag} [^>]*>(.*?)" + changelog_text = re.sub(pattern, r"\1", changelog_text, flags=re.DOTALL) + + # Handle tags without attributes + pattern = f"<{tag}>(.*?)" + changelog_text = re.sub(pattern, r"\1", changelog_text, flags=re.DOTALL) + + return release_text, changelog_text + + ################################################################################ # the soon to be deprecated decorators # From 1e49a4388de25be6ade033472a7b4ab23fd0ccda Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Mon, 25 Sep 2023 22:06:50 +0200 Subject: [PATCH 03/19] refactor: use pyproject.toml to get module version --- sepal_ui/scripts/utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index a7a8d204..82803a47 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -9,6 +9,7 @@ from pathlib import Path from typing import Any, List, Sequence, Tuple, Union from urllib.parse import urlparse +import toml import ee import httplib2 @@ -399,15 +400,12 @@ def get_app_version(github_url: str, branch="release") -> str: """ repo_owner, repo_name = parse_github_url(github_url) - version_url = f"https://raw.githubusercontent.com/{repo_owner}/{repo_name}/{branch}/__version__.py" - + version_url = f"https://raw.githubusercontent.com/{repo_owner}/{repo_name}/{branch}/pyproject.toml" response = requests.get(version_url) if response.status_code == 200: - lines = response.text.split("\n") - for line in lines: - if "__version__" in line: - return line.split("=")[-1].strip().strip("\"'") - + parsed_toml = toml.loads(response.text) + version = parsed_toml.get("project", {}).get("version", None) + return version return None From 98738bb0c4b40b1bd72bd3222e14e58b96d35c13 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 26 Sep 2023 16:38:43 +0200 Subject: [PATCH 04/19] feat: use local repository data to show changelog --- sepal_ui/scripts/utils.py | 123 ++++++++++++++++++----------------- sepal_ui/sepalwidgets/app.py | 6 +- 2 files changed, 66 insertions(+), 63 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index 82803a47..465cdd94 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -1,5 +1,6 @@ """All the helper function of sepal-ui.""" +import configparser import math import os import random @@ -7,14 +8,14 @@ import string import warnings from pathlib import Path -from typing import Any, List, Sequence, Tuple, Union +from typing import Any, Sequence, Tuple, Union from urllib.parse import urlparse -import toml import ee import httplib2 import ipyvuetify as v import requests +import toml from anyascii import anyascii from deprecated.sphinx import deprecated, versionadded from matplotlib import colors as c @@ -373,85 +374,87 @@ def check_input(input_: Any, msg: str = ms.utils.check_input.error) -> bool: return init -def parse_github_url(github_url: str) -> List[Tuple[str, str]]: - """Parse a github url to get the owner and the repo name. - - Args: - github_url: the url of the github repository +def get_app_version() -> str: + """Get the current version of the a github project using the pyproject.toml file in the root. Returns: - a tuple with the owner and the repo name + the version of the repository """ - # Deconstruct the url and get the repo_owner and repo_name - parsed_url = urlparse(github_url) - repo_owner, repo_name = parsed_url.path.strip("/").split("/") + # get the path to the pyproject.toml file + pyproject_path = Path.cwd() / "pyproject.toml" - return repo_owner, repo_name + # check if the file exist + if pyproject_path.exists(): + # read the file using toml + with pyproject_path.open("r") as f: + pyproject = toml.load(f) + # get the version + version = pyproject.get("project", {}).get("version", None) + return version -def get_app_version(github_url: str, branch="release") -> str: - """Get the current version of the a github project using the __version__.py file in the root. + return None - Args: - repo_url: the url of the repository where the app is hosted - Returns: - the version of the repository - """ - repo_owner, repo_name = parse_github_url(github_url) +def get_repo_info(repo_folder: str = os.getcwd()) -> Tuple[str, str]: + """Get the repository name and owner from the git config file.""" + config = configparser.ConfigParser() + git_config_path = os.path.join(repo_folder, ".git", "config") + config.read(git_config_path) - version_url = f"https://raw.githubusercontent.com/{repo_owner}/{repo_name}/{branch}/pyproject.toml" - response = requests.get(version_url) - if response.status_code == 200: - parsed_toml = toml.loads(response.text) - version = parsed_toml.get("project", {}).get("version", None) - return version - return None + try: + remote_url = config.get('remote "origin"', "url") + except (configparser.NoSectionError, configparser.NoOptionError): + return "", "" + + # Parse remote URL for repo_owner and repo_name + match = re.search(r":(.*?)/(.*?)(?:\.git)?$", remote_url) # for SSH + if not match: + match = re.search(r"/(.*?)/(.*?)(?:\.git)?$", remote_url) # for HTTPS + + if match: + repo_owner, repo_name = match.groups() + return repo_owner, repo_name + else: + return "", "" -def get_changelog(github_url: str, branch: str = "release") -> str: - """Check if the repository contains a changelog file and/or a release and return its content. +def get_changelog(repo_folder: str = os.getcwd()) -> str: + """Check if the repository contains a changelog file and/or a remote release and return its content. Returns: str: the content of the release and/or changelog file """ - repo_owner, repo_name = parse_github_url(github_url) - release_text, changelog_text = "", "" + changelog_text, release_text = "", "" + repo_owner, repo_name = get_repo_info(repo_folder) release_url = ( f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" ) - changelog_url = ( - f"https://github.com/{repo_owner}/{repo_name}/blob/{branch}/CHANGELOG.md" - ) - response = requests.get(release_url) - if response.status_code == 200: - release_text = response.json().get("body", "") - url_pattern = r"https://github\.com/[^ ]+/pull/\d+" - - def wrap_url_in_a_tag(match): - url = match.group(0) - return f'{url}' - - # Replace URLs with tags - release_text = re.sub(url_pattern, wrap_url_in_a_tag, release_text) - - response = requests.get(changelog_url) - if response.status_code == 200: - changelog_text = response.json()["payload"]["blob"]["richText"] - - # remove_html_tags_but_keep_text - for tag in ["a", "article", "svg"]: - # Regular expression pattern to capture text between specified HTML tags - # Using a non-greedy approach to capture content - pattern = f"<{tag} [^>]*>(.*?)" - changelog_text = re.sub(pattern, r"\1", changelog_text, flags=re.DOTALL) - - # Handle tags without attributes - pattern = f"<{tag}>(.*?)" - changelog_text = re.sub(pattern, r"\1", changelog_text, flags=re.DOTALL) + if all([repo_owner, repo_name]) and response.status_code == 200: + response_json = response.json() + name = response_json.get("name") + + if name == f"v_{get_app_version()}": + release_text = response_json.get("body") + + url_pattern = r"https://github\.com/[^ ]+/pull/\d+" + + # Replace URLs with tags + def wrap_url_in_a_tag(match): + url = match.group(0) + return f'{url}' + + release_text = re.sub(url_pattern, wrap_url_in_a_tag, release_text) + + # get the path to the pyproject.toml file + changelog_path = Path.cwd() / "CHANGELOG.md" + + # check if the file exist + if changelog_path.exists(): + changelog_text = changelog_path.read_text() return release_text, changelog_text diff --git a/sepal_ui/sepalwidgets/app.py b/sepal_ui/sepalwidgets/app.py index e585797d..b76403f8 100644 --- a/sepal_ui/sepalwidgets/app.py +++ b/sepal_ui/sepalwidgets/app.py @@ -754,17 +754,17 @@ def _remove_banner(self, change: dict) -> None: return -def VersionCard(github_url: str) -> Optional[v.Card]: +def VersionCard() -> Optional[v.Card]: """Returns a card with the current version of the app and a changelog dialog. Args: github_url: the url of the github repository of the app """ - app_version = su.get_app_version(github_url) + app_version = su.get_app_version() if not app_version: return None - release_text, changelog_text = su.get_changelog(github_url) + release_text, changelog_text = su.get_changelog() content = [] From e1e7f3ff8ae607cf6bbaf276cc1250291d95255d Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 26 Sep 2023 18:41:04 +0200 Subject: [PATCH 05/19] feat: define arguments to changelog functions --- sepal_ui/scripts/utils.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index 465cdd94..6c4f6fe0 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -374,14 +374,14 @@ def check_input(input_: Any, msg: str = ms.utils.check_input.error) -> bool: return init -def get_app_version() -> str: +def get_app_version(repo_folder: Union[Path, str] = Path.cwd()) -> str: """Get the current version of the a github project using the pyproject.toml file in the root. Returns: the version of the repository """ # get the path to the pyproject.toml file - pyproject_path = Path.cwd() / "pyproject.toml" + pyproject_path = repo_folder / "pyproject.toml" # check if the file exist if pyproject_path.exists(): @@ -396,30 +396,33 @@ def get_app_version() -> str: return None -def get_repo_info(repo_folder: str = os.getcwd()) -> Tuple[str, str]: +def get_repo_info(repo_folder: Union[Path, str] = Path.cwd()) -> Tuple[str, str]: """Get the repository name and owner from the git config file.""" config = configparser.ConfigParser() - git_config_path = os.path.join(repo_folder, ".git", "config") + git_config_path = Path(repo_folder) / ".git/config" config.read(git_config_path) try: remote_url = config.get('remote "origin"', "url") except (configparser.NoSectionError, configparser.NoOptionError): - return "", "" + return "no find", "asdf" + + # Check if URL is likely SSH + if "git@" in remote_url: + match = re.search(r":(.*?)/(.*?)(?:\.git)?$", remote_url) - # Parse remote URL for repo_owner and repo_name - match = re.search(r":(.*?)/(.*?)(?:\.git)?$", remote_url) # for SSH - if not match: - match = re.search(r"/(.*?)/(.*?)(?:\.git)?$", remote_url) # for HTTPS + # Assume URL is HTTPS otherwise + else: + match = re.search(r"github\.com/(.*?)/(.*?)(?:\.git)?$", remote_url) if match: - repo_owner, repo_name = match.groups() - return repo_owner, repo_name + return match.groups() + else: return "", "" -def get_changelog(repo_folder: str = os.getcwd()) -> str: +def get_changelog(repo_folder: Union[Path, str] = Path.cwd()) -> str: """Check if the repository contains a changelog file and/or a remote release and return its content. Returns: @@ -450,7 +453,7 @@ def wrap_url_in_a_tag(match): release_text = re.sub(url_pattern, wrap_url_in_a_tag, release_text) # get the path to the pyproject.toml file - changelog_path = Path.cwd() / "CHANGELOG.md" + changelog_path = Path(repo_folder) / "CHANGELOG.md" # check if the file exist if changelog_path.exists(): From 691a7254d4d82a7f61ba14a75dcd6164cf24862b Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 26 Sep 2023 18:41:48 +0200 Subject: [PATCH 06/19] feat: define arguments to changelog functions --- sepal_ui/scripts/utils.py | 2 +- sepal_ui/sepalwidgets/app.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index 6c4f6fe0..adbdcd68 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -405,7 +405,7 @@ def get_repo_info(repo_folder: Union[Path, str] = Path.cwd()) -> Tuple[str, str] try: remote_url = config.get('remote "origin"', "url") except (configparser.NoSectionError, configparser.NoOptionError): - return "no find", "asdf" + return "", "" # Check if URL is likely SSH if "git@" in remote_url: diff --git a/sepal_ui/sepalwidgets/app.py b/sepal_ui/sepalwidgets/app.py index b76403f8..c84ef9d7 100644 --- a/sepal_ui/sepalwidgets/app.py +++ b/sepal_ui/sepalwidgets/app.py @@ -472,7 +472,7 @@ def __init__( ) code_link.append(item_bug) - version_card = VersionCard(code) + version_card = VersionCard() if version_card: v_slots = [{"name": "append", "children": [version_card]}] @@ -754,17 +754,17 @@ def _remove_banner(self, change: dict) -> None: return -def VersionCard() -> Optional[v.Card]: +def VersionCard(repo_folder: str = Path.cwd()) -> Optional[v.Card]: """Returns a card with the current version of the app and a changelog dialog. Args: github_url: the url of the github repository of the app """ - app_version = su.get_app_version() + app_version = su.get_app_version(repo_folder) if not app_version: return None - release_text, changelog_text = su.get_changelog() + release_text, changelog_text = su.get_changelog(repo_folder) content = [] @@ -804,7 +804,8 @@ def VersionCard() -> Optional[v.Card]: ) w_version = v.Card( - class_="secondary text-center", + class_="text-center", + tile=True, children=[ v.CardText( children=[ From 8706eb4d09ad4166eda032c68351e178b11f0ecd Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 26 Sep 2023 18:46:17 +0200 Subject: [PATCH 07/19] feat: add dummy changelog and version to panel_app template --- sepal_ui/templates/panel_app/CHANGELOG.md | 5 +++++ sepal_ui/templates/panel_app/pyproject.toml | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 sepal_ui/templates/panel_app/CHANGELOG.md diff --git a/sepal_ui/templates/panel_app/CHANGELOG.md b/sepal_ui/templates/panel_app/CHANGELOG.md new file mode 100644 index 00000000..28d25065 --- /dev/null +++ b/sepal_ui/templates/panel_app/CHANGELOG.md @@ -0,0 +1,5 @@ +## v_1.1.1 (2023-09-26) + +### Feature + +- create a dummy changelog \ No newline at end of file diff --git a/sepal_ui/templates/panel_app/pyproject.toml b/sepal_ui/templates/panel_app/pyproject.toml index 5ef98514..6a02b77d 100644 --- a/sepal_ui/templates/panel_app/pyproject.toml +++ b/sepal_ui/templates/panel_app/pyproject.toml @@ -1,3 +1,6 @@ +[project] +version = "1.1.1" + [sepal-ui] init-notebook = "ui.ipynb" From 9b1973fa9bab21ba946a5ca9fd763dd470088099 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 26 Sep 2023 18:48:30 +0200 Subject: [PATCH 08/19] test: define tests to get changelog on VersionCard component --- tests/test_scripts/test_utils.py | 82 ++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/test_scripts/test_utils.py b/tests/test_scripts/test_utils.py index 9681840f..7cc1f654 100644 --- a/tests/test_scripts/test_utils.py +++ b/tests/test_scripts/test_utils.py @@ -273,3 +273,85 @@ def test_check_input() -> None: su.check_input([]) return + + +def test_get_app_version(tmp_dir): + """Test if the function gets the pyproject version.""" + dummy_repo = tmp_dir / "dummy_repo" + dummy_repo.mkdir(exist_ok=True, parents=True) + pyproject_file = dummy_repo / "pyproject.toml" + + # create a temporary pyproject.toml file + with open(pyproject_file, "w") as f: + f.write("[project]\nversion = '1.0.0'") + + # test the function + version = su.get_app_version(dummy_repo) + assert version == "1.0.0" + + # remove the temporary file + pyproject_file.unlink() + + +def test_get_repo_info(tmp_dir): + """Test if the function returns repo_owner and repo_name correctly.""" + # test the function with a known repository URL + # Create a temporary .git folder inside the temporary directory + + # tmp_dir = Path("delete_mi") + git_folder = tmp_dir / ".git" + git_folder.mkdir(exist_ok=True, parents=True) + + expected_owner = "12rambau" + expected_repo = "sepal_ui" + + config = ConfigParser() + config.add_section('remote "origin"') + config.set( + 'remote "origin"', "url", f"git@github.com:{expected_owner}/{expected_repo}.git" + ) + print(git_folder) + with open(git_folder / "config", "w") as f: + config.write(f) + + repo_owner, repo_name = su.get_repo_info(repo_folder=tmp_dir) + + assert repo_owner == expected_owner + assert repo_name == expected_repo + + config.set( + 'remote "origin"', + "url", + f"https://github.com/{expected_owner}/{expected_repo}.git", + ) + with open(git_folder / "config", "w") as f: + config.write(f) + + repo_owner, repo_name = su.get_repo_info(repo_folder=tmp_dir) + + assert repo_owner == expected_owner + assert repo_name == expected_repo + + +def test_get_changelog(tmp_dir): + """Test if the function returns the changelog correctly.""" + # Create a dummy directory with a changelog file + + dummy_repo = tmp_dir / "dummy_repo" + dummy_repo.mkdir(exist_ok=True, parents=True) + + # Create a dummy changelog file and write some text in it + changelog_file = dummy_repo / "CHANGELOG.md" + changelog_file.touch() + changelog_file.write_text("# Changelog") + + # Test the function + changelog = su.get_changelog(repo_folder=dummy_repo) + + assert changelog == ("", "# Changelog") + + # assume that this is executed in this git repository + release_text, changelog_text = su.get_changelog() + + assert release_text is not None + assert changelog_text is not None From 2c89ce22b86b3188017bfb6bf555e1d8e77c9855 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 26 Sep 2023 18:54:11 +0200 Subject: [PATCH 09/19] build: add toml as dependency to tests --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 255666ee..947a56f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,7 @@ test = [ "Flake8-pyproject", "nbmake", "pytest-regressions", + "toml", ] doc = [ "sphinx<7", From 68db178c44f3afa709baae6fd9232437db1aa4ab Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Wed, 27 Sep 2023 11:23:43 +0200 Subject: [PATCH 10/19] test: test missing cases --- tests/test_scripts/test_utils.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/test_scripts/test_utils.py b/tests/test_scripts/test_utils.py index 7cc1f654..43630ce4 100644 --- a/tests/test_scripts/test_utils.py +++ b/tests/test_scripts/test_utils.py @@ -292,6 +292,10 @@ def test_get_app_version(tmp_dir): # remove the temporary file pyproject_file.unlink() + # test the function with a non-existing pyproject.toml file + version = su.get_app_version(dummy_repo) + assert version is None + def test_get_repo_info(tmp_dir): """Test if the function returns repo_owner and repo_name correctly.""" @@ -310,7 +314,6 @@ def test_get_repo_info(tmp_dir): config.set( 'remote "origin"', "url", f"git@github.com:{expected_owner}/{expected_repo}.git" ) - print(git_folder) with open(git_folder / "config", "w") as f: config.write(f) @@ -332,6 +335,15 @@ def test_get_repo_info(tmp_dir): assert repo_owner == expected_owner assert repo_name == expected_repo + # Test the function with a non-existing matching repository URL + config.set('remote "origin"', "url", "toto") + with open(git_folder / "config", "w") as f: + config.write(f) + + repo_info = su.get_repo_info(repo_folder=tmp_dir) + + assert repo_info == ("", "") + def test_get_changelog(tmp_dir): """Test if the function returns the changelog correctly.""" From d16ac5710aff74dbfb2a1f102676b6c29a9e1c1e Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Wed, 27 Sep 2023 11:28:43 +0200 Subject: [PATCH 11/19] test: test version card component --- sepal_ui/sepalwidgets/app.py | 4 ++- tests/test_sepalwidgets/test_App.py | 43 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/sepal_ui/sepalwidgets/app.py b/sepal_ui/sepalwidgets/app.py index c84ef9d7..a8700a83 100644 --- a/sepal_ui/sepalwidgets/app.py +++ b/sepal_ui/sepalwidgets/app.py @@ -437,6 +437,7 @@ def __init__( code: str = "", wiki: str = "", issue: str = "", + repo_folder: str = Path.cwd(), **kwargs, ) -> None: """Custom NavDrawer using the different DrawerItems of the user. @@ -448,6 +449,7 @@ def __init__( code: the absolute link to the source code wiki: the absolute link the the wiki page issue: the absolute link to the issue tracker + repo_folder: the path to the github repository folder where the changelog and version are stored. Default to the current working directory. kwargs (optional) any parameter from a v.NavigationDrawer. If set, 'app' and 'children' will be overwritten. """ self.items = items @@ -472,7 +474,7 @@ def __init__( ) code_link.append(item_bug) - version_card = VersionCard() + version_card = VersionCard(repo_folder=repo_folder) if version_card: v_slots = [{"name": "append", "children": [version_card]}] diff --git a/tests/test_sepalwidgets/test_App.py b/tests/test_sepalwidgets/test_App.py index fe65c5c0..28581c9e 100644 --- a/tests/test_sepalwidgets/test_App.py +++ b/tests/test_sepalwidgets/test_App.py @@ -1,5 +1,7 @@ """Test the App widget.""" +import os + import ipyvuetify as v import pytest @@ -132,6 +134,47 @@ def test_close_banner(app: sw.App) -> None: return +def test_version_card(tmp_dir) -> None: + """Test the drawer of the app.""" + # arrange + app_version = "999.999.1" + changelog_text = "# Changelog" + dummy_repo = tmp_dir / "dummy_repo" + dummy_repo.mkdir(exist_ok=True, parents=True) + + # Change current working directory to dummy repo + os.chdir(dummy_repo) + + # Check that if there is no pyproject.toml file, the version card is not present + navigation_drawer = sw.NavDrawer([], repo_folder=dummy_repo) + + assert navigation_drawer.v_slots == [] + + # Create a pyproject.toml file and a changelog + pyproject_file = dummy_repo / "pyproject.toml" + + # create a temporary pyproject.toml file + with open(pyproject_file, "w") as f: + f.write(f"[project]\nversion = '{app_version}'") + + # Create a dummy changelog file and write some text in it + changelog_file = dummy_repo / "CHANGELOG.md" + changelog_file.touch() + changelog_file.write_text(f"{changelog_text}") + + navigation_drawer = sw.NavDrawer([], repo_folder=dummy_repo) + + # Check if the version card is present + + assert len(navigation_drawer.v_slots) == 1 + + version_card = navigation_drawer.v_slots[0]["children"][0] + + # Check if the version card has the right content + displayed_version = version_card.children[0].children[0] + assert displayed_version == f"Version: {app_version}" + + @pytest.fixture(scope="function") def app() -> sw.App: """Create a default App. From 2f720635c1854bd44e3d7c90df0b483326dc1012 Mon Sep 17 00:00:00 2001 From: Daniel Guerrero Date: Thu, 28 Sep 2023 18:10:07 +0200 Subject: [PATCH 12/19] style: sepal_ui/scripts/utils.py Co-authored-by: Rambaud Pierrick <12rambau@users.noreply.github.com> --- sepal_ui/scripts/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index adbdcd68..11b7eeb8 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -386,8 +386,7 @@ def get_app_version(repo_folder: Union[Path, str] = Path.cwd()) -> str: # check if the file exist if pyproject_path.exists(): # read the file using toml - with pyproject_path.open("r") as f: - pyproject = toml.load(f) + pyproject = toml.loads(pyproject_path.read_text()) # get the version version = pyproject.get("project", {}).get("version", None) From 87236f484731b657cdb556db1f5f4e3ca0fed207 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Thu, 28 Sep 2023 18:09:36 +0200 Subject: [PATCH 13/19] build: add missing toml to doc requirements --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 947a56f6..cdb00f2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,6 +95,7 @@ doc = [ "m2r2>=0.3.3", "sphinxcontrib-autoprogram", "sphinx-favicon>=1.0.1", + "toml", ] [project.scripts] From b4cacb2d4466adad2b81a5d9bf58c7e31d32822c Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Thu, 28 Sep 2023 18:30:02 +0200 Subject: [PATCH 14/19] refactor: fix typing parameters --- sepal_ui/scripts/utils.py | 13 ++++++++----- sepal_ui/sepalwidgets/app.py | 4 +++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index 11b7eeb8..adfb4e52 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -26,6 +26,9 @@ from sepal_ui.scripts import decorator as sd from sepal_ui.scripts.warning import SepalWarning +# Types +Pathlike = Union[str, Path] + def hide_component(widget: v.VuetifyWidget) -> v.VuetifyWidget: """Hide a vuetify based component. @@ -63,7 +66,7 @@ def show_component(widget: v.VuetifyWidget) -> v.VuetifyWidget: return widget -def create_download_link(pathname: Union[str, Path]) -> str: +def create_download_link(pathname: Pathlike) -> str: """Create a clickable link to download the pathname target. Args: @@ -105,7 +108,7 @@ def random_string(string_length: int = 3) -> str: return "".join(random.choice(letters) for i in range(string_length)) -def get_file_size(filename: Union[str, Path]) -> str: +def get_file_size(filename: Pathlike) -> str: """Get the file size as string of 2 digit in the adapted scale (B, KB, MB....). Args: @@ -374,7 +377,7 @@ def check_input(input_: Any, msg: str = ms.utils.check_input.error) -> bool: return init -def get_app_version(repo_folder: Union[Path, str] = Path.cwd()) -> str: +def get_app_version(repo_folder: Pathlike = Path.cwd()) -> str: """Get the current version of the a github project using the pyproject.toml file in the root. Returns: @@ -395,7 +398,7 @@ def get_app_version(repo_folder: Union[Path, str] = Path.cwd()) -> str: return None -def get_repo_info(repo_folder: Union[Path, str] = Path.cwd()) -> Tuple[str, str]: +def get_repo_info(repo_folder: Pathlike = Path.cwd()) -> Tuple[str, str]: """Get the repository name and owner from the git config file.""" config = configparser.ConfigParser() git_config_path = Path(repo_folder) / ".git/config" @@ -421,7 +424,7 @@ def get_repo_info(repo_folder: Union[Path, str] = Path.cwd()) -> Tuple[str, str] return "", "" -def get_changelog(repo_folder: Union[Path, str] = Path.cwd()) -> str: +def get_changelog(repo_folder: Pathlike = Path.cwd()) -> str: """Check if the repository contains a changelog file and/or a remote release and return its content. Returns: diff --git a/sepal_ui/sepalwidgets/app.py b/sepal_ui/sepalwidgets/app.py index a8700a83..c851f87e 100644 --- a/sepal_ui/sepalwidgets/app.py +++ b/sepal_ui/sepalwidgets/app.py @@ -437,7 +437,7 @@ def __init__( code: str = "", wiki: str = "", issue: str = "", - repo_folder: str = Path.cwd(), + repo_folder: su.Pathlike = "", **kwargs, ) -> None: """Custom NavDrawer using the different DrawerItems of the user. @@ -454,6 +454,8 @@ def __init__( """ self.items = items + repo_folder = Path(repo_folder) if repo_folder else Path.cwd() + v_slots = [] code_link = [] From 1aefe86a37f3955fa282fee15a81f617ece09621 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Thu, 5 Oct 2023 11:45:46 +0200 Subject: [PATCH 15/19] style(scripts.utils): typo --- sepal_ui/scripts/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index adfb4e52..9409d762 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -389,11 +389,10 @@ def get_app_version(repo_folder: Pathlike = Path.cwd()) -> str: # check if the file exist if pyproject_path.exists(): # read the file using toml - pyproject = toml.loads(pyproject_path.read_text()) + pyproject = toml.loads(pyproject_path.read_text()) # get the version - version = pyproject.get("project", {}).get("version", None) - return version + return pyproject.get("project", {}).get("version", None) return None From 5f9788d1118b02069b2978aedf9000c9a294ac66 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Thu, 5 Oct 2023 12:15:03 +0200 Subject: [PATCH 16/19] refactor(version_card): use tomli instead of toml to be inline with #888 --- sepal_ui/scripts/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sepal_ui/scripts/utils.py b/sepal_ui/scripts/utils.py index 9409d762..de2f128b 100644 --- a/sepal_ui/scripts/utils.py +++ b/sepal_ui/scripts/utils.py @@ -15,7 +15,7 @@ import httplib2 import ipyvuetify as v import requests -import toml +import tomli from anyascii import anyascii from deprecated.sphinx import deprecated, versionadded from matplotlib import colors as c @@ -388,8 +388,8 @@ def get_app_version(repo_folder: Pathlike = Path.cwd()) -> str: # check if the file exist if pyproject_path.exists(): - # read the file using toml - pyproject = toml.loads(pyproject_path.read_text()) + # read the file using tomli + pyproject = tomli.loads(pyproject_path.read_text()) # get the version return pyproject.get("project", {}).get("version", None) From d2fd9146eca455e755aa5f9fece4f155dedc879d Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Thu, 5 Oct 2023 12:43:58 +0200 Subject: [PATCH 17/19] build(pyproject): update missed dependencies on test and doc --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ce3f2e4f..989740b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,7 @@ test = [ "Flake8-pyproject", "nbmake", "pytest-regressions", - "toml", + "tomli", ] doc = [ "sphinx<7", @@ -96,7 +96,7 @@ doc = [ "m2r2>=0.3.3", "sphinxcontrib-autoprogram", "sphinx-favicon>=1.0.1", - "toml", + "tomli", ] [project.scripts] From dcdee338c1ba789a038bfc9e8ae7eefbc6045c69 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Sun, 19 Nov 2023 15:53:54 +0100 Subject: [PATCH 18/19] test: use tmp factory --- tests/conftest.py | 10 ++++++++++ tests/test_scripts/test_utils.py | 25 +++++++++++-------------- tests/test_sepalwidgets/test_App.py | 14 ++++++-------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fb099ea0..3cb27c85 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -374,3 +374,13 @@ def cred() -> list: credentials = json.loads(os.getenv("PLANET_API_CREDENTIALS")) return list(credentials.values()) + + +@pytest.fixture(scope="session") +def repo_dir(tmp_path_factory: pytest.TempPathFactory) -> Path: + """Create a dummy repo directory. + + Returns: + Path to the repo dir + """ + return tmp_path_factory.mktemp("repo_dir") diff --git a/tests/test_scripts/test_utils.py b/tests/test_scripts/test_utils.py index 43630ce4..090899ab 100644 --- a/tests/test_scripts/test_utils.py +++ b/tests/test_scripts/test_utils.py @@ -275,9 +275,9 @@ def test_check_input() -> None: return -def test_get_app_version(tmp_dir): +def test_get_app_version(repo_dir): """Test if the function gets the pyproject version.""" - dummy_repo = tmp_dir / "dummy_repo" + dummy_repo = repo_dir / "dummy_repo" dummy_repo.mkdir(exist_ok=True, parents=True) pyproject_file = dummy_repo / "pyproject.toml" @@ -297,13 +297,13 @@ def test_get_app_version(tmp_dir): assert version is None -def test_get_repo_info(tmp_dir): +def test_get_repo_info(repo_dir): """Test if the function returns repo_owner and repo_name correctly.""" # test the function with a known repository URL # Create a temporary .git folder inside the temporary directory - # tmp_dir = Path("delete_mi") - git_folder = tmp_dir / ".git" + # repo_dir = Path("delete_mi") + git_folder = repo_dir / ".git" git_folder.mkdir(exist_ok=True, parents=True) expected_owner = "12rambau" @@ -317,7 +317,7 @@ def test_get_repo_info(tmp_dir): with open(git_folder / "config", "w") as f: config.write(f) - repo_owner, repo_name = su.get_repo_info(repo_folder=tmp_dir) + repo_owner, repo_name = su.get_repo_info(repo_folder=repo_dir) assert repo_owner == expected_owner assert repo_name == expected_repo @@ -330,7 +330,7 @@ def test_get_repo_info(tmp_dir): with open(git_folder / "config", "w") as f: config.write(f) - repo_owner, repo_name = su.get_repo_info(repo_folder=tmp_dir) + repo_owner, repo_name = su.get_repo_info(repo_folder=repo_dir) assert repo_owner == expected_owner assert repo_name == expected_repo @@ -340,25 +340,22 @@ def test_get_repo_info(tmp_dir): with open(git_folder / "config", "w") as f: config.write(f) - repo_info = su.get_repo_info(repo_folder=tmp_dir) + repo_info = su.get_repo_info(repo_folder=repo_dir) assert repo_info == ("", "") -def test_get_changelog(tmp_dir): +def test_get_changelog(repo_dir): """Test if the function returns the changelog correctly.""" # Create a dummy directory with a changelog file - dummy_repo = tmp_dir / "dummy_repo" - dummy_repo.mkdir(exist_ok=True, parents=True) - # Create a dummy changelog file and write some text in it - changelog_file = dummy_repo / "CHANGELOG.md" + changelog_file = repo_dir / "CHANGELOG.md" changelog_file.touch() changelog_file.write_text("# Changelog") # Test the function - changelog = su.get_changelog(repo_folder=dummy_repo) + changelog = su.get_changelog(repo_folder=repo_dir) assert changelog == ("", "# Changelog") diff --git a/tests/test_sepalwidgets/test_App.py b/tests/test_sepalwidgets/test_App.py index 28581c9e..6a517e67 100644 --- a/tests/test_sepalwidgets/test_App.py +++ b/tests/test_sepalwidgets/test_App.py @@ -134,35 +134,33 @@ def test_close_banner(app: sw.App) -> None: return -def test_version_card(tmp_dir) -> None: +def test_version_card(repo_dir) -> None: """Test the drawer of the app.""" # arrange app_version = "999.999.1" changelog_text = "# Changelog" - dummy_repo = tmp_dir / "dummy_repo" - dummy_repo.mkdir(exist_ok=True, parents=True) # Change current working directory to dummy repo - os.chdir(dummy_repo) + os.chdir(repo_dir) # Check that if there is no pyproject.toml file, the version card is not present - navigation_drawer = sw.NavDrawer([], repo_folder=dummy_repo) + navigation_drawer = sw.NavDrawer([], repo_folder=repo_dir) assert navigation_drawer.v_slots == [] # Create a pyproject.toml file and a changelog - pyproject_file = dummy_repo / "pyproject.toml" + pyproject_file = repo_dir / "pyproject.toml" # create a temporary pyproject.toml file with open(pyproject_file, "w") as f: f.write(f"[project]\nversion = '{app_version}'") # Create a dummy changelog file and write some text in it - changelog_file = dummy_repo / "CHANGELOG.md" + changelog_file = repo_dir / "CHANGELOG.md" changelog_file.touch() changelog_file.write_text(f"{changelog_text}") - navigation_drawer = sw.NavDrawer([], repo_folder=dummy_repo) + navigation_drawer = sw.NavDrawer([], repo_folder=repo_dir) # Check if the version card is present From f25905f0f136636468662e0abc127b6bde14cd8d Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Fri, 24 Nov 2023 12:11:22 +0100 Subject: [PATCH 19/19] refactor(VersionCard): use color.main on version card --- sepal_ui/sepalwidgets/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sepal_ui/sepalwidgets/app.py b/sepal_ui/sepalwidgets/app.py index c851f87e..893fec86 100644 --- a/sepal_ui/sepalwidgets/app.py +++ b/sepal_ui/sepalwidgets/app.py @@ -810,6 +810,7 @@ def VersionCard(repo_folder: str = Path.cwd()) -> Optional[v.Card]: w_version = v.Card( class_="text-center", tile=True, + color=color.main, children=[ v.CardText( children=[