# -*- coding: utf-8 -*- # This code is part of Qiskit. # # (C) Copyright IBM 2017, 2019. # # 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. # pylint: disable=protected-access """Interactive Jobs widget.""" import datetime from typing import Union import ipywidgets as wid import plotly.graph_objects as go from qiskit.test.mock.fake_backend import FakeBackend from qiskit.providers.ibmq.ibmqbackend import IBMQBackend from ..ibmqbackend import IBMQBackend from ..visualization.interactive.plotly_wrapper import PlotlyWidget MONTH_NAMES = {1: 'Jan.', 2: 'Feb.', 3: 'Mar.', 4: 'Apr.', 5: 'May', 6: 'June', 7: 'July', 8: 'Aug.', 9: 'Sept.', 10: 'Oct.', 11: 'Nov.', 12: 'Dec.' } def _title_builder(sel_dict: dict) -> str: """Build the title string for the jobs table. Args: sel_dict: Dictionary containing information on jobs. Returns: HTML string for title. """ if 'day' not in sel_dict.keys(): title_str = 'Jobs in {mon} {yr} ({num})'.format(mon=MONTH_NAMES[sel_dict['month']], yr=sel_dict['year'], num=len(sel_dict['jobs'])) else: title_str = 'Jobs on {day} {mon} {yr} ({num})'.format(day=sel_dict['day'], mon=MONTH_NAMES[sel_dict['month']], yr=sel_dict['year'], num=len(sel_dict['jobs'])) return "<h4>{}</h4>".format(title_str) def _job_table_builder(sel_dict: dict) -> str: """Build the job table. Args: sel_dict: Dictionary containing information on jobs. Returns: HTML string for job table. """ table_html = "<table>" table_html += """<style> table { width: auto !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>""" table_html += "<tr><th>Date</th><th>Job ID / Name</th><th>Status</th></tr>" table_footer = "</table>" for jdata in sel_dict['jobs']: date_str = jdata[0].strftime("%H:%M %Z [%d/%b]") _temp_str = "<td>{time}</td><td>{jid}</td><td>{status}</td></tr>" # job has a name if jdata[2]: name = "{name} [{jid}]".format(name=jdata[2], jid=jdata[1]) else: name = jdata[1] table_html += _temp_str.format(time=date_str, jid=name, status=jdata[3]) table_html += table_footer return table_html def _job_summary(backend: Union[IBMQBackend, FakeBackend]) -> PlotlyWidget: """Interactive jobs summary for a backend. Args: backend: Display jobs summary for this backend. Returns: A figure for the rendered job summary. """ now = datetime.datetime.now() past_year_date = now - datetime.timedelta(days=365) date_filter = {'creationDate': {'gt': past_year_date.isoformat()}} jobs = backend.jobs(limit=None, db_filter=date_filter) num_jobs = len(jobs) main_str = "<b>Total Jobs</b><br>{}".format(num_jobs) jobs_dates = {} for job in jobs: _date = job.creation_date() _year = _date.year _id = job.job_id() _name = job.name() _status = job.status().name if _year not in jobs_dates.keys(): jobs_dates[_year] = {} _month = _date.month if _month not in jobs_dates[_year].keys(): jobs_dates[_year][_month] = {} _day = _date.day if _day not in jobs_dates[_year][_month].keys(): jobs_dates[_year][_month][_day] = [] jobs_dates[_year][_month][_day].append([_date, _id, _name, _status]) else: jobs_dates[_year][_month][_day].append([_date, _id, _name, _status]) labels = [main_str] parents = [""] values = [num_jobs] colors = ['#003f5c', '#ffa600', '#2f4b7c', '#f95d6a', '#665191', '#ff7c43', '#a05195', '#d45087'] num_colors = len(colors) wedge_colors = ["#FFFFFF"] month_counter = 0 index_jobs = [{}] for yr_key, yr_dict in jobs_dates.items(): # Do the months for key, val in yr_dict.items(): # Adding the months here. So the total # jobs to include must include all the jobs # in all the days in that month. month_dict = {'year': yr_key, 'month': key, 'jobs': []} total_jobs_month = 0 for day in val.keys(): for jjj in val[day]: total_jobs_month += 1 month_dict['jobs'].append(jjj) # add the month jobs to the index index_jobs.append(month_dict) # Set the label to the year month_label = "{mon} {yr}".format(mon=MONTH_NAMES[int(key)], yr=yr_key) labels.append(month_label) # Set the parents to the main str parents.append(main_str) # Add to the total jobs in that year to values values.append(total_jobs_month) wedge_colors.append(colors[month_counter % num_colors]) month_counter += 1 # Do the days day_counter = 0 for day_num, day_jobs in val.items(): day_dict = {'year': yr_key, 'month': key, 'day': day_num, 'jobs': []} day_dict['jobs'].extend(day_jobs) index_jobs.append(day_dict) _day_num = str(day_num) if _day_num[0] == '0': _day_num = _day_num[1:] if _day_num[-1] == '1': if _day_num[0] != '1' and len(_day_num) == 1: _day_num = _day_num+'st' else: _day_num = _day_num+'th' elif _day_num[-1] == '2': if _day_num[0] != '1' and len(_day_num) == 1: _day_num = _day_num+'nd' else: _day_num = _day_num+'th' elif _day_num[-1] == '3': if _day_num[0] != '1' and len(_day_num) == 1: _day_num = _day_num+'rd' else: _day_num = _day_num+'th' else: _day_num = _day_num+'th' labels.append(_day_num) parents.append(month_label) values.append(len(day_jobs)) wedge_colors.append(colors[day_counter % num_colors]) day_counter += 1 wedge_str = "<b>{label}</b><br><b>{value} Jobs</b>" hover_text = [None]+[wedge_str.format(label=labels[kk], value=values[kk]) for kk in range(1, len(labels))] fig = go.Figure(go.Sunburst(labels=labels, parents=parents, values=values, branchvalues="total", textfont=dict(size=18), outsidetextfont=dict(size=20), maxdepth=2, hoverinfo="text", hovertext=hover_text, marker=dict(colors=wedge_colors), ) ) fig.update_layout(margin=dict(t=10, l=10, r=10, b=10)) sun_wid = PlotlyWidget(fig) sun_wid._active = 0 sun_wid._job_index = index_jobs def callback(trace, selection, _): # pylint: disable=unused-argument idx = selection.point_inds[0] if idx != sun_wid._active: if idx: sun_wid._title.value = _title_builder(sun_wid._job_index[idx]) sun_wid._table.value = _job_table_builder(sun_wid._job_index[idx]) else: sun_wid._title.value = '<h4>Click graph to display jobs</h4>' sun_wid._table.value = '' sun_wid._active = idx sun = sun_wid.data[0] sun.on_click(callback) return sun_wid def jobs_tab(backend: Union[IBMQBackend, FakeBackend]) -> wid.HBox: """Construct a widget containing job information for an input backend. Args: backend: Input backend. Returns: An widget containing job summary. """ title = wid.HTML('<h4>Click graph to display jobs</h4>') table = wid.HTML('', layout=wid.Layout(max_height='500px', height='500px', width='100%', overflow='hidden scroll',)) sun_wid = _job_summary(backend) sun_wid._table = table sun_wid._title = title left = wid.Box(children=[sun_wid], layout=wid.Layout(width='40%', overflow='hidden hidden')) right = wid.VBox(children=[title, table], layout=wid.Layout(width='60%', overflow='hidden hidden')) out = wid.HBox(children=[left, right], layout=wid.Layout(max_height='500px', margin='10px')) return out