mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-07-23 12:51:40 +02:00
initial branch commit, add job compare switch, add gql resolver
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
import { init } from "./generic/utils.js";
|
||||
import Filters from "./generic/Filters.svelte";
|
||||
import JobList from "./generic/JobList.svelte";
|
||||
import JobCompare from "./generic/JobCompare.svelte";
|
||||
import TextFilter from "./generic/helper/TextFilter.svelte";
|
||||
import Refresher from "./generic/helper/Refresher.svelte";
|
||||
import Sorting from "./generic/select/SortSelection.svelte";
|
||||
@@ -36,7 +37,9 @@
|
||||
|
||||
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,
|
||||
matchedJobs = null;
|
||||
jobCompare,
|
||||
matchedListJobs,
|
||||
matchedCompareJobs = null;
|
||||
let sorting = { field: "startTime", type: "col", order: "DESC" },
|
||||
isSortingOpen = false,
|
||||
isMetricsSelectionOpen = false;
|
||||
@@ -49,6 +52,7 @@
|
||||
: !!ccconfig.plot_list_showFootprint;
|
||||
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null;
|
||||
let presetProject = filterPresets?.project ? filterPresets.project : ""
|
||||
let showCompare = false;
|
||||
|
||||
// The filterPresets are handled by the Filters component,
|
||||
// so we need to wait for it to be ready before we can start a query.
|
||||
@@ -72,7 +76,7 @@
|
||||
{/if}
|
||||
|
||||
<!-- ROW2: Tools-->
|
||||
<Row cols={{ xs: 1, md: 2, lg: 4}} class="mb-3">
|
||||
<Row cols={{ xs: 1, md: 2, lg: 5}} class="mb-3">
|
||||
<Col lg="2" class="mb-2 mb-lg-0">
|
||||
<ButtonGroup class="w-100">
|
||||
<Button outline color="primary" on:click={() => (isSortingOpen = true)}>
|
||||
@@ -87,20 +91,24 @@
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Col>
|
||||
<Col lg="4" xl="{(presetProject !== '') ? 5 : 6}" class="mb-1 mb-lg-0">
|
||||
<Col lg="4" class="mb-1 mb-lg-0">
|
||||
<Filters
|
||||
{filterPresets}
|
||||
{matchedJobs}
|
||||
matchedJobs={showCompare? matchedCompareJobs: matchedListJobs}
|
||||
bind:this={filterComponent}
|
||||
on:update-filters={({ detail }) => {
|
||||
selectedCluster = detail.filters[0]?.cluster
|
||||
? detail.filters[0].cluster.eq
|
||||
: null;
|
||||
jobList.queryJobs(detail.filters);
|
||||
if (showCompare) {
|
||||
jobCompare.queryJobs(detail.filters);
|
||||
} else {
|
||||
jobList.queryJobs(detail.filters);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col lg="3" xl="{(presetProject !== '') ? 3 : 2}" class="mb-2 mb-lg-0">
|
||||
<Col lg="2" class="mb-2 mb-lg-0">
|
||||
<TextFilter
|
||||
{presetProject}
|
||||
bind:authlevel
|
||||
@@ -108,24 +116,44 @@
|
||||
on:set-filter={({ detail }) => filterComponent.updateFilters(detail)}
|
||||
/>
|
||||
</Col>
|
||||
<Col lg="3" xl="2" class="mb-1 mb-lg-0">
|
||||
<Col lg="2" class="mb-1 mb-lg-0">
|
||||
<Refresher on:refresh={() => {
|
||||
jobList.refreshJobs()
|
||||
jobList.refreshAllMetrics()
|
||||
if (showCompare) {
|
||||
jobCompare.refreshJobs()
|
||||
jobCompare.refreshAllMetrics()
|
||||
} else {
|
||||
jobList.refreshJobs()
|
||||
jobList.refreshAllMetrics()
|
||||
}
|
||||
}} />
|
||||
</Col>
|
||||
<Col lg="2" class="mb-2 mb-lg-0">
|
||||
<Button color="primary" on:click={() => {
|
||||
showCompare = !showCompare
|
||||
}} >
|
||||
{showCompare ? 'Compare' : 'List'} Jobs
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<!-- ROW3: Job List-->
|
||||
<!-- ROW3: Job List / Job Compare-->
|
||||
<Row>
|
||||
<Col>
|
||||
<JobList
|
||||
bind:this={jobList}
|
||||
bind:metrics
|
||||
bind:sorting
|
||||
bind:matchedJobs
|
||||
bind:showFootprint
|
||||
/>
|
||||
{#if !showCompare}
|
||||
<JobList
|
||||
bind:this={jobList}
|
||||
bind:metrics
|
||||
bind:sorting
|
||||
bind:matchedListJobs
|
||||
bind:showFootprint
|
||||
/>
|
||||
{:else}
|
||||
<JobCompare
|
||||
bind:this={jobCompare}
|
||||
bind:metrics
|
||||
bind:matchedCompareJobs
|
||||
/>
|
||||
{/if}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
|
156
web/frontend/src/generic/JobCompare.svelte
Normal file
156
web/frontend/src/generic/JobCompare.svelte
Normal file
@@ -0,0 +1,156 @@
|
||||
<!--
|
||||
@component jobCompare component; compares jobs according to set filters
|
||||
|
||||
Properties:
|
||||
- `sorting Object?`: Currently active sorting [Default: {field: "startTime", type: "col", order: "DESC"}]
|
||||
- `matchedJobs Number?`: Number of matched jobs for selected filters [Default: 0]
|
||||
- `metrics [String]?`: The currently selected metrics [Default: User-Configured Selection]
|
||||
- `showFootprint Bool`: If to display the jobFootprint component
|
||||
|
||||
Functions:
|
||||
- `refreshJobs()`: Load jobs data with unchanged parameters and 'network-only' keyword
|
||||
- `refreshAllMetrics()`: Trigger downstream refresh of all running jobs' metric data
|
||||
- `queryJobs(filters?: [JobFilter])`: Load jobs data with new filters, starts from page 1
|
||||
-->
|
||||
|
||||
<script>
|
||||
import { getContext } from "svelte";
|
||||
import {
|
||||
queryStore,
|
||||
gql,
|
||||
getContextClient,
|
||||
mutationStore,
|
||||
} from "@urql/svelte";
|
||||
import { Row, Col, Card, Spinner } from "@sveltestrap/sveltestrap";
|
||||
|
||||
const ccconfig = getContext("cc-config"),
|
||||
initialized = getContext("initialized"),
|
||||
globalMetrics = getContext("globalMetrics");
|
||||
|
||||
const equalsCheck = (a, b) => {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
export let matchedCompareJobs = 0;
|
||||
export let metrics = ccconfig.plot_list_selectedMetrics;
|
||||
|
||||
let filter = [];
|
||||
const sorting = { field: "startTime", type: "col", order: "DESC" };
|
||||
|
||||
/* GQL */
|
||||
|
||||
const client = getContextClient();
|
||||
// Pull All Series For Metrics Statistics Only On Node Scope
|
||||
const compareQuery = gql`
|
||||
query ($filter: [JobFilter!]!, $metrics: [String!]!) {
|
||||
jobsMetricStats(filter: $filter, metrics: $metrics) {
|
||||
jobId
|
||||
stats {
|
||||
name
|
||||
data {
|
||||
min
|
||||
avg
|
||||
max
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
$: compareData = queryStore({
|
||||
client: client,
|
||||
query: compareQuery,
|
||||
variables:{ filter, metrics },
|
||||
});
|
||||
|
||||
$: matchedCompareJobs = $compareData.data != null ? $compareData.data.jobsMetricStats.length : -1;
|
||||
|
||||
/* FUNCTIONS */
|
||||
// Force refresh list with existing unchanged variables (== usually would not trigger reactivity)
|
||||
export function refreshJobs() {
|
||||
compareData = queryStore({
|
||||
client: client,
|
||||
query: compareQuery,
|
||||
variables: { filter, metrics },
|
||||
requestPolicy: "network-only",
|
||||
});
|
||||
}
|
||||
|
||||
export function refreshAllMetrics() {
|
||||
// Refresh Job Metrics (Downstream will only query for running jobs)
|
||||
triggerMetricRefresh = true
|
||||
setTimeout(function () {
|
||||
triggerMetricRefresh = false;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// (Re-)query and optionally set new filters; Query will be started reactively.
|
||||
export function queryJobs(filters) {
|
||||
if (filters != null) {
|
||||
let minRunningFor = ccconfig.plot_list_hideShortRunningJobs;
|
||||
if (minRunningFor && minRunningFor > 0) {
|
||||
filters.push({ minRunningFor });
|
||||
}
|
||||
filter = filters;
|
||||
}
|
||||
}
|
||||
|
||||
// Adapt for Persisting Job Selections in DB later down the line
|
||||
// const updateConfigurationMutation = ({ name, value }) => {
|
||||
// return mutationStore({
|
||||
// client: client,
|
||||
// query: gql`
|
||||
// mutation ($name: String!, $value: String!) {
|
||||
// updateConfiguration(name: $name, value: $value)
|
||||
// }
|
||||
// `,
|
||||
// variables: { name, value },
|
||||
// });
|
||||
// };
|
||||
|
||||
// function updateConfiguration(value, page) {
|
||||
// updateConfigurationMutation({
|
||||
// name: "plot_list_jobsPerPage",
|
||||
// value: value,
|
||||
// }).subscribe((res) => {
|
||||
// if (res.fetching === false && !res.error) {
|
||||
// jobs = [] // Empty List
|
||||
// paging = { itemsPerPage: value, page: page }; // Trigger reload of jobList
|
||||
// } else if (res.fetching === false && res.error) {
|
||||
// throw res.error;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
</script>
|
||||
|
||||
{#if $compareData.fetching}
|
||||
<Row>
|
||||
<Col>
|
||||
<Spinner secondary />
|
||||
</Col>
|
||||
</Row>
|
||||
{:else if $compareData.error}
|
||||
<Row>
|
||||
<Col>
|
||||
<Card body color="danger" class="mb-3"
|
||||
><h2>{$compareData.error.message}</h2></Card
|
||||
>
|
||||
</Col>
|
||||
</Row>
|
||||
{:else}
|
||||
{#each $compareData.data.jobsMetricStats as job (job.jobId)}
|
||||
<Row>
|
||||
<Col><b><i>{job.jobId}</i></b></Col>
|
||||
{#each job.stats as stat (stat.name)}
|
||||
<Col><b>{stat.name}</b></Col>
|
||||
<Col>Min {stat.data.min}</Col>
|
||||
<Col>Avg {stat.data.avg}</Col>
|
||||
<Col>Max {stat.data.max}</Col>
|
||||
{/each}
|
||||
</Row>
|
||||
<hr/>
|
||||
{:else}
|
||||
<div> No jobs found </div>
|
||||
{/each}
|
||||
{/if}
|
@@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
export let sorting = { field: "startTime", type: "col", order: "DESC" };
|
||||
export let matchedJobs = 0;
|
||||
export let matchedListJobs = 0;
|
||||
export let metrics = ccconfig.plot_list_selectedMetrics;
|
||||
export let showFootprint;
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: matchedJobs = $jobsStore.data != null ? $jobsStore.data.jobs.count : -1;
|
||||
$: matchedListJobs = $jobsStore.data != null ? $jobsStore.data.jobs.count : -1;
|
||||
|
||||
// Force refresh list with existing unchanged variables (== usually would not trigger reactivity)
|
||||
export function refreshJobs() {
|
||||
@@ -310,7 +310,7 @@
|
||||
bind:page
|
||||
{itemsPerPage}
|
||||
itemText="Jobs"
|
||||
totalItems={matchedJobs}
|
||||
totalItems={matchedListJobs}
|
||||
on:update-paging={({ detail }) => {
|
||||
if (detail.itemsPerPage != itemsPerPage) {
|
||||
updateConfiguration(detail.itemsPerPage.toString(), detail.page);
|
||||
|
@@ -55,7 +55,7 @@
|
||||
const getValues = (type) => labels.map(name => {
|
||||
// Peak is adapted and scaled for job shared state
|
||||
const peak = polarMetrics.find(m => m?.name == name)?.peak
|
||||
const metric = polarData.find(m => m?.name == name)?.stats
|
||||
const metric = polarData.find(m => m?.name == name)?.data
|
||||
const value = (peak && metric) ? (metric[type] / peak) : 0
|
||||
return value <= 1. ? value : 1.
|
||||
})
|
||||
|
@@ -42,7 +42,7 @@
|
||||
query ($dbid: ID!, $selectedMetrics: [String!]!) {
|
||||
jobStats(id: $dbid, metrics: $selectedMetrics) {
|
||||
name
|
||||
stats {
|
||||
data {
|
||||
min
|
||||
avg
|
||||
max
|
||||
|
Reference in New Issue
Block a user