quantum-serverless/client/qiskit_serverless/visualizaiton/widget.py

330 lines
10 KiB
Python

# This code is a Qiskit project.
#
# (C) Copyright IBM 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
===========================================================
Decorators (:mod:`qiskit_serverless.visualization.widget`)
===========================================================
.. currentmodule:: qiskit_serverless.visualization.widget
Qiskit Serverless widgets
==========================
.. autosummary::
:toctree: ../stubs/
Widget
"""
import os
from datetime import datetime
from IPython.display import display, clear_output
from ipywidgets import GridspecLayout, widgets, Layout
from qiskit_serverless.exception import QiskitServerlessException
TABLE_STYLE = """
<style>
table {
width: 100% !important;
font-family:IBM Plex Sans, Arial, sans-serif !important;
}
th, td {
text-align: left !important;
padding: 5px !important;
}
tr:nth-child(even) {background-color: #f6f6f6 !important;}
</style>
"""
class Widget: # pylint: disable=too-many-instance-attributes
"""Widget for displaying information related to provider."""
def __init__(self, provider):
"""Constructor for widget.
Args:
provider: provider
"""
if provider is None:
raise QiskitServerlessException(
"Provider must be set in order to display widget."
)
self.provider = provider
self.job_offset = 0
self.job_limit = 10
self.jobs = self.provider.get_jobs()
self.job_list_view = widgets.Output()
with self.job_list_view:
display(self.render_job_list())
self.job_pagination_view = widgets.Output()
with self.job_pagination_view:
display(self.render_job_pagination())
self.program_offset = 0
self.program_limit = 10
self.programs = self.provider.get_programs()
self.program_list_view = widgets.Output()
with self.program_list_view:
display(self.render_program_list())
self.program_pagination_view = widgets.Output()
with self.program_pagination_view:
display(self.render_program_pagination())
self.information_view = widgets.Output()
with self.information_view:
display(self.render_information())
def render_job_list(self):
"""Renders list of jobs."""
def render_html_row(job):
title = job.raw_data.get("program", {}).get("title", "Job")
status = job.raw_data.get("status", "").lower()
date = datetime.strptime(
job.raw_data.get("created", "2011-11-11T11:11:11.000Z"),
"%Y-%m-%dT%H:%M:%S.%fZ",
).strftime("%m/%d/%Y")
status_style_map = {"succeeded": "color:green;", "failed": "color:red;"}
return f"""
<tr>
<td>{job.job_id}</td>
<td>{title}</td>
<td style={status_style_map.get(status, "")}>{status}</td>
<td>{date}</td>
</tr>
"""
rows = "\n".join([render_html_row(job) for job in self.jobs])
table = f"""
<table>
{TABLE_STYLE}
<tr>
<th>ID</th>
<th>Title</th>
<th>Status</th>
<th>Creation date</th>
</tr>
{rows}
</table>
"""
return widgets.HTML(table)
def render_program_list(self):
"""Renders list of jobs."""
def render_program_row(program):
title = program.raw_data.get("title", "QiskitFunction")
date = datetime.strptime(
program.raw_data.get("created", "2011-11-11T11:11:11.000Z"),
"%Y-%m-%dT%H:%M:%S.%fZ",
).strftime("%m/%d/%Y")
return f"""
<tr>
<td>{title}</td>
<td>{date}</td>
</tr>
"""
rows = "\n".join([render_program_row(program) for program in self.programs])
table = f"""
<table>
{TABLE_STYLE}
<tr>
<th>Title</th>
<th>Creation date</th>
</tr>
{rows}
</table>
"""
return widgets.HTML(table)
def render_job_pagination(self):
"""Renders pagination."""
def paginate(page_button):
"""Handles pagination callback logic."""
if page_button.tooltip == "prev":
self.jobs = self.provider.get_jobs(
limit=self.job_limit, offset=self.job_offset - self.job_limit
)
self.job_offset = self.job_offset - self.job_limit
elif page_button.tooltip == "next":
self.jobs = self.provider.get_jobs(
limit=self.job_limit, offset=self.job_offset + self.job_limit
)
self.job_offset = self.job_offset + self.job_limit
with self.job_list_view:
clear_output()
display(self.render_job_list())
with self.job_pagination_view:
clear_output()
display(self.render_job_pagination())
prev_page = widgets.Button(
description="Prev",
disabled=self.job_offset < 1,
button_style="",
tooltip="prev",
icon="arrow-circle-left",
)
prev_page.on_click(paginate)
prev_page.layout = Layout(width="33%")
next_page = widgets.Button(
description="Next",
disabled=len(self.jobs) != self.job_limit,
button_style="",
tooltip="next",
icon="arrow-circle-right",
)
next_page.on_click(paginate)
next_page.layout = Layout(width="33%")
pagination_number = widgets.Button(
description=f"{self.job_offset}-{self.job_offset + self.job_limit}",
disabled=True,
button_style="",
tooltip="items",
icon="list",
)
pagination_number.layout = Layout(width="33%")
return widgets.HBox([prev_page, pagination_number, next_page])
def render_program_pagination(self):
"""Renders pagination."""
def paginate(page_button):
"""Handles pagination callback logic."""
if page_button.tooltip == "prev":
self.programs = self.provider.get_programs(
limit=self.program_limit,
offset=self.program_offset - self.program_limit,
)
self.job_offset = self.program_offset - self.job_limit
elif page_button.tooltip == "next":
self.jobs = self.provider.get_jobs(
limit=self.program_limit,
offset=self.program_offset + self.program_limit,
)
self.program_offset = self.program_offset + self.program_limit
with self.program_list_view:
clear_output()
display(self.render_program_list())
with self.program_pagination_view:
clear_output()
display(self.render_program_pagination())
prev_page = widgets.Button(
description="Prev",
disabled=self.program_offset < 1,
button_style="",
tooltip="prev",
icon="arrow-circle-left",
)
prev_page.on_click(paginate)
prev_page.layout = Layout(width="33%")
next_page = widgets.Button(
description="Next",
disabled=len(self.programs) != self.program_limit,
button_style="",
tooltip="next",
icon="arrow-circle-right",
)
next_page.on_click(paginate)
next_page.layout = Layout(width="33%")
pagination_number = widgets.Button(
description=f"{self.program_offset}-{self.program_offset + self.program_limit}",
disabled=True,
button_style="",
tooltip="items",
icon="list",
)
pagination_number.layout = Layout(width="33%")
return widgets.HBox([prev_page, pagination_number, next_page])
def header_view(self):
"""Renders header of widget."""
return widgets.Button(
description=f"QiskitServerless [{self.provider.name}]",
button_style="info",
layout=Layout(height="auto", width="auto"),
disabled=True,
)
def render_information(self):
"""Renders information widget."""
client_version = "Unknown"
version_file_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "..", "VERSION.txt"
)
if os.path.exists(version_file_path):
with open(version_file_path, "r", encoding="utf-8") as version_file:
client_version = version_file.read().strip()
table = f"""
<table>
{TABLE_STYLE}
<tr>
<th>Parameter</th>
<th>Value</th>
</tr>
<tr>
<td>Client</td>
<td>{self.provider.name}</td>
</tr>
<tr>
<td>Software version</td>
<td>{client_version}</td>
</tr>
</table>
"""
return widgets.HTML(table)
def show(self):
"""Displays widget."""
job_list_widget = GridspecLayout(10, 2)
job_list_widget[:9, :] = self.job_list_view
job_list_widget[9:, 1] = self.job_pagination_view
program_list_widget = GridspecLayout(10, 2)
program_list_widget[:9, :] = self.program_list_view
program_list_widget[9:, 1] = self.program_pagination_view
tab_nest = widgets.Tab()
tab_nest.children = [
job_list_widget,
program_list_widget,
self.render_information(),
]
tab_nest.set_title(0, "Jobs")
tab_nest.set_title(1, "Functions")
tab_nest.set_title(2, "Info")
return tab_nest