#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2020 FABRIC Testbed
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Author: Komal Thareja(kthare10@renci.org)
import os
import tarfile
import ipywidgets as widgets
from IPython.display import display
from fabrictestbed_extensions.fablib.constants import Constants
from fabrictestbed_extensions.fablib.fablib import FablibManager
[docs]
class ArtifactManagerUI:
"""
A UI class for managing and interacting with artifacts using the FablibManager.
Attributes
----------
download_dir : str
The directory where artifacts will be downloaded.
fablib : FablibManager
Instance of FablibManager for interacting with artifacts.
artifacts : list
List of artifacts retrieved from FablibManager.
error_output : widgets.Output
Widget to display error messages.
title_filter : widgets.Text
Text input widget for filtering artifacts by title.
tag_filter : widgets.Text
Text input widget for filtering artifacts by tags.
project_filter : widgets.Text
Text input widget for filtering artifacts by project.
grid : widgets.GridBox
GridBox widget displaying the artifact information.
"""
def __init__(self, fablib: FablibManager, download_dir: str = "/home/fabric/work"):
"""
Initializes the ArtifactManagerUI with a specified download directory.
Parameters
----------
download_dir : str, optional
The directory where artifacts will be downloaded (default is "/home/fabric/work").
"""
self.download_dir = download_dir
self.fablib = fablib
self.artifacts = self.fablib.get_manager().list_artifacts()
self.error_output = widgets.Output()
# Initialize title and tag filters
self.title_filter = widgets.Text(
description="Title:",
placeholder="Enter title keyword",
layout=widgets.Layout(width="50%"),
)
self.tag_filter = widgets.Text(
description="Tag:",
placeholder="Enter tag keyword",
layout=widgets.Layout(width="50%"),
)
self.title_filter.observe(self.filter_artifacts, names="value")
self.tag_filter.observe(self.filter_artifacts, names="value")
# Initialize grid as None
self.grid = None
[docs]
def download_artifact_handler(
self, artifact_title: str, version_urn: str, version: str
):
"""
Handler for downloading and extracting artifacts.
Parameters
----------
artifact_title : str
The title of the artifact to download.
version_urn : str
The URN of the version to download.
version : str
The version of the artifact to download.
Raises
------
Exception
If there is an error during the download or extraction process.
"""
with self.error_output:
try:
self.error_output.clear_output() # Clear any previous error messages
location = self.fablib.download_artifact(
download_dir=self.download_dir,
version_urn=version_urn,
version=version,
)
print(
f"Artifact '{artifact_title}' version: '{version}' downloaded to {location} successfully!"
)
# Extract the tar file at the same location
if tarfile.is_tarfile(location):
with tarfile.open(location, "r:*") as tar:
extract_path = os.path.dirname(location)
tar.extractall(path=extract_path)
print(
f"Artifact '{artifact_title}' version: '{version}' extracted to {extract_path} successfully!"
)
os.remove(location)
print(f"Removed tar file: {location}")
else:
print(f"Downloaded file '{location}' is not a valid tar file.")
except Exception as e:
print(f"Failed to download artifact: {e}")
[docs]
def filter_artifacts(self, change):
"""
Filters artifacts based on the title, tag or project filter text input.
Parameters
----------
change : dict
The change dictionary containing the new value of the text input.
"""
filter_text = self.title_filter.value.lower()
filtered_artifacts = [
artifact
for artifact in self.artifacts
if filter_text in artifact["title"].lower()
]
tag_text = self.tag_filter.value.lower()
filtered_artifacts = [
artifact
for artifact in filtered_artifacts
if tag_text in ", ".join(artifact["tags"])
]
self.update_ui(filtered_artifacts)
[docs]
def update_ui(self, artifacts):
"""
Updates the UI with the given list of artifacts.
Parameters
----------
artifacts : list
The list of artifacts to be displayed in the UI.
"""
# Clear existing grid content, if any
if self.grid is not None:
self.grid.children = (
[]
) # Clear the grid's content without removing the widget itself
# Define table headers
headers = [
widgets.HTML(value="<b>Title</b>"),
widgets.HTML(value="<b>Description</b>"),
widgets.HTML(value="<b>Tags</b>"),
widgets.HTML(value="<b>Versions</b>"),
widgets.HTML(value="<b>Download</b>"),
widgets.HTML(value="<b>Project</b>"),
widgets.HTML(value="<b>Authors</b>"),
]
for h in headers:
h.style.text_align = "center"
h.style.text_color = Constants.FABRIC_WHITE
h.style.background = Constants.FABRIC_PRIMARY
# Prepare grid items
grid_items = headers.copy()
# Build interactive UI for each artifact
for artifact in artifacts:
title_label = widgets.HTML(value=artifact["title"])
authors_label = widgets.HTML(
value=", ".join([author["name"] for author in artifact["authors"]])
)
description_label = widgets.HTML(value=artifact["description_short"])
tags_label = widgets.HTML(value=", ".join(artifact["tags"]))
project_label = widgets.HTML(value=artifact.get("project_name", "N/A"))
# Version dropdown list
version_options = [
(f"v{version['version']}", version["urn"])
for version in artifact["versions"]
]
version_dropdown = widgets.Dropdown(
options=version_options,
description="",
layout=widgets.Layout(width="auto"),
)
# Download button
download_button = widgets.Button(
description="Download",
tooltip="Download selected version",
layout=widgets.Layout(width="auto"),
)
download_button.style.button_color = Constants.FABRIC_PRIMARY_LIGHT
# Bind the download handler
download_button.on_click(
lambda b, artifact_title=artifact[
"title"
], version_dropdown=version_dropdown: self.download_artifact_handler(
artifact_title, version_dropdown.value, version_dropdown.label
)
)
# Append artifact details and controls to grid items
grid_items.extend(
[
title_label,
description_label,
tags_label,
version_dropdown,
download_button,
project_label,
authors_label,
]
)
# Create and display the grid layout
if self.grid is None:
self.grid = widgets.GridBox(
grid_items,
layout=widgets.Layout(
grid_template_columns="20% 30% 10% 10% 10% 10% 10%",
grid_gap="10px",
align_items="center",
),
)
else:
self.grid.children = grid_items
# Display the filter and the grid
display(widgets.HBox([self.title_filter, self.tag_filter]))
display(self.grid)
display(self.error_output)
[docs]
def create_ui(self):
"""
Creates and displays the user interface for interacting with artifacts.
"""
self.update_ui(self.artifacts)
if __name__ == "__main__":
# Create an instance of the class and render the UI
artifact_manager_ui = ArtifactManagerUI(fablib=FablibManager())
artifact_manager_ui.create_ui()