Merge branch 'feat/565-add-metric-tooltip' into metric-store-tickets

Resolve conflicts in the generated GraphQL code by regenerating it against
the merged schema. cc-lib v2.13.0 adds Tooltip to schema.MetricConfig and
schema.GlobalMetricListItem, so gqlgen now binds the tooltip field directly
and the hand-written globalMetricListItem/metricConfig resolvers introduced
on the tooltip branch are no longer needed.

Also migrate to the cc-lib v2.13.0 metric container types, which changed
from bare maps to structs carrying array-valued metric groups:

  schema.JobData          map -> {Metrics, Groups}
  schema.ScopedJobStats   map -> {Metrics, Groups}
  job.Statistics          map -> schema.JobStatisticsSet{Metrics, Groups}

Callers index .Metrics, return the zero struct instead of nil, and
deepCopy/DecodeJobStats now also carry the Groups payload through.
archive.GetStatistics returns the full JobStatisticsSet so group
statistics survive the round trip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-27 10:22:22 +02:00
co-authored by Claude Opus 5
35 changed files with 411 additions and 212 deletions
+10
View File
@@ -70,3 +70,13 @@ footer {
margin: 0rem 0.8rem;
white-space: nowrap;
}
/* Fix: prevent Sveltestrap collapseIn from leaving the navbar invisible
when prefers-reduced-motion causes transition-duration = 0ms and Svelte
skips calling tick(), leaving the node stuck in .collapsing (height:0). */
@media (prefers-reduced-motion: reduce) {
.navbar-collapse.collapsing {
height: auto !important;
overflow: visible !important;
}
}
+4
View File
@@ -60,6 +60,8 @@
let presetProject = $derived(filterPresets?.project ? filterPresets.project : "");
let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null);
let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null);
const maxClusters = $derived($initq?.data?.clusters?.length || 0);
const maxSubClusters = $derived($initq?.data?.clusters?.find((c) => c.name == selectedCluster)?.subClusters?.length || 0);
let metrics = $derived.by(() => {
if (thisInit && ccconfig) {
if (selectedCluster) {
@@ -243,6 +245,8 @@
presetMetrics={metrics}
cluster={selectedCluster}
subCluster={selectedSubCluster}
{maxClusters}
{maxSubClusters}
configName="metricConfig_jobListMetrics"
footprintSelect
{globalMetrics}
+3 -1
View File
@@ -63,8 +63,9 @@
let pendingHostnameFilter = $state("");
let isMetricsSelectionOpen = $state(false);
/* Derived Init Return */
/* Derived Init Returns */
const thisInit = $derived($initq?.data ? true : false);
const maxSubClusters = $derived($initq?.data?.clusters?.find((c) => c.name == cluster)?.subClusters?.length || 0);
/* Derived States */
const ccconfig = $derived(thisInit ? getContext("cc-config") : null);
@@ -268,6 +269,7 @@
{cluster}
{subCluster}
{globalMetrics}
maxSubClusters={subCluster? null: maxSubClusters}
configName="nodeList_selectedMetrics"
applyMetrics={(newMetrics) =>
selectedMetrics = [...newMetrics]
+4
View File
@@ -90,6 +90,8 @@
const shortDuration = $derived(ccconfig?.jobList_hideShortRunningJobs);
let selectedCluster = $derived(filterPresets?.cluster ? filterPresets.cluster : null);
let selectedSubCluster = $derived(filterPresets?.partition ? filterPresets.partition : null);
const maxClusters = $derived($initq?.data?.clusters?.length || 0);
const maxSubClusters = $derived($initq?.data?.clusters?.find((c) => c.name == selectedCluster)?.subClusters?.length || 0);
let metrics = $derived.by(() => {
if (thisInit && ccconfig) {
if (selectedCluster) {
@@ -531,6 +533,8 @@
presetMetrics={metrics}
cluster={selectedCluster}
subCluster={selectedSubCluster}
{maxClusters}
{maxSubClusters}
configName="metricConfig_jobListMetrics"
footprintSelect
{globalMetrics}
@@ -22,6 +22,8 @@
ModalFooter,
Button,
ListGroup,
Icon,
Tooltip
} from "@sveltestrap/sveltestrap";
import { gql, getContextClient, mutationStore } from "@urql/svelte";
@@ -33,6 +35,8 @@
presetMetrics = [],
cluster = null,
subCluster = null,
maxClusters = null,
maxSubClusters = null,
footprintSelect = false,
configName,
globalMetrics,
@@ -86,20 +90,45 @@
return availableMetrics;
}
function printAvailabilityCount(metric, cluster) {
const avail = globalMetrics.find((gm) => gm.name === metric)?.availability
if (avail) {
if (!cluster) {
return `${avail.length} / ${maxClusters} Cluster`
} else {
const subAvail = avail.find((av) => av.cluster === cluster)?.subClusters
if (subAvail) {
return `${subAvail.length} / ${maxSubClusters} SubCluster`
} else {
return `0 / ${maxSubClusters} SubCluster`
}
}
}
return `0 / ${maxClusters} Cluster`
}
function printAvailability(metric, cluster) {
const avail = globalMetrics.find((gm) => gm.name === metric)?.availability
if (avail) {
if (!cluster) {
return avail.map((av) => av.cluster).join(', ')
return avail.map((av) => av.cluster)
} else {
const subAvail = avail.find((av) => av.cluster === cluster)?.subClusters
if (subAvail) {
return subAvail.join(', ')
return subAvail
} else {
return `Not available for ${cluster}`
return [`Not available for ${cluster}`]
}
}
}
return [`Not available for ${cluster}`]
}
function printTooltip(metric) {
const toolt = globalMetrics.find((gm) => gm.name === metric)?.tooltip
if (toolt) {
return toolt
}
return ""
}
@@ -172,7 +201,7 @@
</script>
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
<ModalHeader>Configure columns (Metric availability shown)</ModalHeader>
<ModalHeader>Configure columns</ModalHeader>
<ModalBody>
<ListGroup>
{#if footprintSelect}
@@ -213,9 +242,34 @@
/>
{/if}
{metric}
<span style="float: right; text-align: justify;">
{printAvailability(metric, cluster)}
</span>
{#if maxClusters !== null || maxSubClusters !== null}
<span style="float: right;" class="ms-1">
<Button id={`${metric}-avail-info`} outline color="secondary" size="sm" class="ml-2">
<b>{ printAvailabilityCount(metric, cluster) }</b>
</Button>
<Tooltip target={`${metric}-avail-info`} placement="right">
<b>Availability</b>
<ul style="text-align: left; padding-left: 1.0rem; margin-bottom: 0.25rem;">
{#each printAvailability(metric, cluster) as avail}
<li>{avail}</li>
{/each}
</ul>
</Tooltip>
</span>
{/if}
{#if printTooltip(metric) !== ""}
<span style="float: right;">
<Button id={`${metric}-kind-info`} outline color="secondary" size="sm" class="ml-2">
<Icon name="info-square" />
</Button>
<Tooltip target={`${metric}-kind-info`} placement="right">
<b>Information</b>
<p style="text-align: left; margin-bottom: 0.25rem;">
{ printTooltip(metric) }
</p>
</Tooltip>
</span>
{/if}
</li>
{/each}
</ListGroup>
+1
View File
@@ -81,6 +81,7 @@ export function init(extraInitQuery = "") {
name
scope
footprint
tooltip
unit { base, prefix }
availability { cluster, subClusters }
}
+1 -1
View File
@@ -41,7 +41,7 @@
<DropdownToggle nav caret class="dropdown-item py-1 px-2">
{cn}
</DropdownToggle>
<DropdownMenu>
<DropdownMenu style="max-height:75vh; overflow-y: auto;">
<DropdownItem class="py-1 px-2"
href={item.href + cn}
>