From ae2a89b56efadfbb42bd517c3f41e55d1b14fbee Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:15:46 +0300 Subject: [PATCH 01/17] move to utils --- src/utils/formatMetricValue.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/utils/formatMetricValue.js diff --git a/src/utils/formatMetricValue.js b/src/utils/formatMetricValue.js new file mode 100644 index 000000000000..484d434307cb --- /dev/null +++ b/src/utils/formatMetricValue.js @@ -0,0 +1,12 @@ +export const formatMetricValue = (value, unit) => { + if (value === null || value === undefined) return 'N/A'; + + if (unit === 's') { + // Convert seconds to appropriate unit + return `${(value * 1e6).toFixed(2)}μs`; + } else if (unit === '' || unit === '%') { + // Assume percentage/fidelity + return `${(value * 100).toFixed(2)}%`; + } + return value.toFixed(3); +}; \ No newline at end of file From b63fb2a5ab7a21ed6b6de0e1dcecf2193c5c9034 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:16:00 +0300 Subject: [PATCH 02/17] make into its own component --- .../StatusModal/CalibrationTable.jsx | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/components/StatusModal/CalibrationTable.jsx diff --git a/src/components/StatusModal/CalibrationTable.jsx b/src/components/StatusModal/CalibrationTable.jsx new file mode 100644 index 000000000000..d7e1629b0074 --- /dev/null +++ b/src/components/StatusModal/CalibrationTable.jsx @@ -0,0 +1,73 @@ +import React from 'react' +import { CTable } from '@cscfi/csc-ui-react'; +import { formatMetricValue } from '../../utils/formatMetricValue'; + +// Render calibration data as a table +export const CalibrationTable = (props) => { + + const { calibrationData, qubitSwitch, couplerSwitch, qubitMetricOptions, couplerMetricOptions } = props; + + if (!calibrationData) return

No calibration data available

; + + const allMetrics = Object.keys(calibrationData); + if (allMetrics.length === 0) return

No metrics available

; + + // Get all unique qubit/coupler IDs + const allIds = new Set(); + allMetrics.forEach(metric => { + Object.keys(calibrationData[metric]).forEach(id => { + if (id !== 'statistics' && ((qubitSwitch && !id.includes("__")) || (couplerSwitch && id.includes("__")))) { + allIds.add(id); + } + }); + }); + + const sortedIds = Array.from(allIds); + + // Filter out metrics that have only N/A values for the filtered IDs + const metrics = allMetrics.filter(metric => { + return sortedIds.some(id => { + const data = calibrationData[metric][id]; + const value = data?.value; + return value !== null && value !== undefined; + }); + }); + + return ( +
+ + + + + + {metrics.map(metric => ( + + ))} + + + + {sortedIds.map(id => ( + + + {metrics.map(metric => { + const data = calibrationData[metric][id]; + const value = data?.value; + const unit = data?.unit || ''; + return ( + + ); + })} + + ))} + +
ID + {qubitMetricOptions.find(m => m.value === metric)?.name || + couplerMetricOptions.find(m => m.value === metric)?.name || + metric} +
{id} + {value !== null && value !== undefined ? formatMetricValue(value, unit) : 'N/A'} +
+
+
+ ); +}; From 33293cb8931fc57c02c71c327a5a712e72ead39d Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:16:18 +0300 Subject: [PATCH 03/17] add device info hook --- src/hooks/useDeviceInfo.jsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/hooks/useDeviceInfo.jsx diff --git a/src/hooks/useDeviceInfo.jsx b/src/hooks/useDeviceInfo.jsx new file mode 100644 index 000000000000..cf0d324dc5f2 --- /dev/null +++ b/src/hooks/useDeviceInfo.jsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react' + +export const useDeviceInfo = (deviceInfoUrl) => { + const [deviceInfo, setDeviceInfo] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchDeviceInfo = async () => { + const url = deviceInfoUrl; + try { + const resp = await fetch (url); + const result = await resp.json(); + setDeviceInfo(result?.data || null); + } catch (err) { + console.error(err); + setError(err); + } + } + + fetchDeviceInfo(); +}, [deviceInfoUrl]); + + return { deviceInfo, error }; +} From 8b8c33d41a37c5337657b53491e1b74635cf8367 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:16:41 +0300 Subject: [PATCH 04/17] import formatMetricValue from utils --- src/components/QcLayouts/Base.jsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/QcLayouts/Base.jsx b/src/components/QcLayouts/Base.jsx index 21bc5a9d6734..627d098b2073 100644 --- a/src/components/QcLayouts/Base.jsx +++ b/src/components/QcLayouts/Base.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { motion } from 'framer-motion'; import { getColorForMetricValue } from '../../utils/generateGradient'; +import { formatMetricValue } from '../../utils/formatMetricValue'; export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitMetric, couplerMetric, qubitMetricFormatted, couplerMetricFormatted, thresholdQubit, thresholdCoupler }) { @@ -52,21 +53,6 @@ export function BaseQcLayout({ rawNodes, edges, spacing, calibrationData, qubitM return { worst, best, average }; }; - // Format metric value for display - const formatMetricValue = (value, unit) => { - if (value === null || value === undefined) return 'N/A'; - - if (unit === 's') { - // Convert seconds to appropriate unit - return `${(value * 1e6).toFixed(2)}μs`; - } else if (unit === '' || unit === '%') { - // Assume percentage/fidelity - return `${(value * 100).toFixed(2)}%`; - } - return value.toFixed(3); - }; - - // Get metric value for a specific qubit const getQubitMetricValue = (qubitId) => { if (!calibrationData || !qubitMetric || !calibrationData[qubitMetric]) { From c4732457ca0a1f065fc57c2ec4dd8cd5e33eac34 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:16:51 +0300 Subject: [PATCH 05/17] use device info hook --- src/components/StatusOverview.jsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/StatusOverview.jsx b/src/components/StatusOverview.jsx index 3c2672b7179f..4d04d4a4546a 100644 --- a/src/components/StatusOverview.jsx +++ b/src/components/StatusOverview.jsx @@ -1,5 +1,6 @@ import React from 'react' + const parseResultMedian = (data, unit) => { if (unit === '') { return (Number.parseFloat(data?.median) * 100).toFixed(2); @@ -14,10 +15,15 @@ export const Overview = (props) => { const calibrationData = props.calibrationData; + const deviceInfoData = props.deviceInfoData; + + + const limitationsData = deviceInfoData?.job_policy || {}; + const limitations = { - "Max circuits per batch": "200", - "Max shots per job": "100000", - "Max jobs in queue": "500" + "Max circuits per batch": limitationsData?.max_number_circuits_per_batch || "-", + "Max shots per job": limitationsData?.max_number_shots_per_job || "-", + "Max jobs in queue": limitationsData?.max_queue_length || "-" } const qualityMetricsSingle = { From cb26c87ffc62f1453af7458ea2eb060a6e26c8f9 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:17:02 +0300 Subject: [PATCH 06/17] add raw data table view --- src/components/StatusModalConent.jsx | 703 +++++++++++++++++---------- 1 file changed, 444 insertions(+), 259 deletions(-) diff --git a/src/components/StatusModalConent.jsx b/src/components/StatusModalConent.jsx index e28b25107de2..dc800ce83dba 100644 --- a/src/components/StatusModalConent.jsx +++ b/src/components/StatusModalConent.jsx @@ -1,15 +1,23 @@ import React from 'react' import { useState, useEffect } from 'react' import { useCalibration } from '../hooks/useCalibration'; +import { useDeviceInfo } from '../hooks/useDeviceInfo'; import { HelmiLayout } from './QcLayouts/Helmi'; import { Q50Layout } from './QcLayouts/Q50'; import { Overview } from './StatusOverview'; +import { CalibrationTable } from './StatusModal/CalibrationTable'; import { generateMetricGradient } from '../utils/generateGradient'; -import { CCard, CCardTitle, CCardContent, CCardActions, CButton, CTabs, CTab, CTabItems, CTabItem, CSelect } from '@cscfi/csc-ui-react'; +import { formatMetricValue } from '../utils/formatMetricValue'; +import { + CCard, CCardTitle, CCardContent, CCardActions, CButton, CTabs, + CTab, CTabItems, CTabItem, CSelect, CRadioGroup, CSwitch, CTable +} from '@cscfi/csc-ui-react'; export const ModalContent = (props) => { - const { calibrationData: calibrationDataAll, error } = useCalibration(`https://fiqci-backend.2.rahtiapp.fi/device/${props.device_id.toLowerCase()}/calibration`) + + const { calibrationData: calibrationDataAll, calibrationError } = useCalibration(`https://fiqci-backend.2.rahtiapp.fi/device/${props.device_id.toLowerCase()}/calibration`) + const { deviceInfo: deviceInfoData, infoError } = useDeviceInfo(`https://fiqci-backend.2.rahtiapp.fi/device/${props.device_id.toLowerCase()}`) const [activeTab, setActiveTab] = useState('overview'); const [qubitMetric, setQubitMetric] = useState(''); @@ -20,10 +28,46 @@ export const ModalContent = (props) => { const [thresholdQubitValue, setThresholdQubitValue] = useState(0.0); const [qubitInputValue, setQubitInputValue] = useState(''); const [couplerInputValue, setCouplerInputValue] = useState(''); + const [rawDataType, setRawDataType] = useState({ name: 'Calibration Data', value: 'calibration_data' }); + const [copySuccess, setCopySuccess] = useState(false); + const [tableView, setTableView] = useState(false); + const [qubitSwitch, setQubitSwitch] = useState(true); + const [couplerSwitch, setCouplerSwitch] = useState(false); const calibrationData = calibrationDataAll.metrics const lastCalibrated = new Date(calibrationDataAll.quality_metric_set_end_timestamp) + // Get current raw data based on selection + const getCurrentRawData = () => { + return rawDataType.value === 'calibration_data' ? calibrationDataAll : deviceInfoData; + }; + + // Copy to clipboard function + const copyToClipboard = async () => { + try { + const data = JSON.stringify(getCurrentRawData(), null, 2); + await navigator.clipboard.writeText(data); + setCopySuccess(true); + setTimeout(() => setCopySuccess(false), 2000); + } catch (err) { + console.error('Failed to copy: ', err); + } + }; + + // Download as JSON file + const downloadRawData = () => { + const data = JSON.stringify(getCurrentRawData(), null, 2); + const blob = new Blob([data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${props.device_id.toLowerCase()}_${rawDataType.value}_${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + // Calculate threshold values when dependencies change useEffect(() => { if (qubitMetric && calibrationData && calibrationData[qubitMetric]) { @@ -35,11 +79,11 @@ export const ModalContent = (props) => { const range = parseFloat(best) - parseFloat(worst); const thresholdValue = parseFloat(worst) + (thresholdQubit * range); setThresholdQubitValue(thresholdValue); - + // Update input display value - const displayValue = qubitStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : - (qubitStats.unit === '' || qubitStats.unit === '%') ? (thresholdValue * 100).toFixed(2) : - thresholdValue.toFixed(3); + const displayValue = qubitStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : + (qubitStats.unit === '' || qubitStats.unit === '%') ? (thresholdValue * 100).toFixed(2) : + thresholdValue.toFixed(3); setQubitInputValue(displayValue); } } @@ -55,11 +99,11 @@ export const ModalContent = (props) => { const range = parseFloat(best) - parseFloat(worst); const thresholdValue = parseFloat(worst) + (thresholdCoupler * range); setThresholdCouplerValue(thresholdValue); - + // Update input display value - const displayValue = couplerStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : - (couplerStats.unit === '' || couplerStats.unit === '%') ? (thresholdValue * 100).toFixed(2) : - thresholdValue.toFixed(3); + const displayValue = couplerStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : + (couplerStats.unit === '' || couplerStats.unit === '%') ? (thresholdValue * 100).toFixed(2) : + thresholdValue.toFixed(3); setCouplerInputValue(displayValue); } } @@ -103,19 +147,72 @@ export const ModalContent = (props) => { unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' }; }; + + // Render calibration data as a table + const renderCalibrationTable = () => { + if (!calibrationData) return

No calibration data available

; - // Format metric value for display - const formatMetricValue = (value, unit) => { - if (value === null || value === undefined) return 'N/A'; + const allMetrics = Object.keys(calibrationData); + if (allMetrics.length === 0) return

No metrics available

; - if (unit === 's') { - // Convert seconds to appropriate unit - return `${(value * 1e6).toFixed(2)}μs`; - } else if (unit === '' || unit === '%') { - // Assume percentage/fidelity - return `${(value * 100).toFixed(2)}%`; - } - return value.toFixed(3); + // Get all unique qubit/coupler IDs + const allIds = new Set(); + allMetrics.forEach(metric => { + Object.keys(calibrationData[metric]).forEach(id => { + if (id !== 'statistics' && ((qubitSwitch && !id.includes("__")) || (couplerSwitch && id.includes("__")))) { + allIds.add(id); + } + }); + }); + + const sortedIds = Array.from(allIds); + + // Filter out metrics that have only N/A values for the filtered IDs + const metrics = allMetrics.filter(metric => { + return sortedIds.some(id => { + const data = calibrationData[metric][id]; + const value = data?.value; + return value !== null && value !== undefined; + }); + }); + + return ( +
+ + + + + + {metrics.map(metric => ( + + ))} + + + + {sortedIds.map(id => ( + + + {metrics.map(metric => { + const data = calibrationData[metric][id]; + const value = data?.value; + const unit = data?.unit || ''; + return ( + + ); + })} + + ))} + +
ID + {qubitMetricOptions.find(m => m.value === metric)?.name || + couplerMetricOptions.find(m => m.value === metric)?.name || + metric} +
{id} + {value !== null && value !== undefined ? formatMetricValue(value, unit) : 'N/A'} +
+
+
+ ); }; @@ -144,105 +241,105 @@ export const ModalContent = (props) => { )} - {(activeTab === "layout" || activeTab === "graphical") && + {(activeTab === "layout" || activeTab === "graphical") &&
-
-

Qubit Metric:

- { - setQubitMetric(e.detail || ''); - setThresholdQubit(0); - setQubitInputValue('0'); - }} - /> -
+
+

Qubit Metric:

+ { + setQubitMetric(e.detail || ''); + setThresholdQubit(0); + setQubitInputValue('0'); + }} + /> +
+ +
+ {(qubitMetric) && (() => { + const qubitStats = qubitMetric ? getMetricStatistics(qubitMetric) : null; + + return ( +
+ {qubitStats && ( +
+
+
+ { + setQubitInputValue(e.target.value); + const inputValue = parseFloat(e.target.value); + if (!isNaN(inputValue)) { + const isLowerBetter = qubitMetric.includes("error"); + const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + const best = isLowerBetter ? qubitStats.worst : qubitStats.best; + const range = parseFloat(best) - parseFloat(worst); + + // Convert display value back to raw value + let rawValue = inputValue; + if (qubitStats.unit === 's') { + rawValue = inputValue / 1e6; // Convert μs back to s + } else if (qubitStats.unit === '' || qubitStats.unit === '%') { + rawValue = inputValue / 100; // Convert % back to decimal + } + + // Calculate slider position (0-1) + const sliderValue = (rawValue - parseFloat(worst)) / range; + const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); -
- {(qubitMetric) && (() => { - const qubitStats = qubitMetric ? getMetricStatistics(qubitMetric) : null; - - return ( -
- {qubitStats && ( -
-
-
- { - setQubitInputValue(e.target.value); - const inputValue = parseFloat(e.target.value); - if (!isNaN(inputValue)) { + setThresholdQubit(clampedSliderValue); + setThresholdQubitValue(rawValue); + } + }} + /> + + {qubitStats.unit === 's' ? 'μs' : + (qubitStats.unit === '' || qubitStats.unit === '%') ? '%' : + qubitStats.unit} + +
+
+
{ const isLowerBetter = qubitMetric.includes("error"); - const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - const best = isLowerBetter ? qubitStats.worst : qubitStats.best; - const range = parseFloat(best) - parseFloat(worst); - - // Convert display value back to raw value - let rawValue = inputValue; - if (qubitStats.unit === 's') { - rawValue = inputValue / 1e6; // Convert μs back to s - } else if (qubitStats.unit === '' || qubitStats.unit === '%') { - rawValue = inputValue / 100; // Convert % back to decimal + let worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + let best = isLowerBetter ? qubitStats.worst : qubitStats.best; + const colors = generateMetricGradient(worst, best, qubitStats.average); + const gradientStops = []; + for (let i = 0; i <= 10; i++) { + const index = Math.floor((i / 10) * (colors.length - 1)); + const percentage = (i / 10) * 100; + gradientStops.push(`${colors[index]} ${percentage}%`); } - - // Calculate slider position (0-1) - const sliderValue = (rawValue - parseFloat(worst)) / range; - const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); - - setThresholdQubit(clampedSliderValue); - setThresholdQubitValue(rawValue); - } - }} - /> - - {qubitStats.unit === 's' ? 'μs' : - (qubitStats.unit === '' || qubitStats.unit === '%') ? '%' : - qubitStats.unit} - -
-
-
{ - const isLowerBetter = qubitMetric.includes("error"); - let worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - let best = isLowerBetter ? qubitStats.worst : qubitStats.best; - const colors = generateMetricGradient(worst, best, qubitStats.average); - const gradientStops = []; - for (let i = 0; i <= 10; i++) { - const index = Math.floor((i / 10) * (colors.length - 1)); - const percentage = (i / 10) * 100; - gradientStops.push(`${colors[index]} ${percentage}%`); - } - return `linear-gradient(to right, ${gradientStops.join(', ')})`; - })() - }}> - setThresholdQubit(parseFloat(e.target.value))} - className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" - style={{ - background: 'transparent', - appearance: 'none', - WebkitAppearance: 'none' - }} - /> - -
-
- - Worst:
- {(() => { - const isLowerBetter = qubitMetric.includes("error"); - const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - return formatMetricValue(worst, qubitStats.unit); - })()} -
- - Best:
- {(() => { - const isLowerBetter = qubitMetric.includes("error"); - const best = isLowerBetter ? qubitStats.worst : qubitStats.best; - return formatMetricValue(best, qubitStats.unit); - })()} -
-
+
+
+ + Worst:
+ {(() => { + const isLowerBetter = qubitMetric.includes("error"); + const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + return formatMetricValue(worst, qubitStats.unit); + })()} +
+ + Best:
+ {(() => { + const isLowerBetter = qubitMetric.includes("error"); + const best = isLowerBetter ? qubitStats.worst : qubitStats.best; + return formatMetricValue(best, qubitStats.unit); + })()} +
+
+
+ )}
- )} -
- ); - })()} -
+ ); + })()} +
-
-

Coupler Metric:

- { - setCouplerMetric(e.detail || ''); - setThresholdCoupler(0); - setCouplerInputValue('0'); - }} - /> -
+
+

Coupler Metric:

+ { + setCouplerMetric(e.detail || ''); + setThresholdCoupler(0); + setCouplerInputValue('0'); + }} + /> +
+ +
+ {(couplerMetric) && (() => { + const couplerStats = couplerMetric ? getMetricStatistics(couplerMetric) : null; -
- {(couplerMetric) && (() => { - const couplerStats = couplerMetric ? getMetricStatistics(couplerMetric) : null; - - return ( -
- - {couplerStats && ( -
-
-
- - { - setCouplerInputValue(e.target.value); - const inputValue = parseFloat(e.target.value); - if (!isNaN(inputValue)) { + return ( +
+ + {couplerStats && ( +
+
+
+ + { + setCouplerInputValue(e.target.value); + const inputValue = parseFloat(e.target.value); + if (!isNaN(inputValue)) { + const isLowerBetter = couplerMetric.includes("error"); + const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + const best = isLowerBetter ? couplerStats.worst : couplerStats.best; + const range = parseFloat(best) - parseFloat(worst); + + // Convert display value back to raw value + let rawValue = inputValue; + if (couplerStats.unit === 's') { + rawValue = inputValue / 1e6; // Convert μs back to s + } else if (couplerStats.unit === '' || couplerStats.unit === '%') { + rawValue = inputValue / 100; // Convert % back to decimal + } + + // Calculate slider position (0-1) + const sliderValue = (rawValue - parseFloat(worst)) / range; + const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); + + setThresholdCoupler(clampedSliderValue); + setThresholdCouplerValue(rawValue); + } + }} + /> + + {couplerStats.unit === 's' ? 'μs' : + (couplerStats.unit === '' || couplerStats.unit === '%') ? '%' : + couplerStats.unit} + +
+
+
{ + const isLowerBetter = couplerMetric.includes("error"); + let worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + let best = isLowerBetter ? couplerStats.worst : couplerStats.best; + const colors = generateMetricGradient(worst, best, couplerStats.average); + const gradientStops = []; + for (let i = 0; i <= 10; i++) { + const index = Math.floor((i / 10) * (colors.length - 1)); + const percentage = (i / 10) * 100; + gradientStops.push(`${colors[index]} ${percentage}%`); + } + return `linear-gradient(to right, ${gradientStops.join(', ')})`; + })() + }}> + setThresholdCoupler(parseFloat(e.target.value))} + className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" + style={{ + background: 'transparent', + appearance: 'none', + WebkitAppearance: 'none' + }} + /> +
+
+ + Worst:
+ {(() => { const isLowerBetter = couplerMetric.includes("error"); const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + return formatMetricValue(worst, couplerStats.unit); + })()} +
+ + + Best:
+ {(() => { + const isLowerBetter = couplerMetric.includes("error"); const best = isLowerBetter ? couplerStats.worst : couplerStats.best; - const range = parseFloat(best) - parseFloat(worst); - - // Convert display value back to raw value - let rawValue = inputValue; - if (couplerStats.unit === 's') { - rawValue = inputValue / 1e6; // Convert μs back to s - } else if (couplerStats.unit === '' || couplerStats.unit === '%') { - rawValue = inputValue / 100; // Convert % back to decimal - } - - // Calculate slider position (0-1) - const sliderValue = (rawValue - parseFloat(worst)) / range; - const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); - - setThresholdCoupler(clampedSliderValue); - setThresholdCouplerValue(rawValue); - } - }} - /> - - {couplerStats.unit === 's' ? 'μs' : - (couplerStats.unit === '' || couplerStats.unit === '%') ? '%' : - couplerStats.unit} - + return formatMetricValue(best, couplerStats.unit); + })()} +
+
-
-
{ - const isLowerBetter = couplerMetric.includes("error"); - let worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - let best = isLowerBetter ? couplerStats.worst : couplerStats.best; - const colors = generateMetricGradient(worst, best, couplerStats.average); - const gradientStops = []; - for (let i = 0; i <= 10; i++) { - const index = Math.floor((i / 10) * (colors.length - 1)); - const percentage = (i / 10) * 100; - gradientStops.push(`${colors[index]} ${percentage}%`); - } - return `linear-gradient(to right, ${gradientStops.join(', ')})`; - })() - }}> - setThresholdCoupler(parseFloat(e.target.value))} - className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" - style={{ - background: 'transparent', - appearance: 'none', - WebkitAppearance: 'none' - }} - /> -
-
- - Worst:
- {(() => { - const isLowerBetter = couplerMetric.includes("error"); - const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - return formatMetricValue(worst, couplerStats.unit); - })()} -
- - - Best:
- {(() => { - const isLowerBetter = couplerMetric.includes("error"); - const best = isLowerBetter ? couplerStats.worst : couplerStats.best; - return formatMetricValue(best, couplerStats.unit); - })()} -
-
+ )}
- )} -
- ); - })()} + ); + })()} +
+ +
+ } + {activeTab === "raw" && +
+

Data Type:

+ setRawDataType(e.detail)} + defaultValue={{ name: 'Calibration Data', value: 'calibration_data' }} + value={rawDataType} + inline={true} + > + + {rawDataType.value === 'calibration_data' && +
+ setTableView(e.detail)} + > + Table View + +
+ } + {tableView && rawDataType.value === 'calibration_data' && + <> +
+ { + setQubitSwitch(e.detail) + setCouplerSwitch(!e.detail) + }} + > + Qubits + +
+
+ { + setQubitSwitch(!e.detail) + setCouplerSwitch(e.detail) + }} + > + Couplers + +
+ + } +
+
+ + {copySuccess ? 'Copied!' : 'Copy'} + + + Download + +
} @@ -434,6 +604,8 @@ export const ModalContent = (props) => { @@ -469,9 +641,22 @@ export const ModalContent = (props) => {
-
-                                        {JSON.stringify(calibrationDataAll, null, 2)}
-                                    
+ {rawDataType.value === 'calibration_data' && tableView ? ( + + ) : ( +
+                                            {rawDataType.value === 'calibration_data' &&
+                                                JSON.stringify(calibrationDataAll, null, 2)}
+                                            {rawDataType.value === 'device_info' &&
+                                                JSON.stringify(deviceInfoData, null, 2)}
+                                        
+ )}
@@ -485,6 +670,6 @@ export const ModalContent = (props) => { props.setIsModalOpen(false)} text>Close
- + ) } \ No newline at end of file From cf18af6835216a8fd93f8856039364864b9e87bb Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:17:48 +0300 Subject: [PATCH 07/17] remove unused --- src/components/StatusModalConent.jsx | 69 ---------------------------- 1 file changed, 69 deletions(-) diff --git a/src/components/StatusModalConent.jsx b/src/components/StatusModalConent.jsx index dc800ce83dba..46254cbe66d4 100644 --- a/src/components/StatusModalConent.jsx +++ b/src/components/StatusModalConent.jsx @@ -147,75 +147,6 @@ export const ModalContent = (props) => { unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' }; }; - - // Render calibration data as a table - const renderCalibrationTable = () => { - if (!calibrationData) return

No calibration data available

; - - const allMetrics = Object.keys(calibrationData); - if (allMetrics.length === 0) return

No metrics available

; - - // Get all unique qubit/coupler IDs - const allIds = new Set(); - allMetrics.forEach(metric => { - Object.keys(calibrationData[metric]).forEach(id => { - if (id !== 'statistics' && ((qubitSwitch && !id.includes("__")) || (couplerSwitch && id.includes("__")))) { - allIds.add(id); - } - }); - }); - - const sortedIds = Array.from(allIds); - - // Filter out metrics that have only N/A values for the filtered IDs - const metrics = allMetrics.filter(metric => { - return sortedIds.some(id => { - const data = calibrationData[metric][id]; - const value = data?.value; - return value !== null && value !== undefined; - }); - }); - - return ( -
- - - - - - {metrics.map(metric => ( - - ))} - - - - {sortedIds.map(id => ( - - - {metrics.map(metric => { - const data = calibrationData[metric][id]; - const value = data?.value; - const unit = data?.unit || ''; - return ( - - ); - })} - - ))} - -
ID - {qubitMetricOptions.find(m => m.value === metric)?.name || - couplerMetricOptions.find(m => m.value === metric)?.name || - metric} -
{id} - {value !== null && value !== undefined ? formatMetricValue(value, unit) : 'N/A'} -
-
-
- ); - }; - - return ( From c1fc871ddc113ef863c088f432a1b08f1c13d97d Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:41:49 +0300 Subject: [PATCH 08/17] remove import --- src/components/QcLayouts/Base.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/QcLayouts/Base.jsx b/src/components/QcLayouts/Base.jsx index 627d098b2073..3bac0e6c31f0 100644 --- a/src/components/QcLayouts/Base.jsx +++ b/src/components/QcLayouts/Base.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useRef } from 'react'; import { motion } from 'framer-motion'; import { getColorForMetricValue } from '../../utils/generateGradient'; import { formatMetricValue } from '../../utils/formatMetricValue'; From 4c1d5000ed6d9edd9d712fde336f2f4c93512e60 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:42:03 +0300 Subject: [PATCH 09/17] refactor sidebar into its own component --- src/components/StatusModal/SideBar.jsx | 487 ++++++++++++++++++++++++ src/components/StatusModalConent.jsx | 496 ++----------------------- 2 files changed, 517 insertions(+), 466 deletions(-) create mode 100644 src/components/StatusModal/SideBar.jsx diff --git a/src/components/StatusModal/SideBar.jsx b/src/components/StatusModal/SideBar.jsx new file mode 100644 index 000000000000..1f59d9586bb1 --- /dev/null +++ b/src/components/StatusModal/SideBar.jsx @@ -0,0 +1,487 @@ +import React, { useState, useEffect } from 'react'; +import { CSelect, CRadioGroup, CSwitch, CButton } from '@cscfi/csc-ui-react'; +import { generateMetricGradient } from '../../utils/generateGradient'; +import { formatMetricValue } from '../../utils/formatMetricValue'; + +export const SideBar = (props) => { + + const [qubitInputValue, setQubitInputValue] = useState(''); + const [couplerInputValue, setCouplerInputValue] = useState(''); + + const [copySuccess, setCopySuccess] = useState(false); + + + const { activeTab, setThresholdCouplerValue, setThresholdQubitValue, + calibrationDataAll, deviceInfoData, devicesWithStatus, + qubitMetricOptions, couplerMetricOptions, tableView, setTableView, + qubitSwitch, setQubitSwitch, couplerSwitch, setCouplerSwitch, rawDataType, + setRawDataType, deviceData, qubitMetric, setQubitMetric, couplerMetric, setCouplerMetric, + thresholdCoupler, setThresholdCoupler, thresholdQubit, setThresholdQubit } = props; + + const calibrationData = calibrationDataAll.metrics + const lastCalibrated = new Date(calibrationDataAll.quality_metric_set_end_timestamp) + + // Get current raw data based on selection + const getCurrentRawData = () => { + return rawDataType.value === 'calibration_data' ? calibrationDataAll : deviceInfoData; + }; + + // Copy to clipboard function + const copyToClipboard = async () => { + try { + const data = JSON.stringify(getCurrentRawData(), null, 2); + await navigator.clipboard.writeText(data); + setCopySuccess(true); + setTimeout(() => setCopySuccess(false), 2000); + } catch (err) { + console.error('Failed to copy: ', err); + } + }; + + // Download as JSON file + const downloadRawData = () => { + const data = JSON.stringify(getCurrentRawData(), null, 2); + const blob = new Blob([data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${deviceData.device_id.toLowerCase()}_${rawDataType.value}_${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + // Calculate statistics for selected metric + const getMetricStatistics = (metric) => { + if (!calibrationData || !metric || !calibrationData[metric]) { + return null; + } + + const values = Object.values(calibrationData[metric]) + .map(item => item?.value) + .filter(value => value !== null && value !== undefined && !isNaN(value)); + + if (values.length === 0) return null; + + const sorted = values.sort((a, b) => a - b); + + return { + worst: sorted[0], + best: sorted[sorted.length - 1], + average: calibrationData[metric].statistics.average, + median: calibrationData[metric].statistics.median, + unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' + }; + }; + + + // Calculate threshold values when dependencies change + useEffect(() => { + if (qubitMetric && calibrationData && calibrationData[qubitMetric]) { + const qubitStats = getMetricStatistics(qubitMetric); + if (qubitStats) { + const isLowerBetter = qubitMetric.includes("error"); + const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + const best = isLowerBetter ? qubitStats.worst : qubitStats.best; + const range = parseFloat(best) - parseFloat(worst); + const thresholdValue = parseFloat(worst) + (thresholdQubit * range); + setThresholdQubitValue(thresholdValue); + + // Update input display value + const displayValue = qubitStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : + (qubitStats.unit === '' || qubitStats.unit === '%') ? (thresholdValue * 100).toFixed(2) : + thresholdValue.toFixed(3); + setQubitInputValue(displayValue); + } + } + }, [qubitMetric, thresholdQubit]); + + useEffect(() => { + if (couplerMetric && calibrationData && calibrationData[couplerMetric]) { + const couplerStats = getMetricStatistics(couplerMetric); + if (couplerStats) { + const isLowerBetter = couplerMetric.includes("error"); + const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + const best = isLowerBetter ? couplerStats.worst : couplerStats.best; + const range = parseFloat(best) - parseFloat(worst); + const thresholdValue = parseFloat(worst) + (thresholdCoupler * range); + setThresholdCouplerValue(thresholdValue); + + // Update input display value + const displayValue = couplerStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : + (couplerStats.unit === '' || couplerStats.unit === '%') ? (thresholdValue * 100).toFixed(2) : + thresholdValue.toFixed(3); + setCouplerInputValue(displayValue); + } + } + }, [couplerMetric, thresholdCoupler]); + + return ( +
+
+

Qubits: {deviceData.qubits}

+

Basis gates: {deviceData.basis}

+

Topology: {deviceData.topology}

+
+ +
+ Service status: + {devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.health ? ( +
+

Online

+
+ ) : ( +
+

Offline

+
+ )} +
+ {(activeTab === "layout" || activeTab === "graphical") && +
+
+
+

Qubit Metric:

+ { + setQubitMetric(e.detail || ''); + setThresholdQubit(0); + setQubitInputValue('0'); + }} + /> +
+ +
+ {(qubitMetric) && (() => { + const qubitStats = qubitMetric ? getMetricStatistics(qubitMetric) : null; + + return ( +
+ {qubitStats && ( +
+
+
+ { + setQubitInputValue(e.target.value); + const inputValue = parseFloat(e.target.value); + if (!isNaN(inputValue)) { + const isLowerBetter = qubitMetric.includes("error"); + const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + const best = isLowerBetter ? qubitStats.worst : qubitStats.best; + const range = parseFloat(best) - parseFloat(worst); + + // Convert display value back to raw value + let rawValue = inputValue; + if (qubitStats.unit === 's') { + rawValue = inputValue / 1e6; // Convert μs back to s + } else if (qubitStats.unit === '' || qubitStats.unit === '%') { + rawValue = inputValue / 100; // Convert % back to decimal + } + + // Calculate slider position (0-1) + const sliderValue = (rawValue - parseFloat(worst)) / range; + const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); + + setThresholdQubit(clampedSliderValue); + setThresholdQubitValue(rawValue); + } + }} + /> + + {qubitStats.unit === 's' ? 'μs' : + (qubitStats.unit === '' || qubitStats.unit === '%') ? '%' : + qubitStats.unit} + +
+
+
{ + const isLowerBetter = qubitMetric.includes("error"); + let worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + let best = isLowerBetter ? qubitStats.worst : qubitStats.best; + const colors = generateMetricGradient(worst, best, qubitStats.average); + const gradientStops = []; + for (let i = 0; i <= 10; i++) { + const index = Math.floor((i / 10) * (colors.length - 1)); + const percentage = (i / 10) * 100; + gradientStops.push(`${colors[index]} ${percentage}%`); + } + return `linear-gradient(to right, ${gradientStops.join(', ')})`; + })() + }}> + setThresholdQubit(parseFloat(e.target.value))} + className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" + style={{ + background: 'transparent', + appearance: 'none', + WebkitAppearance: 'none' + }} + /> + +
+
+ + Worst:
+ {(() => { + const isLowerBetter = qubitMetric.includes("error"); + const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + return formatMetricValue(worst, qubitStats.unit); + })()} +
+ + Best:
+ {(() => { + const isLowerBetter = qubitMetric.includes("error"); + const best = isLowerBetter ? qubitStats.worst : qubitStats.best; + return formatMetricValue(best, qubitStats.unit); + })()} +
+
+
+ )} +
+ ); + })()} +
+ +
+

Coupler Metric:

+ { + setCouplerMetric(e.detail || ''); + setThresholdCoupler(0); + setCouplerInputValue('0'); + }} + /> +
+ +
+ {(couplerMetric) && (() => { + const couplerStats = couplerMetric ? getMetricStatistics(couplerMetric) : null; + + return ( +
+ + {couplerStats && ( +
+
+
+ + { + setCouplerInputValue(e.target.value); + const inputValue = parseFloat(e.target.value); + if (!isNaN(inputValue)) { + const isLowerBetter = couplerMetric.includes("error"); + const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + const best = isLowerBetter ? couplerStats.worst : couplerStats.best; + const range = parseFloat(best) - parseFloat(worst); + + // Convert display value back to raw value + let rawValue = inputValue; + if (couplerStats.unit === 's') { + rawValue = inputValue / 1e6; // Convert μs back to s + } else if (couplerStats.unit === '' || couplerStats.unit === '%') { + rawValue = inputValue / 100; // Convert % back to decimal + } + + // Calculate slider position (0-1) + const sliderValue = (rawValue - parseFloat(worst)) / range; + const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); + + setThresholdCoupler(clampedSliderValue); + setThresholdCouplerValue(rawValue); + } + }} + /> + + {couplerStats.unit === 's' ? 'μs' : + (couplerStats.unit === '' || couplerStats.unit === '%') ? '%' : + couplerStats.unit} + +
+
+
{ + const isLowerBetter = couplerMetric.includes("error"); + let worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + let best = isLowerBetter ? couplerStats.worst : couplerStats.best; + const colors = generateMetricGradient(worst, best, couplerStats.average); + const gradientStops = []; + for (let i = 0; i <= 10; i++) { + const index = Math.floor((i / 10) * (colors.length - 1)); + const percentage = (i / 10) * 100; + gradientStops.push(`${colors[index]} ${percentage}%`); + } + return `linear-gradient(to right, ${gradientStops.join(', ')})`; + })() + }}> + setThresholdCoupler(parseFloat(e.target.value))} + className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" + style={{ + background: 'transparent', + appearance: 'none', + WebkitAppearance: 'none' + }} + /> +
+
+ + Worst:
+ {(() => { + const isLowerBetter = couplerMetric.includes("error"); + const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + return formatMetricValue(worst, couplerStats.unit); + })()} +
+ + + Best:
+ {(() => { + const isLowerBetter = couplerMetric.includes("error"); + const best = isLowerBetter ? couplerStats.worst : couplerStats.best; + return formatMetricValue(best, couplerStats.unit); + })()} +
+
+
+ )} +
+ ); + })()} +
+ +
+
+ } + {activeTab === "raw" && +
+

Data Type:

+ setRawDataType(e.detail)} + defaultValue={{ name: 'Calibration Data', value: 'calibration_data' }} + value={rawDataType} + inline={true} + + > + + {rawDataType.value === 'calibration_data' && +
+ setTableView(e.detail)} + > + Table View + +
+ } + {tableView && rawDataType.value === 'calibration_data' && + <> +
+ { + setQubitSwitch(e.detail) + setCouplerSwitch(!e.detail) + }} + > + Qubits + +
+
+ { + setQubitSwitch(!e.detail) + setCouplerSwitch(e.detail) + }} + > + Couplers + +
+ + } +
+
+ + {copySuccess ? 'Copied!' : 'Copy'} + + + Download + +
+
+
+ } +
+ ) +} \ No newline at end of file diff --git a/src/components/StatusModalConent.jsx b/src/components/StatusModalConent.jsx index 46254cbe66d4..dd40dac947c8 100644 --- a/src/components/StatusModalConent.jsx +++ b/src/components/StatusModalConent.jsx @@ -1,16 +1,15 @@ import React from 'react' -import { useState, useEffect } from 'react' +import { useState } from 'react' import { useCalibration } from '../hooks/useCalibration'; import { useDeviceInfo } from '../hooks/useDeviceInfo'; import { HelmiLayout } from './QcLayouts/Helmi'; import { Q50Layout } from './QcLayouts/Q50'; import { Overview } from './StatusOverview'; import { CalibrationTable } from './StatusModal/CalibrationTable'; -import { generateMetricGradient } from '../utils/generateGradient'; -import { formatMetricValue } from '../utils/formatMetricValue'; +import { SideBar } from './StatusModal/SideBar'; import { CCard, CCardTitle, CCardContent, CCardActions, CButton, CTabs, - CTab, CTabItems, CTabItem, CSelect, CRadioGroup, CSwitch, CTable + CTab, CTabItems, CTabItem } from '@cscfi/csc-ui-react'; @@ -26,10 +25,7 @@ export const ModalContent = (props) => { const [thresholdCoupler, setThresholdCoupler] = useState(0.0); const [thresholdCouplerValue, setThresholdCouplerValue] = useState(0.0); const [thresholdQubitValue, setThresholdQubitValue] = useState(0.0); - const [qubitInputValue, setQubitInputValue] = useState(''); - const [couplerInputValue, setCouplerInputValue] = useState(''); const [rawDataType, setRawDataType] = useState({ name: 'Calibration Data', value: 'calibration_data' }); - const [copySuccess, setCopySuccess] = useState(false); const [tableView, setTableView] = useState(false); const [qubitSwitch, setQubitSwitch] = useState(true); const [couplerSwitch, setCouplerSwitch] = useState(false); @@ -37,78 +33,6 @@ export const ModalContent = (props) => { const calibrationData = calibrationDataAll.metrics const lastCalibrated = new Date(calibrationDataAll.quality_metric_set_end_timestamp) - // Get current raw data based on selection - const getCurrentRawData = () => { - return rawDataType.value === 'calibration_data' ? calibrationDataAll : deviceInfoData; - }; - - // Copy to clipboard function - const copyToClipboard = async () => { - try { - const data = JSON.stringify(getCurrentRawData(), null, 2); - await navigator.clipboard.writeText(data); - setCopySuccess(true); - setTimeout(() => setCopySuccess(false), 2000); - } catch (err) { - console.error('Failed to copy: ', err); - } - }; - - // Download as JSON file - const downloadRawData = () => { - const data = JSON.stringify(getCurrentRawData(), null, 2); - const blob = new Blob([data], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${props.device_id.toLowerCase()}_${rawDataType.value}_${new Date().toISOString().split('T')[0]}.json`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - }; - - // Calculate threshold values when dependencies change - useEffect(() => { - if (qubitMetric && calibrationData && calibrationData[qubitMetric]) { - const qubitStats = getMetricStatistics(qubitMetric); - if (qubitStats) { - const isLowerBetter = qubitMetric.includes("error"); - const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - const best = isLowerBetter ? qubitStats.worst : qubitStats.best; - const range = parseFloat(best) - parseFloat(worst); - const thresholdValue = parseFloat(worst) + (thresholdQubit * range); - setThresholdQubitValue(thresholdValue); - - // Update input display value - const displayValue = qubitStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : - (qubitStats.unit === '' || qubitStats.unit === '%') ? (thresholdValue * 100).toFixed(2) : - thresholdValue.toFixed(3); - setQubitInputValue(displayValue); - } - } - }, [qubitMetric, thresholdQubit, calibrationData]); - - useEffect(() => { - if (couplerMetric && calibrationData && calibrationData[couplerMetric]) { - const couplerStats = getMetricStatistics(couplerMetric); - if (couplerStats) { - const isLowerBetter = couplerMetric.includes("error"); - const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - const best = isLowerBetter ? couplerStats.worst : couplerStats.best; - const range = parseFloat(best) - parseFloat(worst); - const thresholdValue = parseFloat(worst) + (thresholdCoupler * range); - setThresholdCouplerValue(thresholdValue); - - // Update input display value - const displayValue = couplerStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : - (couplerStats.unit === '' || couplerStats.unit === '%') ? (thresholdValue * 100).toFixed(2) : - thresholdValue.toFixed(3); - setCouplerInputValue(displayValue); - } - } - }, [couplerMetric, thresholdCoupler, calibrationData]); - const qubitMetricOptions = [ { name: '1->0 Readout Error', value: 'measure_ssro_error_1_to_0' }, @@ -125,399 +49,39 @@ export const ModalContent = (props) => { { name: 'Clifford Gate Fidelity', value: 'clifford_rb_fidelity' }, ] - // Calculate statistics for selected metric - const getMetricStatistics = (metric) => { - if (!calibrationData || !metric || !calibrationData[metric]) { - return null; - } - - const values = Object.values(calibrationData[metric]) - .map(item => item?.value) - .filter(value => value !== null && value !== undefined && !isNaN(value)); - - if (values.length === 0) return null; - - const sorted = values.sort((a, b) => a - b); - - return { - worst: sorted[0], - best: sorted[sorted.length - 1], - average: calibrationData[metric].statistics.average, - median: calibrationData[metric].statistics.median, - unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' - }; - }; - return ( {props.name}
-
-
-

Qubits: {props.qubits}

-

Basis gates: {props.basis}

-

Topology: {props.topology}

-
- -
- Service status: - {props.devicesWithStatus.find(d => d.device_id === props.device_id)?.health ? ( -
-

Online

-
- ) : ( -
-

Offline

-
- )} -
- {(activeTab === "layout" || activeTab === "graphical") && -
-
-
-

Qubit Metric:

- { - setQubitMetric(e.detail || ''); - setThresholdQubit(0); - setQubitInputValue('0'); - }} - /> -
- -
- {(qubitMetric) && (() => { - const qubitStats = qubitMetric ? getMetricStatistics(qubitMetric) : null; + - return ( -
- {qubitStats && ( -
-
-
- { - setQubitInputValue(e.target.value); - const inputValue = parseFloat(e.target.value); - if (!isNaN(inputValue)) { - const isLowerBetter = qubitMetric.includes("error"); - const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - const best = isLowerBetter ? qubitStats.worst : qubitStats.best; - const range = parseFloat(best) - parseFloat(worst); - - // Convert display value back to raw value - let rawValue = inputValue; - if (qubitStats.unit === 's') { - rawValue = inputValue / 1e6; // Convert μs back to s - } else if (qubitStats.unit === '' || qubitStats.unit === '%') { - rawValue = inputValue / 100; // Convert % back to decimal - } - - // Calculate slider position (0-1) - const sliderValue = (rawValue - parseFloat(worst)) / range; - const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); - - setThresholdQubit(clampedSliderValue); - setThresholdQubitValue(rawValue); - } - }} - /> - - {qubitStats.unit === 's' ? 'μs' : - (qubitStats.unit === '' || qubitStats.unit === '%') ? '%' : - qubitStats.unit} - -
-
-
{ - const isLowerBetter = qubitMetric.includes("error"); - let worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - let best = isLowerBetter ? qubitStats.worst : qubitStats.best; - const colors = generateMetricGradient(worst, best, qubitStats.average); - const gradientStops = []; - for (let i = 0; i <= 10; i++) { - const index = Math.floor((i / 10) * (colors.length - 1)); - const percentage = (i / 10) * 100; - gradientStops.push(`${colors[index]} ${percentage}%`); - } - return `linear-gradient(to right, ${gradientStops.join(', ')})`; - })() - }}> - setThresholdQubit(parseFloat(e.target.value))} - className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" - style={{ - background: 'transparent', - appearance: 'none', - WebkitAppearance: 'none' - }} - /> - -
-
- - Worst:
- {(() => { - const isLowerBetter = qubitMetric.includes("error"); - const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - return formatMetricValue(worst, qubitStats.unit); - })()} -
- - Best:
- {(() => { - const isLowerBetter = qubitMetric.includes("error"); - const best = isLowerBetter ? qubitStats.worst : qubitStats.best; - return formatMetricValue(best, qubitStats.unit); - })()} -
-
-
- )} -
- ); - })()} -
- -
-

Coupler Metric:

- { - setCouplerMetric(e.detail || ''); - setThresholdCoupler(0); - setCouplerInputValue('0'); - }} - /> -
- -
- {(couplerMetric) && (() => { - const couplerStats = couplerMetric ? getMetricStatistics(couplerMetric) : null; - - return ( -
- - {couplerStats && ( -
-
-
- - { - setCouplerInputValue(e.target.value); - const inputValue = parseFloat(e.target.value); - if (!isNaN(inputValue)) { - const isLowerBetter = couplerMetric.includes("error"); - const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - const best = isLowerBetter ? couplerStats.worst : couplerStats.best; - const range = parseFloat(best) - parseFloat(worst); - - // Convert display value back to raw value - let rawValue = inputValue; - if (couplerStats.unit === 's') { - rawValue = inputValue / 1e6; // Convert μs back to s - } else if (couplerStats.unit === '' || couplerStats.unit === '%') { - rawValue = inputValue / 100; // Convert % back to decimal - } - - // Calculate slider position (0-1) - const sliderValue = (rawValue - parseFloat(worst)) / range; - const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); - - setThresholdCoupler(clampedSliderValue); - setThresholdCouplerValue(rawValue); - } - }} - /> - - {couplerStats.unit === 's' ? 'μs' : - (couplerStats.unit === '' || couplerStats.unit === '%') ? '%' : - couplerStats.unit} - -
-
-
{ - const isLowerBetter = couplerMetric.includes("error"); - let worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - let best = isLowerBetter ? couplerStats.worst : couplerStats.best; - const colors = generateMetricGradient(worst, best, couplerStats.average); - const gradientStops = []; - for (let i = 0; i <= 10; i++) { - const index = Math.floor((i / 10) * (colors.length - 1)); - const percentage = (i / 10) * 100; - gradientStops.push(`${colors[index]} ${percentage}%`); - } - return `linear-gradient(to right, ${gradientStops.join(', ')})`; - })() - }}> - setThresholdCoupler(parseFloat(e.target.value))} - className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" - style={{ - background: 'transparent', - appearance: 'none', - WebkitAppearance: 'none' - }} - /> -
-
- - Worst:
- {(() => { - const isLowerBetter = couplerMetric.includes("error"); - const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - return formatMetricValue(worst, couplerStats.unit); - })()} -
- - - Best:
- {(() => { - const isLowerBetter = couplerMetric.includes("error"); - const best = isLowerBetter ? couplerStats.worst : couplerStats.best; - return formatMetricValue(best, couplerStats.unit); - })()} -
-
-
- )} -
- ); - })()} -
- -
-
- } - {activeTab === "raw" && -
-

Data Type:

- setRawDataType(e.detail)} - defaultValue={{ name: 'Calibration Data', value: 'calibration_data' }} - value={rawDataType} - inline={true} - - > - - {rawDataType.value === 'calibration_data' && -
- setTableView(e.detail)} - > - Table View - -
- } - {tableView && rawDataType.value === 'calibration_data' && - <> -
- { - setQubitSwitch(e.detail) - setCouplerSwitch(!e.detail) - }} - > - Qubits - -
-
- { - setQubitSwitch(!e.detail) - setCouplerSwitch(e.detail) - }} - > - Couplers - -
- - } -
-
- - {copySuccess ? 'Copied!' : 'Copy'} - - - Download - -
-
-
- } -
setActiveTab(e.detail)} className='col-span-1 md:col-span-2 lg:col-span-3'> Overview From 961f4ac00cde834a7a026b22b872aa8a474e3768 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:42:51 +0300 Subject: [PATCH 10/17] remove unused --- src/components/StatusModal/SideBar.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/StatusModal/SideBar.jsx b/src/components/StatusModal/SideBar.jsx index 1f59d9586bb1..e336416ba931 100644 --- a/src/components/StatusModal/SideBar.jsx +++ b/src/components/StatusModal/SideBar.jsx @@ -19,7 +19,6 @@ export const SideBar = (props) => { thresholdCoupler, setThresholdCoupler, thresholdQubit, setThresholdQubit } = props; const calibrationData = calibrationDataAll.metrics - const lastCalibrated = new Date(calibrationDataAll.quality_metric_set_end_timestamp) // Get current raw data based on selection const getCurrentRawData = () => { From 37a11cb1f6ebde2568cae5fcdbad80ccd0a50276 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:51:13 +0300 Subject: [PATCH 11/17] refactor a bit --- src/components/StatusModal/SideBar.jsx | 73 ++++++-------------------- src/utils/sidebarUtils.js | 49 +++++++++++++++++ 2 files changed, 64 insertions(+), 58 deletions(-) create mode 100644 src/utils/sidebarUtils.js diff --git a/src/components/StatusModal/SideBar.jsx b/src/components/StatusModal/SideBar.jsx index e336416ba931..5732ef5529ce 100644 --- a/src/components/StatusModal/SideBar.jsx +++ b/src/components/StatusModal/SideBar.jsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import { CSelect, CRadioGroup, CSwitch, CButton } from '@cscfi/csc-ui-react'; import { generateMetricGradient } from '../../utils/generateGradient'; import { formatMetricValue } from '../../utils/formatMetricValue'; +import { getCurrentRawData, copyToClipboard, downloadRawData, getMetricStatistics } from '../../utils/sidebarUtils'; export const SideBar = (props) => { @@ -10,7 +11,6 @@ export const SideBar = (props) => { const [copySuccess, setCopySuccess] = useState(false); - const { activeTab, setThresholdCouplerValue, setThresholdQubitValue, calibrationDataAll, deviceInfoData, devicesWithStatus, qubitMetricOptions, couplerMetricOptions, tableView, setTableView, @@ -20,65 +20,22 @@ export const SideBar = (props) => { const calibrationData = calibrationDataAll.metrics - // Get current raw data based on selection - const getCurrentRawData = () => { - return rawDataType.value === 'calibration_data' ? calibrationDataAll : deviceInfoData; - }; - // Copy to clipboard function - const copyToClipboard = async () => { - try { - const data = JSON.stringify(getCurrentRawData(), null, 2); - await navigator.clipboard.writeText(data); - setCopySuccess(true); - setTimeout(() => setCopySuccess(false), 2000); - } catch (err) { - console.error('Failed to copy: ', err); - } + const handleCopyToClipboard = async () => { + const data = JSON.stringify(getCurrentRawData(rawDataType, calibrationDataAll, deviceInfoData), null, 2); + await copyToClipboard(data, setCopySuccess); }; // Download as JSON file - const downloadRawData = () => { - const data = JSON.stringify(getCurrentRawData(), null, 2); - const blob = new Blob([data], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `${deviceData.device_id.toLowerCase()}_${rawDataType.value}_${new Date().toISOString().split('T')[0]}.json`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - }; - - // Calculate statistics for selected metric - const getMetricStatistics = (metric) => { - if (!calibrationData || !metric || !calibrationData[metric]) { - return null; - } - - const values = Object.values(calibrationData[metric]) - .map(item => item?.value) - .filter(value => value !== null && value !== undefined && !isNaN(value)); - - if (values.length === 0) return null; - - const sorted = values.sort((a, b) => a - b); - - return { - worst: sorted[0], - best: sorted[sorted.length - 1], - average: calibrationData[metric].statistics.average, - median: calibrationData[metric].statistics.median, - unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' - }; + const handleDownloadRawData = () => { + const data = JSON.stringify(getCurrentRawData(rawDataType, calibrationDataAll, deviceInfoData), null, 2); + downloadRawData(data, deviceData, rawDataType); }; - // Calculate threshold values when dependencies change useEffect(() => { - if (qubitMetric && calibrationData && calibrationData[qubitMetric]) { - const qubitStats = getMetricStatistics(qubitMetric); + if (qubitMetric && calibrationData) { + const qubitStats = getMetricStatistics(calibrationData, qubitMetric); if (qubitStats) { const isLowerBetter = qubitMetric.includes("error"); const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; @@ -97,8 +54,8 @@ export const SideBar = (props) => { }, [qubitMetric, thresholdQubit]); useEffect(() => { - if (couplerMetric && calibrationData && calibrationData[couplerMetric]) { - const couplerStats = getMetricStatistics(couplerMetric); + if (couplerMetric && calibrationData) { + const couplerStats = getMetricStatistics(calibrationData, couplerMetric); if (couplerStats) { const isLowerBetter = couplerMetric.includes("error"); const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; @@ -158,7 +115,7 @@ export const SideBar = (props) => {
{(qubitMetric) && (() => { - const qubitStats = qubitMetric ? getMetricStatistics(qubitMetric) : null; + const qubitStats = qubitMetric ? getMetricStatistics(calibrationData, qubitMetric) : null; return (
@@ -300,7 +257,7 @@ export const SideBar = (props) => {
{(couplerMetric) && (() => { - const couplerStats = couplerMetric ? getMetricStatistics(couplerMetric) : null; + const couplerStats = couplerMetric ? getMetricStatistics(calibrationData, couplerMetric) : null; return (
@@ -465,7 +422,7 @@ export const SideBar = (props) => {
{copySuccess ? 'Copied!' : 'Copy'} @@ -473,7 +430,7 @@ export const SideBar = (props) => { Download diff --git a/src/utils/sidebarUtils.js b/src/utils/sidebarUtils.js new file mode 100644 index 000000000000..0d36544dc93d --- /dev/null +++ b/src/utils/sidebarUtils.js @@ -0,0 +1,49 @@ +// Utility functions for SideBar + +export const getCurrentRawData = (rawDataType, calibrationDataAll, deviceInfoData) => { + return rawDataType.value === 'calibration_data' ? calibrationDataAll : deviceInfoData; +}; + +export const copyToClipboard = async (data, setCopySuccess) => { + try { + await navigator.clipboard.writeText(data); + setCopySuccess(true); + setTimeout(() => setCopySuccess(false), 2000); + } catch (err) { + console.error('Failed to copy: ', err); + } +}; + +export const downloadRawData = (data, deviceData, rawDataType) => { + const blob = new Blob([data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${deviceData.device_id.toLowerCase()}_${rawDataType.value}_${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +}; + +export const getMetricStatistics = (calibrationData, metric) => { + if (!calibrationData || !metric || !calibrationData[metric]) { + return null; + } + + const values = Object.values(calibrationData[metric]) + .map(item => item?.value) + .filter(value => value !== null && value !== undefined && !isNaN(value)); + + if (values.length === 0) return null; + + const sorted = values.sort((a, b) => a - b); + + return { + worst: sorted[0], + best: sorted[sorted.length - 1], + average: calibrationData[metric].statistics.average, + median: calibrationData[metric].statistics.median, + unit: Object.values(calibrationData[metric]).find(item => item?.unit)?.unit || '' + }; +}; From 8f044378535dce9eb998ee656cd5cca044b441d9 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 13:55:04 +0300 Subject: [PATCH 12/17] simplify state --- src/components/StatusModal/SideBar.jsx | 130 +++++++++++++------------ src/components/StatusModalConent.jsx | 91 ++++++++--------- 2 files changed, 114 insertions(+), 107 deletions(-) diff --git a/src/components/StatusModal/SideBar.jsx b/src/components/StatusModal/SideBar.jsx index 5732ef5529ce..3d070f2ef381 100644 --- a/src/components/StatusModal/SideBar.jsx +++ b/src/components/StatusModal/SideBar.jsx @@ -5,44 +5,50 @@ import { formatMetricValue } from '../../utils/formatMetricValue'; import { getCurrentRawData, copyToClipboard, downloadRawData, getMetricStatistics } from '../../utils/sidebarUtils'; export const SideBar = (props) => { + const { + activeTab, + metricsState, + updateMetricsState, + viewState, + updateViewState, + calibrationDataAll, + deviceInfoData, + devicesWithStatus, + qubitMetricOptions, + couplerMetricOptions, + deviceData, + } = props; + + const calibrationData = calibrationDataAll.metrics; const [qubitInputValue, setQubitInputValue] = useState(''); const [couplerInputValue, setCouplerInputValue] = useState(''); const [copySuccess, setCopySuccess] = useState(false); - const { activeTab, setThresholdCouplerValue, setThresholdQubitValue, - calibrationDataAll, deviceInfoData, devicesWithStatus, - qubitMetricOptions, couplerMetricOptions, tableView, setTableView, - qubitSwitch, setQubitSwitch, couplerSwitch, setCouplerSwitch, rawDataType, - setRawDataType, deviceData, qubitMetric, setQubitMetric, couplerMetric, setCouplerMetric, - thresholdCoupler, setThresholdCoupler, thresholdQubit, setThresholdQubit } = props; - - const calibrationData = calibrationDataAll.metrics - // Copy to clipboard function const handleCopyToClipboard = async () => { - const data = JSON.stringify(getCurrentRawData(rawDataType, calibrationDataAll, deviceInfoData), null, 2); + const data = JSON.stringify(getCurrentRawData(viewState.rawDataType, calibrationDataAll, deviceInfoData), null, 2); await copyToClipboard(data, setCopySuccess); }; // Download as JSON file const handleDownloadRawData = () => { - const data = JSON.stringify(getCurrentRawData(rawDataType, calibrationDataAll, deviceInfoData), null, 2); - downloadRawData(data, deviceData, rawDataType); + const data = JSON.stringify(getCurrentRawData(viewState.rawDataType, calibrationDataAll, deviceInfoData), null, 2); + downloadRawData(data, deviceData, viewState.rawDataType); }; // Calculate threshold values when dependencies change useEffect(() => { - if (qubitMetric && calibrationData) { - const qubitStats = getMetricStatistics(calibrationData, qubitMetric); + if (metricsState.qubitMetric && calibrationData) { + const qubitStats = getMetricStatistics(calibrationData, metricsState.qubitMetric); if (qubitStats) { - const isLowerBetter = qubitMetric.includes("error"); + const isLowerBetter = metricsState.qubitMetric.includes("error"); const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; const best = isLowerBetter ? qubitStats.worst : qubitStats.best; const range = parseFloat(best) - parseFloat(worst); - const thresholdValue = parseFloat(worst) + (thresholdQubit * range); - setThresholdQubitValue(thresholdValue); + const thresholdValue = parseFloat(worst) + (metricsState.thresholdQubit * range); + updateMetricsState('thresholdQubitValue', thresholdValue); // Update input display value const displayValue = qubitStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : @@ -51,18 +57,18 @@ export const SideBar = (props) => { setQubitInputValue(displayValue); } } - }, [qubitMetric, thresholdQubit]); + }, [metricsState.qubitMetric, metricsState.thresholdQubit]); useEffect(() => { - if (couplerMetric && calibrationData) { - const couplerStats = getMetricStatistics(calibrationData, couplerMetric); + if (metricsState.couplerMetric && calibrationData) { + const couplerStats = getMetricStatistics(calibrationData, metricsState.couplerMetric); if (couplerStats) { - const isLowerBetter = couplerMetric.includes("error"); + const isLowerBetter = metricsState.couplerMetric.includes("error"); const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; const best = isLowerBetter ? couplerStats.worst : couplerStats.best; const range = parseFloat(best) - parseFloat(worst); - const thresholdValue = parseFloat(worst) + (thresholdCoupler * range); - setThresholdCouplerValue(thresholdValue); + const thresholdValue = parseFloat(worst) + (metricsState.thresholdCoupler * range); + updateMetricsState('thresholdCouplerValue', thresholdValue); // Update input display value const displayValue = couplerStats.unit === 's' ? (thresholdValue * 1e6).toFixed(2) : @@ -71,7 +77,7 @@ export const SideBar = (props) => { setCouplerInputValue(displayValue); } } - }, [couplerMetric, thresholdCoupler]); + }, [metricsState.couplerMetric, metricsState.thresholdCoupler]); return (
@@ -102,20 +108,20 @@ export const SideBar = (props) => { hideDetails={true} className='py-2' clearable - value={qubitMetric} + value={metricsState.qubitMetric} items={qubitMetricOptions} placeholder='Choose metric' onChangeValue={(e) => { - setQubitMetric(e.detail || ''); - setThresholdQubit(0); + updateMetricsState('qubitMetric', e.detail || ''); + updateMetricsState('thresholdQubit', 0); setQubitInputValue('0'); }} />
- {(qubitMetric) && (() => { - const qubitStats = qubitMetric ? getMetricStatistics(calibrationData, qubitMetric) : null; + {(metricsState.qubitMetric) && (() => { + const qubitStats = metricsState.qubitMetric ? getMetricStatistics(calibrationData, metricsState.qubitMetric) : null; return (
@@ -132,7 +138,7 @@ export const SideBar = (props) => { setQubitInputValue(e.target.value); const inputValue = parseFloat(e.target.value); if (!isNaN(inputValue)) { - const isLowerBetter = qubitMetric.includes("error"); + const isLowerBetter = metricsState.qubitMetric.includes("error"); const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; const best = isLowerBetter ? qubitStats.worst : qubitStats.best; const range = parseFloat(best) - parseFloat(worst); @@ -149,8 +155,8 @@ export const SideBar = (props) => { const sliderValue = (rawValue - parseFloat(worst)) / range; const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); - setThresholdQubit(clampedSliderValue); - setThresholdQubitValue(rawValue); + updateMetricsState('thresholdQubit', clampedSliderValue); + updateMetricsState('thresholdQubitValue', rawValue); } }} /> @@ -164,7 +170,7 @@ export const SideBar = (props) => {
{ - const isLowerBetter = qubitMetric.includes("error"); + const isLowerBetter = metricsState.qubitMetric.includes("error"); let worst = isLowerBetter ? qubitStats.best : qubitStats.worst; let best = isLowerBetter ? qubitStats.worst : qubitStats.best; const colors = generateMetricGradient(worst, best, qubitStats.average); @@ -182,8 +188,8 @@ export const SideBar = (props) => { min="0" max="1" step="0.01" - value={thresholdQubit} - onChange={(e) => setThresholdQubit(parseFloat(e.target.value))} + value={metricsState.thresholdQubit} + onChange={(e) => updateMetricsState('thresholdQubit', parseFloat(e.target.value))} className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" style={{ background: 'transparent', @@ -217,7 +223,7 @@ export const SideBar = (props) => { Worst:
{(() => { - const isLowerBetter = qubitMetric.includes("error"); + const isLowerBetter = metricsState.qubitMetric.includes("error"); const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; return formatMetricValue(worst, qubitStats.unit); })()} @@ -225,7 +231,7 @@ export const SideBar = (props) => { Best:
{(() => { - const isLowerBetter = qubitMetric.includes("error"); + const isLowerBetter = metricsState.qubitMetric.includes("error"); const best = isLowerBetter ? qubitStats.worst : qubitStats.best; return formatMetricValue(best, qubitStats.unit); })()} @@ -244,20 +250,20 @@ export const SideBar = (props) => { hideDetails={true} className='py-2' clearable - value={couplerMetric} + value={metricsState.couplerMetric} items={couplerMetricOptions} placeholder='Choose metric' onChangeValue={(e) => { - setCouplerMetric(e.detail || ''); - setThresholdCoupler(0); + updateMetricsState('couplerMetric', e.detail || ''); + updateMetricsState('thresholdCoupler', 0); setCouplerInputValue('0'); }} />
- {(couplerMetric) && (() => { - const couplerStats = couplerMetric ? getMetricStatistics(calibrationData, couplerMetric) : null; + {(metricsState.couplerMetric) && (() => { + const couplerStats = metricsState.couplerMetric ? getMetricStatistics(calibrationData, metricsState.couplerMetric) : null; return (
@@ -276,7 +282,7 @@ export const SideBar = (props) => { setCouplerInputValue(e.target.value); const inputValue = parseFloat(e.target.value); if (!isNaN(inputValue)) { - const isLowerBetter = couplerMetric.includes("error"); + const isLowerBetter = metricsState.couplerMetric.includes("error"); const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; const best = isLowerBetter ? couplerStats.worst : couplerStats.best; const range = parseFloat(best) - parseFloat(worst); @@ -293,8 +299,8 @@ export const SideBar = (props) => { const sliderValue = (rawValue - parseFloat(worst)) / range; const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); - setThresholdCoupler(clampedSliderValue); - setThresholdCouplerValue(rawValue); + updateMetricsState('thresholdCoupler', clampedSliderValue); + updateMetricsState('thresholdCouplerValue', rawValue); } }} /> @@ -308,7 +314,7 @@ export const SideBar = (props) => {
{ - const isLowerBetter = couplerMetric.includes("error"); + const isLowerBetter = metricsState.couplerMetric.includes("error"); let worst = isLowerBetter ? couplerStats.best : couplerStats.worst; let best = isLowerBetter ? couplerStats.worst : couplerStats.best; const colors = generateMetricGradient(worst, best, couplerStats.average); @@ -326,8 +332,8 @@ export const SideBar = (props) => { min="0" max="1" step="0.01" - value={thresholdCoupler} - onChange={(e) => setThresholdCoupler(parseFloat(e.target.value))} + value={metricsState.thresholdCoupler} + onChange={(e) => updateMetricsState('thresholdCoupler', parseFloat(e.target.value))} className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" style={{ background: 'transparent', @@ -340,7 +346,7 @@ export const SideBar = (props) => { Worst:
{(() => { - const isLowerBetter = couplerMetric.includes("error"); + const isLowerBetter = metricsState.couplerMetric.includes("error"); const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; return formatMetricValue(worst, couplerStats.unit); })()} @@ -349,7 +355,7 @@ export const SideBar = (props) => { Best:
{(() => { - const isLowerBetter = couplerMetric.includes("error"); + const isLowerBetter = metricsState.couplerMetric.includes("error"); const best = isLowerBetter ? couplerStats.worst : couplerStats.best; return formatMetricValue(best, couplerStats.unit); })()} @@ -375,31 +381,31 @@ export const SideBar = (props) => { { name: 'Calibration Data', value: 'calibration_data' }, { name: 'Device Info', value: 'device_info' }, ]} - onChangeValue={(e) => setRawDataType(e.detail)} + onChangeValue={(e) => updateViewState('rawDataType', e.detail)} defaultValue={{ name: 'Calibration Data', value: 'calibration_data' }} - value={rawDataType} + value={viewState.rawDataType} inline={true} > - {rawDataType.value === 'calibration_data' && + {viewState.rawDataType.value === 'calibration_data' &&
setTableView(e.detail)} + value={viewState.tableView} + onChangeValue={(e) => updateViewState('tableView', e.detail)} > Table View
} - {tableView && rawDataType.value === 'calibration_data' && + {viewState.tableView && viewState.rawDataType.value === 'calibration_data' && <>
{ - setQubitSwitch(e.detail) - setCouplerSwitch(!e.detail) + updateMetricsState('qubitSwitch', e.detail) + updateMetricsState('couplerSwitch', !e.detail) }} > Qubits @@ -407,10 +413,10 @@ export const SideBar = (props) => {
{ - setQubitSwitch(!e.detail) - setCouplerSwitch(e.detail) + updateMetricsState('qubitSwitch', !e.detail) + updateMetricsState('couplerSwitch', e.detail) }} > Couplers diff --git a/src/components/StatusModalConent.jsx b/src/components/StatusModalConent.jsx index dd40dac947c8..89c4da3e6e00 100644 --- a/src/components/StatusModalConent.jsx +++ b/src/components/StatusModalConent.jsx @@ -19,16 +19,31 @@ export const ModalContent = (props) => { const { deviceInfo: deviceInfoData, infoError } = useDeviceInfo(`https://fiqci-backend.2.rahtiapp.fi/device/${props.device_id.toLowerCase()}`) const [activeTab, setActiveTab] = useState('overview'); - const [qubitMetric, setQubitMetric] = useState(''); - const [couplerMetric, setCouplerMetric] = useState(''); - const [thresholdQubit, setThresholdQubit] = useState(0.0); - const [thresholdCoupler, setThresholdCoupler] = useState(0.0); - const [thresholdCouplerValue, setThresholdCouplerValue] = useState(0.0); - const [thresholdQubitValue, setThresholdQubitValue] = useState(0.0); - const [rawDataType, setRawDataType] = useState({ name: 'Calibration Data', value: 'calibration_data' }); - const [tableView, setTableView] = useState(false); - const [qubitSwitch, setQubitSwitch] = useState(true); - const [couplerSwitch, setCouplerSwitch] = useState(false); + + const [metricsState, setMetricsState] = useState({ + qubitMetric: '', + couplerMetric: '', + thresholdQubit: 0.0, + thresholdCoupler: 0.0, + thresholdCouplerValue: 0.0, + thresholdQubitValue: 0.0, + }); + + const [viewState, setViewState] = useState({ + rawDataType: { name: 'Calibration Data', value: 'calibration_data' }, + tableView: false, + qubitSwitch: true, + couplerSwitch: false, + }); + + // Update functions for grouped states + const updateMetricsState = (key, value) => { + setMetricsState((prevState) => ({ ...prevState, [key]: value })); + }; + + const updateViewState = (key, value) => { + setViewState((prevState) => ({ ...prevState, [key]: value })); + }; const calibrationData = calibrationDataAll.metrics const lastCalibrated = new Date(calibrationDataAll.quality_metric_set_end_timestamp) @@ -56,30 +71,16 @@ export const ModalContent = (props) => {
setActiveTab(e.detail)} className='col-span-1 md:col-span-2 lg:col-span-3'> @@ -109,22 +110,22 @@ export const ModalContent = (props) => { {props.device_id.toLowerCase() === 'q50' ? ( m.value === qubitMetric)?.name || qubitMetric} - couplerMetricFormatted={couplerMetricOptions.find(m => m.value === couplerMetric)?.name || couplerMetric} - thresholdQubit={thresholdQubitValue} - thresholdCoupler={thresholdCouplerValue} + qubitMetric={metricsState.qubitMetric} + couplerMetric={metricsState.couplerMetric} + qubitMetricFormatted={qubitMetricOptions.find(m => m.value === metricsState.qubitMetric)?.name || metricsState.qubitMetric} + couplerMetricFormatted={couplerMetricOptions.find(m => m.value === metricsState.couplerMetric)?.name || metricsState.couplerMetric} + thresholdQubit={metricsState.thresholdQubitValue} + thresholdCoupler={metricsState.thresholdCouplerValue} /> ) : ( m.value === qubitMetric)?.name || qubitMetric} - couplerMetricFormatted={couplerMetricOptions.find(m => m.value === couplerMetric)?.name || couplerMetric} - thresholdQubit={thresholdQubitValue} - thresholdCoupler={thresholdCouplerValue} + qubitMetric={metricsState.qubitMetric} + couplerMetric={metricsState.couplerMetric} + qubitMetricFormatted={qubitMetricOptions.find(m => m.value === metricsState.qubitMetric)?.name || metricsState.qubitMetric} + couplerMetricFormatted={couplerMetricOptions.find(m => m.value === metricsState.couplerMetric)?.name || metricsState.couplerMetric} + thresholdQubit={metricsState.thresholdQubitValue} + thresholdCoupler={metricsState.thresholdCouplerValue} /> )}
@@ -136,19 +137,19 @@ export const ModalContent = (props) => {
- {rawDataType.value === 'calibration_data' && tableView ? ( + {viewState.rawDataType.value === 'calibration_data' && viewState.tableView ? ( ) : (
-                                            {rawDataType.value === 'calibration_data' &&
+                                            {viewState.rawDataType.value === 'calibration_data' &&
                                                 JSON.stringify(calibrationDataAll, null, 2)}
-                                            {rawDataType.value === 'device_info' &&
+                                            {viewState.rawDataType.value === 'device_info' &&
                                                 JSON.stringify(deviceInfoData, null, 2)}
                                         
)} From 54e8e63218185049f019fc43ec3dd6650c9b2c33 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 14:05:00 +0300 Subject: [PATCH 13/17] refactor --- src/components/StatusModal/DeviceStatus.jsx | 30 ++ src/components/StatusModal/MetricSwitcher.jsx | 285 ++++++++++++++++ src/components/StatusModal/SideBar.jsx | 305 +----------------- 3 files changed, 330 insertions(+), 290 deletions(-) create mode 100644 src/components/StatusModal/DeviceStatus.jsx create mode 100644 src/components/StatusModal/MetricSwitcher.jsx diff --git a/src/components/StatusModal/DeviceStatus.jsx b/src/components/StatusModal/DeviceStatus.jsx new file mode 100644 index 000000000000..d14eefc742fe --- /dev/null +++ b/src/components/StatusModal/DeviceStatus.jsx @@ -0,0 +1,30 @@ +import React from 'react'; + +export const DeviceStatus = (props) => { + + const { deviceData, devicesWithStatus } = props; + + return ( + <> +
+

Qubits: {deviceData.qubits}

+

Basis gates: {deviceData.basis}

+

Topology: {deviceData.topology}

+
+ +
+ Service status: + {devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.health ? ( +
+

Online

+
+ ) : ( +
+

Offline

+
+ )} +
+ + ) + +} \ No newline at end of file diff --git a/src/components/StatusModal/MetricSwitcher.jsx b/src/components/StatusModal/MetricSwitcher.jsx new file mode 100644 index 000000000000..d7137fbdfed7 --- /dev/null +++ b/src/components/StatusModal/MetricSwitcher.jsx @@ -0,0 +1,285 @@ +import React from 'react'; +import { CSelect } from '@cscfi/csc-ui-react'; +import { generateMetricGradient } from '../../utils/generateGradient'; +import { formatMetricValue } from '../../utils/formatMetricValue'; +import { getMetricStatistics } from '../../utils/sidebarUtils'; + +export const MetricSwitcher = (props) => { + + const { metricsState, updateMetricsState, calibrationData, + qubitMetricOptions, couplerMetricOptions, qubitInputValue, setQubitInputValue, + couplerInputValue, setCouplerInputValue } = props; + + return ( +
+
+
+

Qubit Metric:

+ { + updateMetricsState('qubitMetric', e.detail || ''); + updateMetricsState('thresholdQubit', 0); + setQubitInputValue('0'); + }} + /> +
+ +
+ {(metricsState.qubitMetric) && (() => { + const qubitStats = metricsState.qubitMetric ? getMetricStatistics(calibrationData, metricsState.qubitMetric) : null; + + return ( +
+ {qubitStats && ( +
+
+
+ { + setQubitInputValue(e.target.value); + const inputValue = parseFloat(e.target.value); + if (!isNaN(inputValue)) { + const isLowerBetter = metricsState.qubitMetric.includes("error"); + const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + const best = isLowerBetter ? qubitStats.worst : qubitStats.best; + const range = parseFloat(best) - parseFloat(worst); + + // Convert display value back to raw value + let rawValue = inputValue; + if (qubitStats.unit === 's') { + rawValue = inputValue / 1e6; // Convert μs back to s + } else if (qubitStats.unit === '' || qubitStats.unit === '%') { + rawValue = inputValue / 100; // Convert % back to decimal + } + + // Calculate slider position (0-1) + const sliderValue = (rawValue - parseFloat(worst)) / range; + const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); + + updateMetricsState('thresholdQubit', clampedSliderValue); + updateMetricsState('thresholdQubitValue', rawValue); + } + }} + /> + + {qubitStats.unit === 's' ? 'μs' : + (qubitStats.unit === '' || qubitStats.unit === '%') ? '%' : + qubitStats.unit} + +
+
+
{ + const isLowerBetter = metricsState.qubitMetric.includes("error"); + let worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + let best = isLowerBetter ? qubitStats.worst : qubitStats.best; + const colors = generateMetricGradient(worst, best, qubitStats.average); + const gradientStops = []; + for (let i = 0; i <= 10; i++) { + const index = Math.floor((i / 10) * (colors.length - 1)); + const percentage = (i / 10) * 100; + gradientStops.push(`${colors[index]} ${percentage}%`); + } + return `linear-gradient(to right, ${gradientStops.join(', ')})`; + })() + }}> + updateMetricsState('thresholdQubit', parseFloat(e.target.value))} + className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" + style={{ + background: 'transparent', + appearance: 'none', + WebkitAppearance: 'none' + }} + /> + +
+
+ + Worst:
+ {(() => { + const isLowerBetter = metricsState.qubitMetric.includes("error"); + const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; + return formatMetricValue(worst, qubitStats.unit); + })()} +
+ + Best:
+ {(() => { + const isLowerBetter = metricsState.qubitMetric.includes("error"); + const best = isLowerBetter ? qubitStats.worst : qubitStats.best; + return formatMetricValue(best, qubitStats.unit); + })()} +
+
+
+ )} +
+ ); + })()} +
+ +
+

Coupler Metric:

+ { + updateMetricsState('couplerMetric', e.detail || ''); + updateMetricsState('thresholdCoupler', 0); + setCouplerInputValue('0'); + }} + /> +
+ +
+ {(metricsState.couplerMetric) && (() => { + const couplerStats = metricsState.couplerMetric ? getMetricStatistics(calibrationData, metricsState.couplerMetric) : null; + + return ( +
+ + {couplerStats && ( +
+
+
+ + { + setCouplerInputValue(e.target.value); + const inputValue = parseFloat(e.target.value); + if (!isNaN(inputValue)) { + const isLowerBetter = metricsState.couplerMetric.includes("error"); + const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + const best = isLowerBetter ? couplerStats.worst : couplerStats.best; + const range = parseFloat(best) - parseFloat(worst); + + // Convert display value back to raw value + let rawValue = inputValue; + if (couplerStats.unit === 's') { + rawValue = inputValue / 1e6; // Convert μs back to s + } else if (couplerStats.unit === '' || couplerStats.unit === '%') { + rawValue = inputValue / 100; // Convert % back to decimal + } + + // Calculate slider position (0-1) + const sliderValue = (rawValue - parseFloat(worst)) / range; + const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); + + updateMetricsState('thresholdCoupler', clampedSliderValue); + updateMetricsState('thresholdCouplerValue', rawValue); + } + }} + /> + + {couplerStats.unit === 's' ? 'μs' : + (couplerStats.unit === '' || couplerStats.unit === '%') ? '%' : + couplerStats.unit} + +
+
+
{ + const isLowerBetter = metricsState.couplerMetric.includes("error"); + let worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + let best = isLowerBetter ? couplerStats.worst : couplerStats.best; + const colors = generateMetricGradient(worst, best, couplerStats.average); + const gradientStops = []; + for (let i = 0; i <= 10; i++) { + const index = Math.floor((i / 10) * (colors.length - 1)); + const percentage = (i / 10) * 100; + gradientStops.push(`${colors[index]} ${percentage}%`); + } + return `linear-gradient(to right, ${gradientStops.join(', ')})`; + })() + }}> + updateMetricsState('thresholdCoupler', parseFloat(e.target.value))} + className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" + style={{ + background: 'transparent', + appearance: 'none', + WebkitAppearance: 'none' + }} + /> +
+
+ + Worst:
+ {(() => { + const isLowerBetter = metricsState.couplerMetric.includes("error"); + const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; + return formatMetricValue(worst, couplerStats.unit); + })()} +
+ + + Best:
+ {(() => { + const isLowerBetter = metricsState.couplerMetric.includes("error"); + const best = isLowerBetter ? couplerStats.worst : couplerStats.best; + return formatMetricValue(best, couplerStats.unit); + })()} +
+
+
+ )} +
+ ); + })()} +
+ +
+
+ ) +} \ No newline at end of file diff --git a/src/components/StatusModal/SideBar.jsx b/src/components/StatusModal/SideBar.jsx index 3d070f2ef381..94b06afb716f 100644 --- a/src/components/StatusModal/SideBar.jsx +++ b/src/components/StatusModal/SideBar.jsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; -import { CSelect, CRadioGroup, CSwitch, CButton } from '@cscfi/csc-ui-react'; -import { generateMetricGradient } from '../../utils/generateGradient'; -import { formatMetricValue } from '../../utils/formatMetricValue'; +import { DeviceStatus } from './DeviceStatus'; +import { MetricSwitcher } from './MetricSwitcher'; +import { CRadioGroup, CSwitch, CButton } from '@cscfi/csc-ui-react'; import { getCurrentRawData, copyToClipboard, downloadRawData, getMetricStatistics } from '../../utils/sidebarUtils'; export const SideBar = (props) => { @@ -81,295 +81,20 @@ export const SideBar = (props) => { return (
-
-

Qubits: {deviceData.qubits}

-

Basis gates: {deviceData.basis}

-

Topology: {deviceData.topology}

-
+ -
- Service status: - {devicesWithStatus.find(d => d.device_id === deviceData.device_id)?.health ? ( -
-

Online

-
- ) : ( -
-

Offline

-
- )} -
{(activeTab === "layout" || activeTab === "graphical") && -
-
-
-

Qubit Metric:

- { - updateMetricsState('qubitMetric', e.detail || ''); - updateMetricsState('thresholdQubit', 0); - setQubitInputValue('0'); - }} - /> -
- -
- {(metricsState.qubitMetric) && (() => { - const qubitStats = metricsState.qubitMetric ? getMetricStatistics(calibrationData, metricsState.qubitMetric) : null; - - return ( -
- {qubitStats && ( -
-
-
- { - setQubitInputValue(e.target.value); - const inputValue = parseFloat(e.target.value); - if (!isNaN(inputValue)) { - const isLowerBetter = metricsState.qubitMetric.includes("error"); - const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - const best = isLowerBetter ? qubitStats.worst : qubitStats.best; - const range = parseFloat(best) - parseFloat(worst); - - // Convert display value back to raw value - let rawValue = inputValue; - if (qubitStats.unit === 's') { - rawValue = inputValue / 1e6; // Convert μs back to s - } else if (qubitStats.unit === '' || qubitStats.unit === '%') { - rawValue = inputValue / 100; // Convert % back to decimal - } - - // Calculate slider position (0-1) - const sliderValue = (rawValue - parseFloat(worst)) / range; - const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); - - updateMetricsState('thresholdQubit', clampedSliderValue); - updateMetricsState('thresholdQubitValue', rawValue); - } - }} - /> - - {qubitStats.unit === 's' ? 'μs' : - (qubitStats.unit === '' || qubitStats.unit === '%') ? '%' : - qubitStats.unit} - -
-
-
{ - const isLowerBetter = metricsState.qubitMetric.includes("error"); - let worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - let best = isLowerBetter ? qubitStats.worst : qubitStats.best; - const colors = generateMetricGradient(worst, best, qubitStats.average); - const gradientStops = []; - for (let i = 0; i <= 10; i++) { - const index = Math.floor((i / 10) * (colors.length - 1)); - const percentage = (i / 10) * 100; - gradientStops.push(`${colors[index]} ${percentage}%`); - } - return `linear-gradient(to right, ${gradientStops.join(', ')})`; - })() - }}> - updateMetricsState('thresholdQubit', parseFloat(e.target.value))} - className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" - style={{ - background: 'transparent', - appearance: 'none', - WebkitAppearance: 'none' - }} - /> - -
-
- - Worst:
- {(() => { - const isLowerBetter = metricsState.qubitMetric.includes("error"); - const worst = isLowerBetter ? qubitStats.best : qubitStats.worst; - return formatMetricValue(worst, qubitStats.unit); - })()} -
- - Best:
- {(() => { - const isLowerBetter = metricsState.qubitMetric.includes("error"); - const best = isLowerBetter ? qubitStats.worst : qubitStats.best; - return formatMetricValue(best, qubitStats.unit); - })()} -
-
-
- )} -
- ); - })()} -
- -
-

Coupler Metric:

- { - updateMetricsState('couplerMetric', e.detail || ''); - updateMetricsState('thresholdCoupler', 0); - setCouplerInputValue('0'); - }} - /> -
- -
- {(metricsState.couplerMetric) && (() => { - const couplerStats = metricsState.couplerMetric ? getMetricStatistics(calibrationData, metricsState.couplerMetric) : null; - - return ( -
- - {couplerStats && ( -
-
-
- - { - setCouplerInputValue(e.target.value); - const inputValue = parseFloat(e.target.value); - if (!isNaN(inputValue)) { - const isLowerBetter = metricsState.couplerMetric.includes("error"); - const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - const best = isLowerBetter ? couplerStats.worst : couplerStats.best; - const range = parseFloat(best) - parseFloat(worst); - - // Convert display value back to raw value - let rawValue = inputValue; - if (couplerStats.unit === 's') { - rawValue = inputValue / 1e6; // Convert μs back to s - } else if (couplerStats.unit === '' || couplerStats.unit === '%') { - rawValue = inputValue / 100; // Convert % back to decimal - } - - // Calculate slider position (0-1) - const sliderValue = (rawValue - parseFloat(worst)) / range; - const clampedSliderValue = Math.max(0, Math.min(1, sliderValue)); - - updateMetricsState('thresholdCoupler', clampedSliderValue); - updateMetricsState('thresholdCouplerValue', rawValue); - } - }} - /> - - {couplerStats.unit === 's' ? 'μs' : - (couplerStats.unit === '' || couplerStats.unit === '%') ? '%' : - couplerStats.unit} - -
-
-
{ - const isLowerBetter = metricsState.couplerMetric.includes("error"); - let worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - let best = isLowerBetter ? couplerStats.worst : couplerStats.best; - const colors = generateMetricGradient(worst, best, couplerStats.average); - const gradientStops = []; - for (let i = 0; i <= 10; i++) { - const index = Math.floor((i / 10) * (colors.length - 1)); - const percentage = (i / 10) * 100; - gradientStops.push(`${colors[index]} ${percentage}%`); - } - return `linear-gradient(to right, ${gradientStops.join(', ')})`; - })() - }}> - updateMetricsState('thresholdCoupler', parseFloat(e.target.value))} - className="absolute top-0 left-0 w-full h-full opacity-70 cursor-pointer slider" - style={{ - background: 'transparent', - appearance: 'none', - WebkitAppearance: 'none' - }} - /> -
-
- - Worst:
- {(() => { - const isLowerBetter = metricsState.couplerMetric.includes("error"); - const worst = isLowerBetter ? couplerStats.best : couplerStats.worst; - return formatMetricValue(worst, couplerStats.unit); - })()} -
- - - Best:
- {(() => { - const isLowerBetter = metricsState.couplerMetric.includes("error"); - const best = isLowerBetter ? couplerStats.worst : couplerStats.best; - return formatMetricValue(best, couplerStats.unit); - })()} -
-
-
- )} -
- ); - })()} -
- -
-
+ } {activeTab === "raw" &&
From d59cde672e643b751e3adf4c06d69f67e4640400 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 14:17:30 +0300 Subject: [PATCH 14/17] refactor --- .../StatusModal/CalibrationTable.jsx | 1 - .../StatusModal/RawDataSwitcher.jsx | 95 +++++++++++++++++++ src/components/StatusModal/SideBar.jsx | 95 +++---------------- 3 files changed, 107 insertions(+), 84 deletions(-) create mode 100644 src/components/StatusModal/RawDataSwitcher.jsx diff --git a/src/components/StatusModal/CalibrationTable.jsx b/src/components/StatusModal/CalibrationTable.jsx index d7e1629b0074..b9d7c5f3b661 100644 --- a/src/components/StatusModal/CalibrationTable.jsx +++ b/src/components/StatusModal/CalibrationTable.jsx @@ -6,7 +6,6 @@ import { formatMetricValue } from '../../utils/formatMetricValue'; export const CalibrationTable = (props) => { const { calibrationData, qubitSwitch, couplerSwitch, qubitMetricOptions, couplerMetricOptions } = props; - if (!calibrationData) return

No calibration data available

; const allMetrics = Object.keys(calibrationData); diff --git a/src/components/StatusModal/RawDataSwitcher.jsx b/src/components/StatusModal/RawDataSwitcher.jsx new file mode 100644 index 000000000000..81807f6f5ff7 --- /dev/null +++ b/src/components/StatusModal/RawDataSwitcher.jsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { CRadioGroup, CSwitch, CButton } from '@cscfi/csc-ui-react'; +import { getCurrentRawData, copyToClipboard, downloadRawData } from '../../utils/sidebarUtils'; + +export const RawDataSwitcher = (props) => { + + const { viewState, updateViewState, metricsState, updateMetricsState, + calibrationDataAll, deviceInfoData, deviceData, copySuccess, setCopySuccess } = props; + + // Copy to clipboard function + const handleCopyToClipboard = async () => { + const data = JSON.stringify(getCurrentRawData(viewState.rawDataType, calibrationDataAll, deviceInfoData), null, 2); + await copyToClipboard(data, setCopySuccess); + }; + + // Download as JSON file + const handleDownloadRawData = () => { + const data = JSON.stringify(getCurrentRawData(viewState.rawDataType, calibrationDataAll, deviceInfoData), null, 2); + downloadRawData(data, deviceData, viewState.rawDataType); + }; + + return ( +
+

Data Type:

+ updateViewState('rawDataType', e.detail)} + defaultValue={{ name: 'Calibration Data', value: 'calibration_data' }} + value={viewState.rawDataType} + inline={true} + + > + + {viewState.rawDataType.value === 'calibration_data' && +
+ updateViewState('tableView', e.detail)} + > + Table View + +
+ } + {viewState.tableView && viewState.rawDataType.value === 'calibration_data' && + <> +
+ { + updateViewState('qubitSwitch', e.detail) + updateViewState('couplerSwitch', !e.detail) + }} + > + Qubits + +
+
+ { + updateViewState('qubitSwitch', !e.detail) + updateViewState('couplerSwitch', e.detail) + }} + > + Couplers + +
+ + } +
+
+ + {copySuccess ? 'Copied!' : 'Copy'} + + + Download + +
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/StatusModal/SideBar.jsx b/src/components/StatusModal/SideBar.jsx index 94b06afb716f..6f5aec080f50 100644 --- a/src/components/StatusModal/SideBar.jsx +++ b/src/components/StatusModal/SideBar.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { DeviceStatus } from './DeviceStatus'; import { MetricSwitcher } from './MetricSwitcher'; +import { RawDataSwitcher } from './RawDataSwitcher'; import { CRadioGroup, CSwitch, CButton } from '@cscfi/csc-ui-react'; import { getCurrentRawData, copyToClipboard, downloadRawData, getMetricStatistics } from '../../utils/sidebarUtils'; @@ -26,18 +27,6 @@ export const SideBar = (props) => { const [copySuccess, setCopySuccess] = useState(false); - // Copy to clipboard function - const handleCopyToClipboard = async () => { - const data = JSON.stringify(getCurrentRawData(viewState.rawDataType, calibrationDataAll, deviceInfoData), null, 2); - await copyToClipboard(data, setCopySuccess); - }; - - // Download as JSON file - const handleDownloadRawData = () => { - const data = JSON.stringify(getCurrentRawData(viewState.rawDataType, calibrationDataAll, deviceInfoData), null, 2); - downloadRawData(data, deviceData, viewState.rawDataType); - }; - // Calculate threshold values when dependencies change useEffect(() => { if (metricsState.qubitMetric && calibrationData) { @@ -97,77 +86,17 @@ export const SideBar = (props) => { /> } {activeTab === "raw" && -
-

Data Type:

- updateViewState('rawDataType', e.detail)} - defaultValue={{ name: 'Calibration Data', value: 'calibration_data' }} - value={viewState.rawDataType} - inline={true} - - > - - {viewState.rawDataType.value === 'calibration_data' && -
- updateViewState('tableView', e.detail)} - > - Table View - -
- } - {viewState.tableView && viewState.rawDataType.value === 'calibration_data' && - <> -
- { - updateMetricsState('qubitSwitch', e.detail) - updateMetricsState('couplerSwitch', !e.detail) - }} - > - Qubits - -
-
- { - updateMetricsState('qubitSwitch', !e.detail) - updateMetricsState('couplerSwitch', e.detail) - }} - > - Couplers - -
- - } -
-
- - {copySuccess ? 'Copied!' : 'Copy'} - - - Download - -
-
-
+ }
) From 0c2ae05941c9e70f53d1af9b758bf4abb2ab6db1 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 14:18:29 +0300 Subject: [PATCH 15/17] move files --- src/components/{ => StatusModal}/StatusModal.jsx | 2 +- .../{ => StatusModal}/StatusModalConent.jsx | 12 ++++++------ src/components/{ => StatusModal}/StatusOverview.jsx | 0 3 files changed, 7 insertions(+), 7 deletions(-) rename src/components/{ => StatusModal}/StatusModal.jsx (96%) rename src/components/{ => StatusModal}/StatusModalConent.jsx (96%) rename src/components/{ => StatusModal}/StatusOverview.jsx (100%) diff --git a/src/components/StatusModal.jsx b/src/components/StatusModal/StatusModal.jsx similarity index 96% rename from src/components/StatusModal.jsx rename to src/components/StatusModal/StatusModal.jsx index 0f2ca50b10a7..7493bf6a7e37 100644 --- a/src/components/StatusModal.jsx +++ b/src/components/StatusModal/StatusModal.jsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react' import { CModal } from '@cscfi/csc-ui-react'; -import { ModalContent } from './StatusModalConent'; +import { ModalContent } from '../StatusModalConent'; export default function useWindowSize() { const [width, setWidth] = useState( diff --git a/src/components/StatusModalConent.jsx b/src/components/StatusModal/StatusModalConent.jsx similarity index 96% rename from src/components/StatusModalConent.jsx rename to src/components/StatusModal/StatusModalConent.jsx index 89c4da3e6e00..cd28aaa8777d 100644 --- a/src/components/StatusModalConent.jsx +++ b/src/components/StatusModal/StatusModalConent.jsx @@ -1,12 +1,12 @@ import React from 'react' import { useState } from 'react' -import { useCalibration } from '../hooks/useCalibration'; -import { useDeviceInfo } from '../hooks/useDeviceInfo'; -import { HelmiLayout } from './QcLayouts/Helmi'; -import { Q50Layout } from './QcLayouts/Q50'; +import { useCalibration } from '../../hooks/useCalibration'; +import { useDeviceInfo } from '../../hooks/useDeviceInfo'; +import { HelmiLayout } from '../QcLayouts/Helmi'; +import { Q50Layout } from '../QcLayouts/Q50'; import { Overview } from './StatusOverview'; -import { CalibrationTable } from './StatusModal/CalibrationTable'; -import { SideBar } from './StatusModal/SideBar'; +import { CalibrationTable } from './CalibrationTable'; +import { SideBar } from './SideBar'; import { CCard, CCardTitle, CCardContent, CCardActions, CButton, CTabs, CTab, CTabItems, CTabItem diff --git a/src/components/StatusOverview.jsx b/src/components/StatusModal/StatusOverview.jsx similarity index 100% rename from src/components/StatusOverview.jsx rename to src/components/StatusModal/StatusOverview.jsx From 4f367490ece47704ea66ca1849f945457a3f41a1 Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 14:18:55 +0300 Subject: [PATCH 16/17] remove unused --- src/components/StatusIndicator.jsx | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 src/components/StatusIndicator.jsx diff --git a/src/components/StatusIndicator.jsx b/src/components/StatusIndicator.jsx deleted file mode 100644 index ce867a89014e..000000000000 --- a/src/components/StatusIndicator.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react" - -export const StatusIndicator = ({ isUp, describe }) => { - const indicatorColor = status => { - if (status == null) return 'gray' - else return status ? 'green' : 'red' - } - - const indicator = -
- - const indicatorWithDescription = - <> - {indicator} Service is {isUp ? 'available' : 'down'} - - - return describe ? indicatorWithDescription : indicator -} From 901eca501abb43fa9ef401b3034438c885abcdbf Mon Sep 17 00:00:00 2001 From: Joonas Nivala Date: Wed, 30 Jul 2025 14:20:35 +0300 Subject: [PATCH 17/17] fix imports --- src/components/ServiceStatus.jsx | 2 +- src/components/StatusModal/StatusModal.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ServiceStatus.jsx b/src/components/ServiceStatus.jsx index 9b727903bff3..da3270d16b4d 100644 --- a/src/components/ServiceStatus.jsx +++ b/src/components/ServiceStatus.jsx @@ -3,7 +3,7 @@ import React, { useState } from 'react' import { useStatus } from '../hooks/useStatus' import { mdiInformation, mdiClose, mdiAlert } from '@mdi/js'; import { CCard, CCardTitle, CCardContent, CIcon } from '@cscfi/csc-ui-react'; -import { StatusModal } from './StatusModal'; +import { StatusModal } from './StatusModal/StatusModal'; const StatusCard = (props) => { const isOnline = props.health; diff --git a/src/components/StatusModal/StatusModal.jsx b/src/components/StatusModal/StatusModal.jsx index 7493bf6a7e37..0f2ca50b10a7 100644 --- a/src/components/StatusModal/StatusModal.jsx +++ b/src/components/StatusModal/StatusModal.jsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react' import { CModal } from '@cscfi/csc-ui-react'; -import { ModalContent } from '../StatusModalConent'; +import { ModalContent } from './StatusModalConent'; export default function useWindowSize() { const [width, setWidth] = useState(