complete review of context initialization and access, streamlining

This commit is contained in:
Christoph Kluge
2026-02-06 17:51:39 +01:00
parent a8d385a1ee
commit c43d4a0f16
21 changed files with 365 additions and 318 deletions

View File

@@ -39,10 +39,6 @@
} = $props();
/* Const Init */
const ccconfig = getContext("cc-config");
const initialized = getContext("initialized");
const globalMetrics = getContext("globalMetrics");
const usePaging = ccconfig?.jobList_usePaging || false;
const jobInfoColumnWidth = 250;
const client = getContextClient();
const query = gql`
@@ -100,11 +96,18 @@
let headerPaddingTop = $state(0);
let jobs = $state([]);
let page = $state(1);
let itemsPerPage = $state(usePaging ? (ccconfig?.jobList_jobsPerPage || 10) : 10);
let triggerMetricRefresh = $state(false);
let tableWidth = $state(0);
/* Derived */
const initialized = $derived(getContext("initialized") || false);
const ccconfig = $derived(initialized ? getContext("cc-config") : null);
const globalMetrics = $derived(initialized ? getContext("globalMetrics") : null);
const clusterInfos = $derived(initialized ? getContext("clusters"): null);
const resampleConfig = $derived(initialized ? getContext("resampling") : null);
const usePaging = $derived(ccconfig?.jobList_usePaging || false);
let itemsPerPage = $derived(usePaging ? (ccconfig?.jobList_jobsPerPage || 10) : 10);
let filter = $derived([...filterBuffer]);
let paging = $derived({ itemsPerPage, page });
const plotWidth = $derived.by(() => {
@@ -274,7 +277,7 @@
style="width: {plotWidth}px; padding-top: {headerPaddingTop}px"
>
{metric}
{#if $initialized}
{#if initialized}
({getUnit(metric)})
{/if}
</th>
@@ -292,7 +295,8 @@
</tr>
{:else}
{#each jobs as job (job.id)}
<JobListRow {triggerMetricRefresh} {job} {metrics} {plotWidth} {showFootprint} previousSelect={selectedJobs.includes(job.id)}
<JobListRow {triggerMetricRefresh} {job} {metrics} {plotWidth} {showFootprint} {globalMetrics} {clusterInfos} {resampleConfig}
previousSelect={selectedJobs.includes(job.id)}
selectJob={(detail) => selectedJobs = [...selectedJobs, detail]}
unselectJob={(detail) => selectedJobs = selectedJobs.filter(item => item !== detail)}
/>

View File

@@ -30,11 +30,10 @@
setFilter
} = $props();
/* Const Init */
const clusters = getContext("clusters");
const initialized = getContext("initialized");
/* Derived */
const initialized = $derived(getContext("initialized") || false);
const clusterInfos = $derived($initialized ? getContext("clusters") : null);
let pendingCluster = $derived(presetCluster);
let pendingPartition = $derived(presetPartition);
</script>
@@ -56,7 +55,7 @@
>
Any Cluster
</ListGroupItem>
{#each clusters as cluster}
{#each clusterInfos as cluster}
<ListGroupItem
disabled={disableClusterSelection}
active={pendingCluster == cluster.name}
@@ -80,7 +79,7 @@
>
Any Partition
</ListGroupItem>
{#each clusters?.find((c) => c.name == pendingCluster)?.partitions as partition}
{#each clusterInfos?.find((c) => c.name == pendingCluster)?.partitions as partition}
<ListGroupItem
active={pendingPartition == partition}
onclick={() => (pendingPartition = partition)}

View File

@@ -42,8 +42,8 @@
contains: "Contains",
}
const findMaxNumAccels = (clusters) =>
clusters.reduce(
const findMaxNumAccels = (infos) =>
infos.reduce(
(max, cluster) =>
Math.max(
max,
@@ -56,8 +56,8 @@
);
// Limited to Single-Node Thread Count
const findMaxNumHWThreadsPerNode = (clusters) =>
clusters.reduce(
const findMaxNumHWThreadsPerNode = (infos) =>
infos.reduce(
(max, cluster) =>
Math.max(
max,
@@ -92,8 +92,8 @@
let threadState = $derived(presetNumHWThreads);
let accState = $derived(presetNumAccelerators);
const clusters = $derived(getContext("clusters"));
const initialized = $derived(getContext("initialized"));
const initialized = $derived(getContext("initialized") || false);
const clusterInfos = $derived($initialized ? getContext("clusters") : null);
// Is Selection Active
const nodesActive = $derived(!(JSON.stringify(nodesState) === JSON.stringify({ from: 1, to: maxNumNodes })));
const threadActive = $derived(!(JSON.stringify(threadState) === JSON.stringify({ from: 1, to: maxNumHWThreads })));
@@ -109,12 +109,12 @@
$effect(() => {
if ($initialized) {
if (activeCluster != null) {
const { subClusters } = clusters.find((c) => c.name == activeCluster);
const { subClusters } = clusterInfos.find((c) => c.name == activeCluster);
maxNumAccelerators = findMaxNumAccels([{ subClusters }]);
maxNumHWThreads = findMaxNumHWThreadsPerNode([{ subClusters }]);
} else if (clusters.length > 0) {
maxNumAccelerators = findMaxNumAccels(clusters);
maxNumHWThreads = findMaxNumHWThreadsPerNode(clusters);
} else if (clusterInfos.length > 0) {
maxNumAccelerators = findMaxNumAccels(clusterInfos);
maxNumHWThreads = findMaxNumHWThreadsPerNode(clusterInfos);
}
}
});

View File

@@ -31,8 +31,8 @@
} = $props();
/* Derived */
const allTags = $derived(getContext("tags"))
const initialized = $derived(getContext("initialized"))
const initialized = $derived(getContext("initialized") || false)
const allTags = $derived($initialized ? getContext("tags") : [])
/* State Init */
let searchTerm = $state("");

View File

@@ -18,8 +18,8 @@
} = $props();
/* Derived */
const allTags = $derived(getContext('tags'));
const initialized = $derived(getContext('initialized'));
const initialized = $derived(getContext('initialized') || false);
const allTags = $derived($initialized ? getContext('tags') : []);
/* Effects */
$effect(() => {

View File

@@ -48,8 +48,6 @@
const client = getContextClient();
/* State Init */
let initialized = getContext("initialized")
let allTags = getContext("tags")
let newTagType = $state("");
let newTagName = $state("");
let filterTerm = $state("");
@@ -57,10 +55,13 @@
let isOpen = $state(false);
/* Derived */
const initialized = $derived(getContext("initialized") || false );
let allTags = $derived(initialized ? getContext("tags") : [])
let newTagScope = $derived(username);
const isAdmin = $derived((roles && authlevel == roles.admin));
const isSupport = $derived((roles && authlevel == roles.support));
const allTagsFiltered = $derived(($initialized, jobTags, fuzzySearchTags(filterTerm, allTags))); // $init und JobTags only for triggering react
const allTagsFiltered = $derived((initialized, jobTags, fuzzySearchTags(filterTerm, allTags))); // $init und JobTags only for triggering react
const usedTagsFiltered = $derived(matchJobTags(jobTags, allTagsFiltered, 'used', isAdmin, isSupport));
const unusedTagsFiltered = $derived(matchJobTags(jobTags, allTagsFiltered, 'unused', isAdmin, isSupport));

View File

@@ -11,11 +11,13 @@
- `triggerMetricRefresh Bool?`: If changed to true from upstream, will trigger metric query [Default: false]
- `selectJob Func`: The callback function to select a job for comparison
- `unselectJob Func`: The callback function to unselect a job from comparison
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `clusterInfos [Obj]`: Includes the backend supplied cluster topology
- `resampleConfig [Obj]`: Includes the backend supplied resampling info
-->
<script>
import { queryStore, gql, getContextClient } from "@urql/svelte";
import { getContext } from "svelte";
import { Card, Spinner } from "@sveltestrap/sveltestrap";
import { maxScope, checkMetricDisabled } from "../utils.js";
import JobInfo from "./JobInfo.svelte";
@@ -33,13 +35,13 @@
triggerMetricRefresh = false,
selectJob,
unselectJob,
globalMetrics,
clusterInfos,
resampleConfig
} = $props();
/* Const Init */
const client = getContextClient();
const cluster = getContext("clusters");
const resampleConfig = getContext("resampling") || null;
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
const query = gql`
query ($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!, $selectedResolution: Int) {
jobMetrics(id: $id, metrics: $metrics, scopes: $scopes, resolution: $selectedResolution) {
@@ -73,11 +75,11 @@
`;
/* State Init */
let selectedResolution = $state(resampleDefault);
let zoomStates = $state({});
let thresholdStates = $state({});
/* Derived */
const resampleDefault = $derived(resampleConfig ? Math.max(...resampleConfig.resolutions) : 0);
const jobId = $derived(job?.id);
const scopes = $derived.by(() => {
if (job.numNodes == 1) {
@@ -87,6 +89,8 @@
return ["node"];
};
});
let selectedResolution = $derived(resampleDefault);
let isSelected = $derived(previousSelect);
let metricsQuery = $derived(queryStore({
client: client,
@@ -94,6 +98,7 @@
variables: { id: jobId, metrics, scopes, selectedResolution },
})
);
const refinedData = $derived($metricsQuery?.data?.jobMetrics ? sortAndSelectScope($metricsQuery.data.jobMetrics) : []);
/* Effects */
@@ -160,6 +165,7 @@
return {
name: jobMetric.data.name,
disabled: checkMetricDisabled(
globalMetrics,
jobMetric.data.name,
job.cluster,
job.subCluster,
@@ -220,7 +226,7 @@
series={metric.data.metric.series}
statisticsSeries={metric.data.metric.statisticsSeries}
metric={metric.data.name}
cluster={cluster.find((c) => c.name == job.cluster)}
cluster={clusterInfos.find((c) => c.name == job.cluster)}
subCluster={job.subCluster}
isShared={job.shared != "none"}
numhwthreads={job.numHWThreads}

View File

@@ -6,6 +6,7 @@
- `ìsOpen Bool`: Is selection opened [Bindable]
- `configName String`: The config id string to be updated in database on selection change
- `presetSelectedHistograms [String]`: The currently selected metrics to display as histogram
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `applyChange Func`: The callback function to apply current selection
-->
@@ -24,10 +25,11 @@
/* Svelte 5 Props */
let {
cluster,
cluster = "",
isOpen = $bindable(),
configName,
presetSelectedHistograms,
globalMetrics,
applyChange
} = $props();
@@ -42,11 +44,11 @@
function loadHistoMetrics(thisCluster) {
// isInit Check Removed: Parent Component has finished Init-Query: Globalmetrics available here.
if (!thisCluster) {
return getContext("globalMetrics")
return globalMetrics
.filter((gm) => gm?.footprint)
.map((fgm) => { return fgm.name })
} else {
return getContext("globalMetrics")
return globalMetrics
.filter((gm) => gm?.availability.find((av) => av.cluster == thisCluster))
.filter((agm) => agm?.footprint)
.map((afgm) => { return afgm.name })

View File

@@ -9,13 +9,12 @@
- `cluster String?`: The currently selected cluster [Default: null]
- `subCluster String?`: The currently selected subCluster [Default: null]
- `footprintSelect Bool?`: Render checkbox for footprint display in upstream component [Default: false]
- `preInitialized Bool?`: If the parent component has a dedicated call to init() [Default: false]
- `configName String`: The config key for the last saved selection (constant)
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `applyMetrics Func`: The callback function to apply current selection
-->
<script>
import { getContext } from "svelte";
import {
Modal,
ModalBody,
@@ -35,14 +34,12 @@
cluster = null,
subCluster = null,
footprintSelect = false,
preInitialized = false, // Job View is Pre-Init'd: $initialized "alone" store returns false
configName,
globalMetrics,
applyMetrics
} = $props();
/* Const Init */
const globalMetrics = getContext("globalMetrics");
const initialized = getContext("initialized");
const client = getContextClient();
const updateConfigurationMutation = ({ name, value }) => {
return mutationStore({
@@ -58,27 +55,23 @@
/* State Init */
let pendingShowFootprint = $state(!!showFootprint);
let listedMetrics = $state([]);
let columnHovering = $state(null);
/* Derives States */
let pendingMetrics = $derived(presetMetrics);
const allMetrics = $derived(loadAvailable(preInitialized || $initialized));
const allMetrics = $derived(loadAvailable(globalMetrics));
let pendingMetrics = $derived(presetMetrics || []);
let listedMetrics = $derived([...presetMetrics, ...allMetrics.difference(new Set(presetMetrics))]); // List (preset) active metrics first, then list inactives
/* Reactive Effects */
$effect(() => {
totalMetrics = allMetrics?.size || 0;
});
$effect(() => {
listedMetrics = [...presetMetrics, ...allMetrics.difference(new Set(presetMetrics))]; // List (preset) active metrics first, then list inactives
});
/* Functions */
function loadAvailable(init) {
function loadAvailable(gms) {
const availableMetrics = new Set();
if (init) {
for (let gm of globalMetrics) {
if (gms) {
for (let gm of gms) {
if (!cluster) {
availableMetrics.add(gm.name)
} else {
@@ -90,7 +83,7 @@
}
}
}
return availableMetrics
return availableMetrics;
}
function printAvailability(metric, cluster) {

View File

@@ -5,11 +5,11 @@
- `presetSorting Object?`: The latest sort selection state
- Default { field: "startTime", type: "col", order: "DESC" }
- `isOpen Bool?`: Is modal opened [Bindable, Default: false]
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
- `applySorting Func`: The callback function to apply current selection
-->
<script>
import { getContext, onMount } from "svelte";
import {
Icon,
Button,
@@ -25,12 +25,11 @@
let {
isOpen = $bindable(false),
presetSorting = { field: "startTime", type: "col", order: "DESC" },
globalMetrics,
applySorting
} = $props();
/* Const Init */
const initialized = getContext("initialized");
const globalMetrics = getContext("globalMetrics");
const fixedSortables = $state([
{ field: "startTime", type: "col", text: "Start Time (Default)", order: "DESC" },
{ field: "duration", type: "col", text: "Duration", order: "DESC" },
@@ -42,22 +41,11 @@
/* State Init */
let activeColumnIdx = $state(0);
let metricSortables = $state([]);
/* Derived */
let sorting = $derived({...presetSorting})
let sortableColumns = $derived([...fixedSortables, ...metricSortables]);
/* Effect */
$effect(() => {
if ($initialized) {
loadMetricSortables();
};
});
/* Functions */
function loadMetricSortables() {
metricSortables = globalMetrics.map((gm) => {
let metricSortables = $derived.by(() => {
return globalMetrics.map((gm) => {
if (gm?.footprint) {
return {
field: gm.name + '_' + gm.footprint,
@@ -68,8 +56,10 @@
}
return null
}).filter((r) => r != null)
};
});
let sortableColumns = $derived([...fixedSortables, ...metricSortables]);
/* Functions */
function loadActiveIndex() {
activeColumnIdx = sortableColumns.findIndex(
(col) => col.field == sorting.field,

View File

@@ -302,19 +302,17 @@ export function stickyHeader(datatableHeaderSelector, updatePading) {
onDestroy(() => document.removeEventListener("scroll", onscroll));
}
export function checkMetricDisabled(m, c, s) { // [m]etric, [c]luster, [s]ubcluster
const metrics = getContext("globalMetrics");
const available = metrics?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s)
export function checkMetricDisabled(gm, m, c, s) { // [g]lobal[m]etrics, [m]etric, [c]luster, [s]ubcluster
const available = gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s)
// Return inverse logic
return !available
}
export function checkMetricsDisabled(ma, c, s) { // [m]etric[a]rray, [c]luster, [s]ubcluster
export function checkMetricsDisabled(gm, ma, c, s) { // [g]lobal[m]etrics, [m]etric[a]rray, [c]luster, [s]ubcluster
let result = {};
const metrics = getContext("globalMetrics");
ma.forEach((m) => {
// Return named inverse logic: !available
result[m] = !(metrics?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s))
result[m] = !(gm?.find((gm) => gm.name === m)?.availability?.find((av) => av.cluster === c)?.subClusters?.includes(s))
});
return result
}