2024-07-25 17:10:00 +02:00
<!--
@component Main user jobs list display component; displays job list and additional information for a given user
Properties:
- `user Object`: The GraphQL user object
- `filterPresets Object`: Optional predefined filter values
-->
2022-06-22 11:20:57 +02:00
< script >
2024-03-09 10:30:40 +01:00
import { onMount , getContext } from "svelte";
import {
Table,
Row,
Col,
Button,
2024-10-07 17:36:40 +02:00
ButtonGroup,
2024-03-09 10:30:40 +01:00
Icon,
Card,
Spinner,
} from "@sveltestrap/sveltestrap";
2024-07-26 12:34:18 +02:00
import {
queryStore,
gql,
getContextClient,
} from "@urql/svelte";
import {
init,
convert2uplot,
scramble,
scrambleNames,
} from "./generic/utils.js";
import JobList from "./generic/JobList.svelte";
import Filters from "./generic/Filters.svelte";
2024-10-02 17:48:46 +02:00
import PlotGrid from "./generic/PlotGrid.svelte";
2024-07-26 12:34:18 +02:00
import Histogram from "./generic/plots/Histogram.svelte";
import MetricSelection from "./generic/select/MetricSelection.svelte";
import HistogramSelection from "./generic/select/HistogramSelection.svelte";
import Sorting from "./generic/select/SortSelection.svelte";
import TextFilter from "./generic/helper/TextFilter.svelte"
import Refresher from "./generic/helper/Refresher.svelte";
2022-06-22 11:20:57 +02:00
2024-03-09 10:30:40 +01:00
const { query : initq } = init();
2022-06-22 11:20:57 +02:00
2024-03-09 10:30:40 +01:00
const ccconfig = getContext("cc-config");
2022-06-22 11:20:57 +02:00
2024-03-09 10:30:40 +01:00
export let user;
export let filterPresets;
2022-06-22 11:20:57 +02:00
2024-03-09 10:30:40 +01:00
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 jobList;
let jobFilters = [];
2024-10-07 17:36:40 +02:00
let matchedJobs = 0;
2024-07-22 15:41:33 +02:00
let sorting = { field : "startTime" , type : "col" , order : "DESC" } ,
2024-03-09 10:30:40 +01:00
isSortingOpen = false;
let metrics = ccconfig.plot_list_selectedMetrics,
isMetricsSelectionOpen = false;
let w1,
w2,
histogramHeight = 250,
isHistogramSelectionOpen = false;
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null;
let showFootprint = filterPresets.cluster
? !!ccconfig[`plot_list_showFootprint:${ filterPresets . cluster } `]
: !!ccconfig.plot_list_showFootprint;
2023-12-06 12:58:03 +01:00
2024-03-09 10:30:40 +01:00
$: metricsInHistograms = selectedCluster
? ccconfig[`user_view_histogramMetrics:${ selectedCluster } `] || []
: ccconfig.user_view_histogramMetrics || [];
2022-06-22 11:20:57 +02:00
2024-03-09 10:30:40 +01:00
const client = getContextClient();
$: stats = queryStore({
client: client,
query: gql`
query ($jobFilters: [JobFilter!]!, $metricsInHistograms: [String!]) {
jobsStatistics(filter: $jobFilters, metrics: $metricsInHistograms) {
totalJobs
shortJobs
totalWalltime
totalCoreHours
histDuration {
count
value
}
histNumNodes {
count
value
}
histMetrics {
metric
unit
2024-07-22 15:41:33 +02:00
stat
2024-03-09 10:30:40 +01:00
data {
min
max
count
bin
}
}
}
}
`,
variables: { jobFilters , metricsInHistograms } ,
});
2022-06-22 11:20:57 +02:00
2024-07-25 17:10:00 +02:00
onMount(() => filterComponent.updateFilters());
2022-06-22 11:20:57 +02:00
< / script >
2024-10-07 17:36:40 +02:00
<!-- ROW1: Status -->
{ #if $initq . fetching }
< Row >
2024-03-09 10:30:40 +01:00
< Col >
< Spinner / >
< / Col >
2024-10-07 17:36:40 +02:00
< / Row >
{ :else if $initq . error }
< Row >
< Col >
2024-03-09 10:30:40 +01:00
< Card body color = "danger" > { $initq . error . message } </ Card >
< / Col >
2024-10-07 17:36:40 +02:00
< / Row >
{ /if }
2024-03-09 10:30:40 +01:00
2024-10-07 17:36:40 +02:00
<!-- ROW2: Tools -->
< Row cols = {{ xs : 1 , md : 2 , lg : 4 }} class="mb-3" >
< Col lg = "2" class = "mb-2 mb-lg-0" >
< ButtonGroup class = "w-100" >
< Button outline color = "primary" on:click = {() => ( isSortingOpen = true )} >
< Icon name = "sort-up" / > Sorting
< / Button >
< Button
outline
color="primary"
on:click={() => ( isMetricsSelectionOpen = true )}
>
< Icon name = "graph-up" / > Metrics
< / Button >
< / ButtonGroup >
2024-03-09 10:30:40 +01:00
< / Col >
2024-10-07 17:36:40 +02:00
< Col lg = "4" xl = "6" class = "mb-1 mb-lg-0" >
2024-03-09 10:30:40 +01:00
< Filters
{ filterPresets }
2024-10-07 17:36:40 +02:00
{ matchedJobs }
2024-03-09 10:30:40 +01:00
startTimeQuickSelect={ true }
bind:this={ filterComponent }
2024-07-25 17:10:00 +02:00
on:update-filters={({ detail }) => {
2024-03-09 10:30:40 +01:00
jobFilters = [...detail.filters, { user : { eq : user.username } } ];
selectedCluster = jobFilters[0]?.cluster
? jobFilters[0].cluster.eq
: null;
2024-07-25 17:10:00 +02:00
jobList.queryJobs(jobFilters);
2024-03-09 10:30:40 +01:00
}}
/>
< / Col >
2024-10-07 17:36:40 +02:00
< Col lg = "3" xl = "2" class = "mb-2 mb-lg-0" >
2024-05-23 15:43:09 +02:00
< TextFilter
2024-07-25 17:10:00 +02:00
on:set-filter={({ detail }) => filterComponent . updateFilters ( detail )}
2024-05-23 15:43:09 +02:00
/>
< / Col >
2024-10-07 17:36:40 +02:00
< Col lg = "3" xl = "2" class = "mb-1 mb-lg-0" >
2024-07-25 17:10:00 +02:00
< Refresher on:refresh = {() => {
jobList.refreshJobs()
jobList.refreshAllMetrics()
}} />
2024-03-09 10:30:40 +01:00
< / Col >
< / Row >
2024-10-07 17:36:40 +02:00
<!-- ROW3: Base Information -->
< Row cols = {{ xs : 1 , md : 3 }} class="mb-2" >
2024-03-09 10:30:40 +01:00
{ #if $stats . error }
< Col >
< Card body color = "danger" > { $stats . error . message } </ Card >
2022-06-22 11:20:57 +02:00
< / Col >
2024-03-09 10:30:40 +01:00
{ :else if ! $stats . data }
< Col >
< Spinner secondary / >
2022-06-22 11:20:57 +02:00
< / Col >
2024-03-09 10:30:40 +01:00
{ : else }
2024-10-02 17:48:46 +02:00
< Col >
2024-03-09 10:30:40 +01:00
< Table >
< tbody >
< tr >
< th scope = "row" > Username< / th >
< td > { scrambleNames ? scramble ( user . username ) : user . username } </ td >
< / tr >
{ #if user . name }
< tr >
< th scope = "row" > Name< / th >
< td > { scrambleNames ? scramble ( user . name ) : user . name } </ td >
< / tr >
{ /if }
{ #if user . email }
< tr >
< th scope = "row" > Email< / th >
< td > { user . email } </ td >
< / tr >
{ /if }
< tr >
< th scope = "row" > Total Jobs< / th >
< td > { $stats . data . jobsStatistics [ 0 ]. totalJobs } </ td >
< / tr >
< tr >
< th scope = "row" > Short Jobs< / th >
< td > { $stats . data . jobsStatistics [ 0 ]. shortJobs } </ td >
< / tr >
< tr >
< th scope = "row" > Total Walltime< / th >
< td > { $stats . data . jobsStatistics [ 0 ]. totalWalltime } </ td >
< / tr >
< tr >
< th scope = "row" > Total Core Hours< / th >
< td > { $stats . data . jobsStatistics [ 0 ]. totalCoreHours } </ td >
< / tr >
< / tbody >
< / Table >
2022-06-22 11:20:57 +02:00
< / Col >
2024-10-07 17:36:40 +02:00
< Col class = "px-1" >
2024-10-02 17:48:46 +02:00
< div bind:clientWidth = { w1 } >
{ #key $stats . data . jobsStatistics [ 0 ]. histDuration }
< Histogram
data={ convert2uplot ( $stats . data . jobsStatistics [ 0 ]. histDuration )}
2024-10-07 17:36:40 +02:00
width={ w1 }
2024-10-02 17:48:46 +02:00
height={ histogramHeight }
title="Duration Distribution"
xlabel="Current Runtimes"
xunit="Hours"
ylabel="Number of Jobs"
yunit="Jobs"
/>
{ /key }
< / div >
< / Col >
2024-10-07 17:36:40 +02:00
< Col class = "px-1" >
2024-10-02 17:48:46 +02:00
< div bind:clientWidth = { w2 } >
{ #key $stats . data . jobsStatistics [ 0 ]. histNumNodes }
< Histogram
data={ convert2uplot ( $stats . data . jobsStatistics [ 0 ]. histNumNodes )}
2024-10-07 17:36:40 +02:00
width={ w2 }
2024-10-02 17:48:46 +02:00
height={ histogramHeight }
title="Number of Nodes Distribution"
xlabel="Allocated Nodes"
xunit="Nodes"
ylabel="Number of Jobs"
yunit="Jobs"
/>
{ /key }
< / div >
< / Col >
2024-03-09 10:30:40 +01:00
{ /if }
2022-06-22 11:20:57 +02:00
< / Row >
2024-10-02 17:48:46 +02:00
2024-10-07 17:36:40 +02:00
<!-- ROW4+5: Selectable Histograms -->
< Row cols = {{ xs : 1 , md : 5 }} >
< Col >
< Button
outline
color="secondary"
on:click={() => ( isHistogramSelectionOpen = true )}
>
< Icon name = "bar-chart-line" / > Select Histograms
< / Button >
< / Col >
< / Row >
{ #if metricsInHistograms ? . length > 0 }
2024-10-02 17:48:46 +02:00
{ #if $stats . error }
< Row >
2024-03-09 10:30:40 +01:00
< Col >
< Card body color = "danger" > { $stats . error . message } </ Card >
< / Col >
2024-10-02 17:48:46 +02:00
< / Row >
{ :else if ! $stats . data }
< Row >
2024-03-09 10:30:40 +01:00
< Col >
< Spinner secondary / >
< / Col >
2024-10-02 17:48:46 +02:00
< / Row >
{ : else }
2024-10-07 17:36:40 +02:00
< hr class = "my-2" / >
2024-10-02 17:48:46 +02:00
{ #key $stats . data . jobsStatistics [ 0 ]. histMetrics }
< PlotGrid
let:item
let:width
renderFor="user"
items={ $stats . data . jobsStatistics [ 0 ]. histMetrics }
itemsPerRow={ 3 }
>
< Histogram
data={ convert2uplot ( item . data )}
usesBins={ true }
{ width }
height={ 250 }
title="Distribution of '{ item . metric } ({ item . stat } )' footprints"
xlabel={ ` ${ item . metric } bin maximum ${ item ? . unit ? ` [ $ { item . unit }] ` : `` } `}
xunit={ item . unit }
ylabel="Number of Jobs"
yunit="Jobs"
/>
< / PlotGrid >
{ /key }
{ /if }
2024-10-07 17:36:40 +02:00
{ : else }
< Row class = "mt-2" >
< Col >
< Card body > No footprint histograms selected.< / Card >
< / Col >
< / Row >
2023-12-05 11:59:01 +01:00
{ /if }
2024-10-07 17:36:40 +02:00
<!-- ROW6: JOB LIST -->
< Row class = "mt-3" >
2024-03-09 10:30:40 +01:00
< Col >
2024-10-07 17:36:40 +02:00
< JobList
bind:this={ jobList }
bind:matchedJobs
bind:metrics
bind:sorting
bind:showFootprint
/>
2024-03-09 10:30:40 +01:00
< / Col >
2022-06-22 11:20:57 +02:00
< / Row >
2024-03-09 10:30:40 +01:00
< Sorting bind:sorting bind:isOpen = { isSortingOpen } / >
< MetricSelection
bind:cluster={ selectedCluster }
configName="plot_list_selectedMetrics"
bind:metrics
bind:isOpen={ isMetricsSelectionOpen }
bind:showFootprint
2024-07-25 17:10:00 +02:00
footprintSelect={ true }
2024-03-09 10:30:40 +01:00
/>
2022-06-22 11:20:57 +02:00
2023-12-12 15:42:14 +01:00
< HistogramSelection
2024-03-09 10:30:40 +01:00
bind:cluster={ selectedCluster }
bind:metricsInHistograms
bind:isOpen={ isHistogramSelectionOpen }
/>