mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-04-19 18:21:42 +02:00
feat: add nodename matcher select to filter, defaults to equal match
- see PR !353
This commit is contained in:
parent
b3a1037ade
commit
d770292be8
@ -194,10 +194,12 @@ func (r *JobRepository) FindConcurrentJobs(
|
|||||||
|
|
||||||
queryRunning := query.Where("job.job_state = ?").Where("(job.start_time BETWEEN ? AND ? OR job.start_time < ?)",
|
queryRunning := query.Where("job.job_state = ?").Where("(job.start_time BETWEEN ? AND ? OR job.start_time < ?)",
|
||||||
"running", startTimeTail, stopTimeTail, startTime)
|
"running", startTimeTail, stopTimeTail, startTime)
|
||||||
|
// Get At Least One Exact Hostname Match from JSON Resources Array in Database
|
||||||
queryRunning = queryRunning.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, '$.hostname') = ?)", hostname)
|
queryRunning = queryRunning.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, '$.hostname') = ?)", hostname)
|
||||||
|
|
||||||
query = query.Where("job.job_state != ?").Where("((job.start_time BETWEEN ? AND ?) OR (job.start_time + job.duration) BETWEEN ? AND ? OR (job.start_time < ?) AND (job.start_time + job.duration) > ?)",
|
query = query.Where("job.job_state != ?").Where("((job.start_time BETWEEN ? AND ?) OR (job.start_time + job.duration) BETWEEN ? AND ? OR (job.start_time < ?) AND (job.start_time + job.duration) > ?)",
|
||||||
"running", startTimeTail, stopTimeTail, startTimeFront, stopTimeTail, startTime, stopTime)
|
"running", startTimeTail, stopTimeTail, startTimeFront, stopTimeTail, startTime, stopTime)
|
||||||
|
// Get At Least One Exact Hostname Match from JSON Resources Array in Database
|
||||||
query = query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, '$.hostname') = ?)", hostname)
|
query = query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, '$.hostname') = ?)", hostname)
|
||||||
|
|
||||||
rows, err := query.RunWith(r.stmtCache).Query()
|
rows, err := query.RunWith(r.stmtCache).Query()
|
||||||
|
@ -67,7 +67,8 @@ func (r *JobRepository) QueryJobs(
|
|||||||
|
|
||||||
rows, err := query.RunWith(r.stmtCache).Query()
|
rows, err := query.RunWith(r.stmtCache).Query()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error while running query: %v", err)
|
queryString, queryVars, _ := query.ToSql()
|
||||||
|
log.Errorf("Error while running query '%s' %v: %v", queryString, queryVars, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,14 +198,7 @@ func BuildWhereClause(filter *model.JobFilter, query sq.SelectBuilder) sq.Select
|
|||||||
query = buildIntCondition("job.num_hwthreads", filter.NumHWThreads, query)
|
query = buildIntCondition("job.num_hwthreads", filter.NumHWThreads, query)
|
||||||
}
|
}
|
||||||
if filter.Node != nil {
|
if filter.Node != nil {
|
||||||
log.Infof("Applying node filter: %v", filter.Node)
|
query = buildResourceJsonCondition("hostname", filter.Node, query)
|
||||||
if filter.Node.Eq != nil {
|
|
||||||
query = query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, '$.hostname') = ?)", *filter.Node.Eq)
|
|
||||||
} else if filter.Node.Contains != nil {
|
|
||||||
query = query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, '$.hostname') LIKE ?)", "%"+*filter.Node.Contains+"%")
|
|
||||||
} else {
|
|
||||||
query = buildStringCondition("job.resources", filter.Node, query)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if filter.Energy != nil {
|
if filter.Energy != nil {
|
||||||
query = buildFloatCondition("job.energy", filter.Energy, query)
|
query = buildFloatCondition("job.energy", filter.Energy, query)
|
||||||
@ -306,6 +300,28 @@ func buildMetaJsonCondition(jsonField string, cond *model.StringInput, query sq.
|
|||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildResourceJsonCondition(jsonField string, cond *model.StringInput, query sq.SelectBuilder) sq.SelectBuilder {
|
||||||
|
// Verify and Search Only in Valid Jsons
|
||||||
|
query = query.Where("JSON_VALID(resources)")
|
||||||
|
// add "AND" Sql query Block for field match
|
||||||
|
if cond.Eq != nil {
|
||||||
|
return query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, \"$."+jsonField+"\") = ?)", *cond.Eq)
|
||||||
|
}
|
||||||
|
if cond.Neq != nil { // Currently Unused
|
||||||
|
return query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, \"$."+jsonField+"\") != ?)", *cond.Neq)
|
||||||
|
}
|
||||||
|
if cond.StartsWith != nil { // Currently Unused
|
||||||
|
return query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, \"$."+jsonField+"\")) LIKE ?)", fmt.Sprint(*cond.StartsWith, "%"))
|
||||||
|
}
|
||||||
|
if cond.EndsWith != nil { // Currently Unused
|
||||||
|
return query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, \"$."+jsonField+"\") LIKE ?)", fmt.Sprint("%", *cond.EndsWith))
|
||||||
|
}
|
||||||
|
if cond.Contains != nil {
|
||||||
|
return query.Where("EXISTS (SELECT 1 FROM json_each(job.resources) WHERE json_extract(value, \"$."+jsonField+"\") LIKE ?)", fmt.Sprint("%", *cond.Contains, "%"))
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
|
matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
|
||||||
matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
|
matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
|
||||||
|
@ -53,10 +53,16 @@
|
|||||||
{ range: "last30d", rangeLabel: "Last 30 days"}
|
{ range: "last30d", rangeLabel: "Last 30 days"}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const nodeMatchLabels = {
|
||||||
|
eq: "",
|
||||||
|
contains: " Contains",
|
||||||
|
}
|
||||||
|
|
||||||
let filters = {
|
let filters = {
|
||||||
projectMatch: filterPresets.projectMatch || "contains",
|
projectMatch: filterPresets.projectMatch || "contains",
|
||||||
userMatch: filterPresets.userMatch || "contains",
|
userMatch: filterPresets.userMatch || "contains",
|
||||||
jobIdMatch: filterPresets.jobIdMatch || "eq",
|
jobIdMatch: filterPresets.jobIdMatch || "eq",
|
||||||
|
nodeMatch: filterPresets.nodeMatch || "eq",
|
||||||
|
|
||||||
cluster: filterPresets.cluster || null,
|
cluster: filterPresets.cluster || null,
|
||||||
partition: filterPresets.partition || null,
|
partition: filterPresets.partition || null,
|
||||||
@ -106,7 +112,7 @@
|
|||||||
|
|
||||||
let items = [];
|
let items = [];
|
||||||
if (filters.cluster) items.push({ cluster: { eq: filters.cluster } });
|
if (filters.cluster) items.push({ cluster: { eq: filters.cluster } });
|
||||||
if (filters.node) items.push({ node: { contains: filters.node } });
|
if (filters.node) items.push({ node: { [filters.nodeMatch]: filters.node } });
|
||||||
if (filters.partition) items.push({ partition: { eq: filters.partition } });
|
if (filters.partition) items.push({ partition: { eq: filters.partition } });
|
||||||
if (filters.states.length != allJobStates.length)
|
if (filters.states.length != allJobStates.length)
|
||||||
items.push({ state: filters.states });
|
items.push({ state: filters.states });
|
||||||
@ -178,6 +184,8 @@
|
|||||||
let opts = [];
|
let opts = [];
|
||||||
if (filters.cluster) opts.push(`cluster=${filters.cluster}`);
|
if (filters.cluster) opts.push(`cluster=${filters.cluster}`);
|
||||||
if (filters.node) opts.push(`node=${filters.node}`);
|
if (filters.node) opts.push(`node=${filters.node}`);
|
||||||
|
if (filters.node && filters.nodeMatch != "eq") // "eq" is default-case
|
||||||
|
opts.push(`nodeMatch=${filters.nodeMatch}`);
|
||||||
if (filters.partition) opts.push(`partition=${filters.partition}`);
|
if (filters.partition) opts.push(`partition=${filters.partition}`);
|
||||||
if (filters.states.length != allJobStates.length)
|
if (filters.states.length != allJobStates.length)
|
||||||
for (let state of filters.states) opts.push(`state=${state}`);
|
for (let state of filters.states) opts.push(`state=${state}`);
|
||||||
@ -196,7 +204,7 @@
|
|||||||
opts.push(`jobId=${singleJobId}`);
|
opts.push(`jobId=${singleJobId}`);
|
||||||
}
|
}
|
||||||
if (filters.jobIdMatch != "eq")
|
if (filters.jobIdMatch != "eq")
|
||||||
opts.push(`jobIdMatch=${filters.jobIdMatch}`);
|
opts.push(`jobIdMatch=${filters.jobIdMatch}`); // "eq" is default-case
|
||||||
for (let tag of filters.tags) opts.push(`tag=${tag}`);
|
for (let tag of filters.tags) opts.push(`tag=${tag}`);
|
||||||
if (filters.duration.from && filters.duration.to)
|
if (filters.duration.from && filters.duration.to)
|
||||||
opts.push(`duration=${filters.duration.from}-${filters.duration.to}`);
|
opts.push(`duration=${filters.duration.from}-${filters.duration.to}`);
|
||||||
@ -218,13 +226,13 @@
|
|||||||
} else {
|
} else {
|
||||||
for (let singleUser of filters.user) opts.push(`user=${singleUser}`);
|
for (let singleUser of filters.user) opts.push(`user=${singleUser}`);
|
||||||
}
|
}
|
||||||
if (filters.userMatch != "contains")
|
if (filters.userMatch != "contains") // "contains" is default-case
|
||||||
opts.push(`userMatch=${filters.userMatch}`);
|
opts.push(`userMatch=${filters.userMatch}`);
|
||||||
if (filters.project) opts.push(`project=${filters.project}`);
|
if (filters.project) opts.push(`project=${filters.project}`);
|
||||||
|
if (filters.project && filters.projectMatch != "contains") // "contains" is default-case
|
||||||
|
opts.push(`projectMatch=${filters.projectMatch}`);
|
||||||
if (filters.jobName) opts.push(`jobName=${filters.jobName}`);
|
if (filters.jobName) opts.push(`jobName=${filters.jobName}`);
|
||||||
if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`);
|
if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`);
|
||||||
if (filters.project && filters.projectMatch != "contains")
|
|
||||||
opts.push(`projectMatch=${filters.projectMatch}`);
|
|
||||||
if (filters.stats.length != 0)
|
if (filters.stats.length != 0)
|
||||||
for (let stat of filters.stats) {
|
for (let stat of filters.stats) {
|
||||||
opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
|
opts.push(`stat=${stat.field}-${stat.from}-${stat.to}`);
|
||||||
@ -386,7 +394,7 @@
|
|||||||
|
|
||||||
{#if filters.node != null}
|
{#if filters.node != null}
|
||||||
<Info icon="hdd-stack" on:click={() => (isResourcesOpen = true)}>
|
<Info icon="hdd-stack" on:click={() => (isResourcesOpen = true)}>
|
||||||
Node: {filters.node}
|
Node{nodeMatchLabels[filters.nodeMatch]}: {filters.node}
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -449,6 +457,7 @@
|
|||||||
bind:numHWThreads={filters.numHWThreads}
|
bind:numHWThreads={filters.numHWThreads}
|
||||||
bind:numAccelerators={filters.numAccelerators}
|
bind:numAccelerators={filters.numAccelerators}
|
||||||
bind:namedNode={filters.node}
|
bind:namedNode={filters.node}
|
||||||
|
bind:nodeMatch={filters.nodeMatch}
|
||||||
bind:isNodesModified
|
bind:isNodesModified
|
||||||
bind:isHwthreadsModified
|
bind:isHwthreadsModified
|
||||||
bind:isAccsModified
|
bind:isAccsModified
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
ModalBody,
|
ModalBody,
|
||||||
ModalHeader,
|
ModalHeader,
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
|
Input
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
import DoubleRangeSlider from "../select/DoubleRangeSlider.svelte";
|
||||||
|
|
||||||
@ -40,11 +41,18 @@
|
|||||||
export let isHwthreadsModified = false;
|
export let isHwthreadsModified = false;
|
||||||
export let isAccsModified = false;
|
export let isAccsModified = false;
|
||||||
export let namedNode = null;
|
export let namedNode = null;
|
||||||
|
export let nodeMatch = "eq"
|
||||||
|
|
||||||
let pendingNumNodes = numNodes,
|
let pendingNumNodes = numNodes,
|
||||||
pendingNumHWThreads = numHWThreads,
|
pendingNumHWThreads = numHWThreads,
|
||||||
pendingNumAccelerators = numAccelerators,
|
pendingNumAccelerators = numAccelerators,
|
||||||
pendingNamedNode = namedNode;
|
pendingNamedNode = namedNode,
|
||||||
|
pendingNodeMatch = nodeMatch;
|
||||||
|
|
||||||
|
const nodeMatchLabels = {
|
||||||
|
eq: "Equal To",
|
||||||
|
contains: "Contains",
|
||||||
|
}
|
||||||
|
|
||||||
const findMaxNumAccels = (clusters) =>
|
const findMaxNumAccels = (clusters) =>
|
||||||
clusters.reduce(
|
clusters.reduce(
|
||||||
@ -145,7 +153,17 @@
|
|||||||
<ModalHeader>Select number of utilized Resources</ModalHeader>
|
<ModalHeader>Select number of utilized Resources</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<h6>Named Node</h6>
|
<h6>Named Node</h6>
|
||||||
<input type="text" class="form-control" bind:value={pendingNamedNode} />
|
<div class="d-flex">
|
||||||
|
<Input type="text" class="w-75" bind:value={pendingNamedNode} />
|
||||||
|
<div class="mx-1"></div>
|
||||||
|
<Input type="select" class="w-25" bind:value={pendingNodeMatch}>
|
||||||
|
{#each Object.entries(nodeMatchLabels) as [nodeMatchKey, nodeMatchLabel]}
|
||||||
|
<option value={nodeMatchKey}>
|
||||||
|
{nodeMatchLabel}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
<h6 style="margin-top: 1rem;">Number of Nodes</h6>
|
<h6 style="margin-top: 1rem;">Number of Nodes</h6>
|
||||||
<DoubleRangeSlider
|
<DoubleRangeSlider
|
||||||
on:change={({ detail }) => {
|
on:change={({ detail }) => {
|
||||||
@ -215,11 +233,13 @@
|
|||||||
to: pendingNumAccelerators.to,
|
to: pendingNumAccelerators.to,
|
||||||
};
|
};
|
||||||
namedNode = pendingNamedNode;
|
namedNode = pendingNamedNode;
|
||||||
|
nodeMatch = pendingNodeMatch;
|
||||||
dispatch("set-filter", {
|
dispatch("set-filter", {
|
||||||
numNodes,
|
numNodes,
|
||||||
numHWThreads,
|
numHWThreads,
|
||||||
numAccelerators,
|
numAccelerators,
|
||||||
namedNode,
|
namedNode,
|
||||||
|
nodeMatch
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -233,6 +253,7 @@
|
|||||||
pendingNumHWThreads = { from: null, to: null };
|
pendingNumHWThreads = { from: null, to: null };
|
||||||
pendingNumAccelerators = { from: null, to: null };
|
pendingNumAccelerators = { from: null, to: null };
|
||||||
pendingNamedNode = null;
|
pendingNamedNode = null;
|
||||||
|
pendingNodeMatch = null;
|
||||||
numNodes = { from: pendingNumNodes.from, to: pendingNumNodes.to };
|
numNodes = { from: pendingNumNodes.from, to: pendingNumNodes.to };
|
||||||
numHWThreads = {
|
numHWThreads = {
|
||||||
from: pendingNumHWThreads.from,
|
from: pendingNumHWThreads.from,
|
||||||
@ -246,11 +267,13 @@
|
|||||||
isHwthreadsModified = false;
|
isHwthreadsModified = false;
|
||||||
isAccsModified = false;
|
isAccsModified = false;
|
||||||
namedNode = pendingNamedNode;
|
namedNode = pendingNamedNode;
|
||||||
|
nodeMatch = pendingNodeMatch;
|
||||||
dispatch("set-filter", {
|
dispatch("set-filter", {
|
||||||
numNodes,
|
numNodes,
|
||||||
numHWThreads,
|
numHWThreads,
|
||||||
numAccelerators,
|
numAccelerators,
|
||||||
namedNode,
|
namedNode,
|
||||||
|
nodeMatch
|
||||||
});
|
});
|
||||||
}}>Reset</Button
|
}}>Reset</Button
|
||||||
>
|
>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user