diff --git a/web/frontend/src/Analysis.root.svelte b/web/frontend/src/Analysis.root.svelte index ada3944..3b7272d 100644 --- a/web/frontend/src/Analysis.root.svelte +++ b/web/frontend/src/Analysis.root.svelte @@ -37,14 +37,12 @@ import ScatterPlot from "./generic/plots/Scatter.svelte"; import RooflineHeatmap from "./generic/plots/RooflineHeatmap.svelte"; - const { query: initq } = init(); - - export let filterPresets; + /* Svelte 5 Props */ + let { filterPresets } = $props(); // By default, look at the jobs of the last 6 hours: if (filterPresets?.startTime == null) { if (filterPresets == null) filterPresets = {}; - let now = new Date(Date.now()); let hourAgo = new Date(now); hourAgo.setHours(hourAgo.getHours() - 6); @@ -54,27 +52,12 @@ }; } - let cluster; - let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the - let jobFilters = []; - let rooflineMaxY; - let colWidth1, colWidth2; - let numBins = 50; - let maxY = -1; - + /* Const Init */ + const { query: initq } = init(); + const client = getContextClient(); const initialized = getContext("initialized"); const globalMetrics = getContext("globalMetrics"); const ccconfig = getContext("cc-config"); - - let metricsInHistograms = ccconfig.analysis_view_histogramMetrics, - metricsInScatterplots = ccconfig.analysis_view_scatterPlotMetrics; - - $: metrics = [ - ...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()]), - ]; - - $: clusterName = cluster?.name ? cluster.name : cluster; - const sortOptions = [ { key: "totalWalltime", label: "Walltime" }, { key: "totalNodeHours", label: "Node Hours" }, @@ -86,7 +69,22 @@ { key: "project", label: "Project ID" }, ]; - let sortSelection = + /* Var Init */ + let availableMetrics = []; + let metricUnits = {}; + let metricScopes = {}; + let rooflineMaxY; + let cluster; + let colWidth1, colWidth2; + let numBins = 50; + let maxY = -1; + + /* State Init */ + let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the + let jobFilters = $state([]); + let metricsInHistograms = $state(ccconfig.analysis_view_histogramMetrics) + let metricsInScatterplots = $state(ccconfig.analysis_view_scatterPlotMetrics) + let sortSelection = $state( sortOptions.find( (option) => option.key == @@ -94,17 +92,20 @@ ) || sortOptions.find( (option) => option.key == ccconfig.analysis_view_selectedTopCategory, - ); - let groupSelection = - groupOptions.find( - (option) => - option.key == - ccconfig[`analysis_view_selectedTopEntity:${filterPresets.cluster}`], - ) || - groupOptions.find( - (option) => option.key == ccconfig.analysis_view_selectedTopEntity, - ); + ) + ); + let groupSelection = $state( + groupOptions.find( + (option) => + option.key == + ccconfig[`analysis_view_selectedTopEntity:${filterPresets.cluster}`], + ) || + groupOptions.find( + (option) => option.key == ccconfig.analysis_view_selectedTopEntity, + ) + ); + /* Init Function */ getContext("on-init")(({ data }) => { if (data != null) { cluster = data.clusters.find((c) => c.name == filterPresets.cluster); @@ -121,120 +122,144 @@ } }); - const client = getContextClient(); + /* Derived Vars */ + let clusterName = $derived(cluster?.name ? cluster.name : cluster); + let metrics = $derived( + [...new Set([...metricsInHistograms, ...metricsInScatterplots.flat()])] + ); - $: statsQuery = queryStore({ - client: client, - query: gql` - query ($jobFilters: [JobFilter!]!) { - stats: jobsStatistics(filter: $jobFilters) { - totalJobs - shortJobs - totalWalltime - totalNodeHours - totalCoreHours - totalAccHours - histDuration { - count - value - } - histNumCores { - count - value + let statsQuery = $derived( + queryStore({ + client: client, + query: gql` + query ($jobFilters: [JobFilter!]!) { + stats: jobsStatistics(filter: $jobFilters) { + totalJobs + shortJobs + totalWalltime + totalNodeHours + totalCoreHours + totalAccHours + histDuration { + count + value + } + histNumCores { + count + value + } } } - } - `, - variables: { jobFilters }, - }); + `, + variables: { jobFilters }, + }) + ); - $: topQuery = queryStore({ - client: client, - query: gql` - query ( - $jobFilters: [JobFilter!]! - $paging: PageRequest! - $sortBy: SortByAggregate! - $groupBy: Aggregate! - ) { - topList: jobsStatistics( - filter: $jobFilters - page: $paging - sortBy: $sortBy - groupBy: $groupBy + let topQuery = $derived( + queryStore({ + client: client, + query: gql` + query ( + $jobFilters: [JobFilter!]! + $paging: PageRequest! + $sortBy: SortByAggregate! + $groupBy: Aggregate! ) { - id - name - totalWalltime - totalNodeHours - totalCoreHours - totalAccHours + topList: jobsStatistics( + filter: $jobFilters + page: $paging + sortBy: $sortBy + groupBy: $groupBy + ) { + id + name + totalWalltime + totalNodeHours + totalCoreHours + totalAccHours + } } - } - `, - variables: { - jobFilters, - paging: { itemsPerPage: 10, page: 1 }, - sortBy: sortSelection.key.toUpperCase(), - groupBy: groupSelection.key.toUpperCase(), - }, - }); + `, + variables: { + jobFilters, + paging: { itemsPerPage: 10, page: 1 }, + sortBy: sortSelection.key.toUpperCase(), + groupBy: groupSelection.key.toUpperCase(), + }, + }) + ); // Note: Different footprints than those saved in DB per Job -> Caused by Legacy Naming - $: footprintsQuery = queryStore({ - client: client, - query: gql` - query ($jobFilters: [JobFilter!]!, $metrics: [String!]!) { - footprints: jobsFootprints(filter: $jobFilters, metrics: $metrics) { - timeWeights { - nodeHours - accHours - coreHours - } - metrics { - metric - data + let footprintsQuery = $derived( + queryStore({ + client: client, + query: gql` + query ($jobFilters: [JobFilter!]!, $metrics: [String!]!) { + footprints: jobsFootprints(filter: $jobFilters, metrics: $metrics) { + timeWeights { + nodeHours + accHours + coreHours + } + metrics { + metric + data + } } } - } - `, - variables: { jobFilters, metrics }, - }); - - $: rooflineQuery = queryStore({ - client: client, - query: gql` - query ( - $jobFilters: [JobFilter!]! - $rows: Int! - $cols: Int! - $minX: Float! - $minY: Float! - $maxX: Float! - $maxY: Float! - ) { - rooflineHeatmap( - filter: $jobFilters - rows: $rows - cols: $cols - minX: $minX - minY: $minY - maxX: $maxX - maxY: $maxY - ) - } - `, - variables: { - jobFilters, - rows: 50, - cols: 50, - minX: 0.01, - minY: 1, - maxX: 1000, - maxY, - }, + `, + variables: { jobFilters, metrics }, + }) + ); + + let rooflineQuery = $derived( + queryStore({ + client: client, + query: gql` + query ( + $jobFilters: [JobFilter!]! + $rows: Int! + $cols: Int! + $minX: Float! + $minY: Float! + $maxX: Float! + $maxY: Float! + ) { + rooflineHeatmap( + filter: $jobFilters + rows: $rows + cols: $cols + minX: $minX + minY: $minY + maxX: $maxX + maxY: $maxY + ) + } + `, + variables: { + jobFilters, + rows: 50, + cols: 50, + minX: 0.01, + minY: 1, + maxX: 1000, + maxY, + }, + }) + ); + + /* Reactive Effects */ + $effect(() => { + loadMetrics($initialized) + }); + $effect(() => { + updateEntityConfiguration(groupSelection.key); + }); + $effect(() => { + updateCategoryConfiguration(sortSelection.key); }); + /* Functions */ const updateConfigurationMutation = ({ name, value }) => { return mutationStore({ client: client, @@ -287,9 +312,6 @@ } } - let availableMetrics = []; - let metricUnits = {}; - let metricScopes = {}; function loadMetrics(isInitialized) { if (!isInitialized) return availableMetrics = [...globalMetrics.filter((gm) => gm?.availability.find((av) => av.cluster == cluster.name))] @@ -299,10 +321,7 @@ } } - $: loadMetrics($initialized) - $: updateEntityConfiguration(groupSelection.key); - $: updateCategoryConfiguration(sortSelection.key); - + /* On Mount */ onMount(() => filterComponent.updateFilters()); @@ -329,7 +348,7 @@ {filterPresets} disableClusterSelection={true} startTimeQuickSelect={true} - on:update-filters={({ detail }) => { + applyFilters={(detail) => { jobFilters = detail.filters; }} /> diff --git a/web/frontend/src/List.root.svelte b/web/frontend/src/List.root.svelte index 1a88b36..bcebd14 100644 --- a/web/frontend/src/List.root.svelte +++ b/web/frontend/src/List.root.svelte @@ -31,10 +31,8 @@ } from "./generic/utils.js"; import Filters from "./generic/Filters.svelte"; - const {} = init(); - - export let type; - export let filterPresets; + /* Svelte 5 Props */ + let { type, filterPresets } = $props(); // By default, look at the jobs of the last 30 days: if (filterPresets?.startTime == null) { @@ -51,35 +49,38 @@ "Invalid list type provided!", ); - let filterComponent; // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the - let jobFilters = []; - let nameFilter = ""; - let sorting = { field: "totalJobs", direction: "down" }; - + /* Const Init */ + const {} = init(); const client = getContextClient(); - $: stats = queryStore({ - client: client, - query: gql` - query($jobFilters: [JobFilter!]!) { - rows: jobsStatistics(filter: $jobFilters, groupBy: ${type}) { - id - name - totalJobs - totalWalltime - totalCoreHours - totalAccHours - } - }`, - variables: { jobFilters }, - }); - function changeSorting(event, field) { - let target = event.target; - while (target.tagName != "BUTTON") target = target.parentElement; + /* State Init*/ + let filterComponent = $state(); // see why here: https://stackoverflow.com/questions/58287729/how-can-i-export-a-function-from-a-svelte-component-that-changes-a-value-in-the + let jobFilters = $state([]); + let nameFilter = $state(""); + let sorting = $state({ field: "totalJobs", direction: "down" }); - let direction = target.children[0].className.includes("up") ? "down" : "up"; - target.children[0].className = `bi-sort-numeric-${direction}`; - sorting = { field, direction }; + /* Derived Vars */ + let stats = $derived( + queryStore({ + client: client, + query: gql` + query($jobFilters: [JobFilter!]!) { + rows: jobsStatistics(filter: $jobFilters, groupBy: ${type}) { + id + name + totalJobs + totalWalltime + totalCoreHours + totalAccHours + } + }`, + variables: { jobFilters }, + }) + ); + + /* Functions */ + function changeSorting(field) { + sorting = { field, direction: sorting?.direction == "down" ? "up" : "down" }; } function sort(stats, sorting, nameFilter) { @@ -87,10 +88,10 @@ ? (a, b) => b.id.localeCompare(a.id) : (a, b) => a.id.localeCompare(b.id) - // "-50": Forces empty strings to the end of the list + // Force empty or undefined strings to the end of the list const nameCmp = sorting.direction == "up" - ? (a, b) => (a.name == '') ? -50 : b.name.localeCompare(a.name) - : (a, b) => (b.name == '') ? -50 : a.name.localeCompare(b.name) + ? (a, b) => !a?.name ? 1 : (!b?.name ? -1 : (b.name.localeCompare(a.name))) + : (a, b) => !a?.name ? 1 : (!b?.name ? -1 : (a.name.localeCompare(b.name))) const intCmp = sorting.direction == "up" ? (a, b) => a[sorting.field] - b[sorting.field] @@ -105,6 +106,7 @@ } } + /* On Mount */ onMount(() => filterComponent.updateFilters()); @@ -129,7 +131,7 @@ {filterPresets} startTimeQuickSelect={true} menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up" - on:update-filters={({ detail }) => { + applyFilters={(detail) => { jobFilters = detail.filters; }} /> @@ -147,9 +149,14 @@ {#if type == "USER"} @@ -158,9 +165,13 @@ {/if} @@ -169,9 +180,14 @@