diff --git a/api/schema.graphqls b/api/schema.graphqls index 693ed29..db19626 100644 --- a/api/schema.graphqls +++ b/api/schema.graphqls @@ -300,8 +300,8 @@ type TimeRangeOutput { range: String, from: Time!, to: Time! } input JobFilter { tags: [ID!] + dbId: [ID!] jobId: StringInput - jobIds: [ID!] arrayJobId: Int user: StringInput project: StringInput diff --git a/internal/graph/generated/generated.go b/internal/graph/generated/generated.go index e20fb69..615b3f8 100644 --- a/internal/graph/generated/generated.go +++ b/internal/graph/generated/generated.go @@ -2465,8 +2465,8 @@ type TimeRangeOutput { range: String, from: Time!, to: Time! } input JobFilter { tags: [ID!] + dbId: [ID!] jobId: StringInput - jobIds: [ID!] arrayJobId: Int user: StringInput project: StringInput @@ -16447,7 +16447,7 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj any asMap[k] = v } - fieldsInOrder := [...]string{"tags", "jobId", "jobIds", "arrayJobId", "user", "project", "jobName", "cluster", "partition", "duration", "energy", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "metricStats", "exclusive", "node"} + fieldsInOrder := [...]string{"tags", "dbId", "jobId", "arrayJobId", "user", "project", "jobName", "cluster", "partition", "duration", "energy", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "metricStats", "exclusive", "node"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -16461,6 +16461,13 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj any return it, err } it.Tags = data + case "dbId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("dbId")) + data, err := ec.unmarshalOID2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + it.DbID = data case "jobId": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jobId")) data, err := ec.unmarshalOStringInput2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑbackendᚋinternalᚋgraphᚋmodelᚐStringInput(ctx, v) @@ -16468,13 +16475,6 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj any return it, err } it.JobID = data - case "jobIds": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jobIds")) - data, err := ec.unmarshalOID2ᚕstringᚄ(ctx, v) - if err != nil { - return it, err - } - it.JobIds = data case "arrayJobId": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("arrayJobId")) data, err := ec.unmarshalOInt2ᚖint(ctx, v) diff --git a/internal/graph/model/models_gen.go b/internal/graph/model/models_gen.go index dbe4462..d88fdc5 100644 --- a/internal/graph/model/models_gen.go +++ b/internal/graph/model/models_gen.go @@ -50,8 +50,8 @@ type IntRangeOutput struct { type JobFilter struct { Tags []string `json:"tags,omitempty"` + DbID []string `json:"dbId,omitempty"` JobID *StringInput `json:"jobId,omitempty"` - JobIds []string `json:"jobIds,omitempty"` ArrayJobID *int `json:"arrayJobId,omitempty"` User *StringInput `json:"user,omitempty"` Project *StringInput `json:"project,omitempty"` diff --git a/internal/repository/jobQuery.go b/internal/repository/jobQuery.go index 3f75ec6..6a2ddec 100644 --- a/internal/repository/jobQuery.go +++ b/internal/repository/jobQuery.go @@ -146,17 +146,16 @@ func BuildWhereClause(filter *model.JobFilter, query sq.SelectBuilder) sq.Select // This is an OR-Logic query: Returns all distinct jobs with at least one of the requested tags; TODO: AND-Logic query? query = query.Join("jobtag ON jobtag.job_id = job.id").Where(sq.Eq{"jobtag.tag_id": filter.Tags}).Distinct() } + if filter.DbID != nil { + dbIDs := make([]string, len(filter.DbID)) + for i, val := range filter.DbID { + dbIDs[i] = val + } + query = query.Where(sq.Eq{"job.id": dbIDs}) + } if filter.JobID != nil { query = buildStringCondition("job.job_id", filter.JobID, query) } - if filter.JobIds != nil { - jobIds := make([]string, len(filter.JobIds)) - for i, val := range filter.JobIds { - jobIds[i] = string(val) - } - - query = query.Where(sq.Eq{"job.job_id": jobIds}) - } if filter.ArrayJobID != nil { query = query.Where("job.array_job_id = ?", *filter.ArrayJobID) } diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index bf74391..5386553 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -297,6 +297,9 @@ func buildFilterPresets(query url.Values) map[string]interface{} { } } } + if len(query["dbId"]) != 0 { + filterPresets["dbId"] = query["dbId"] + } if query.Get("jobId") != "" { if len(query["jobId"]) == 1 { filterPresets["jobId"] = query.Get("jobId") diff --git a/web/frontend/src/Jobs.root.svelte b/web/frontend/src/Jobs.root.svelte index 57f0b5b..6d05646 100644 --- a/web/frontend/src/Jobs.root.svelte +++ b/web/frontend/src/Jobs.root.svelte @@ -37,6 +37,7 @@ 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 filterBuffer = []; + let selectedJobs = []; let jobList, jobCompare, matchedListJobs, @@ -59,6 +60,10 @@ // so we need to wait for it to be ready before we can start a query. // This is also why JobList component starts out with a paused query. onMount(() => filterComponent.updateFilters()); + + $: if (filterComponent && selectedJobs.length == 0) { + filterComponent.updateFilters({dbId: []}) + } @@ -80,7 +85,7 @@ - + + + + @@ -148,6 +162,7 @@ bind:sorting bind:matchedListJobs bind:showFootprint + bind:selectedJobs {filterBuffer} /> {:else} @@ -161,7 +176,7 @@ - + Math.floor(Date.parse(rfc3339) / 1000); - let opts = []; if (filters.cluster) opts.push(`cluster=${filters.cluster}`); if (filters.node) opts.push(`node=${filters.node}`); @@ -196,6 +198,11 @@ if (filters.startTime.range) { opts.push(`startTime=${filters.startTime.range}`) } + if (filters.dbId.length != 0) { + for (let dbi of filters.dbId) { + opts.push(`dbId=${dbi}`); + } + } if (filters.jobId.length != 0) if (filters.jobIdMatch != "in") { opts.push(`jobId=${filters.jobId}`); diff --git a/web/frontend/src/generic/JobList.svelte b/web/frontend/src/generic/JobList.svelte index 03044f0..e6ae6f6 100644 --- a/web/frontend/src/generic/JobList.svelte +++ b/web/frontend/src/generic/JobList.svelte @@ -39,6 +39,7 @@ export let metrics = ccconfig.plot_list_selectedMetrics; export let showFootprint; export let filterBuffer = []; + export let selectedJobs = []; let usePaging = ccconfig.job_list_usePaging let itemsPerPage = usePaging ? ccconfig.plot_list_jobsPerPage : 10; @@ -285,7 +286,10 @@ {:else} {#each jobs as job (job)} - + selectedJobs = [...selectedJobs, detail]} + on:unselect-job={({detail}) => selectedJobs = selectedJobs.filter(item => item !== detail)} + /> {:else} No jobs found diff --git a/web/frontend/src/generic/joblist/JobInfo.svelte b/web/frontend/src/generic/joblist/JobInfo.svelte index 8917653..f5cb066 100644 --- a/web/frontend/src/generic/joblist/JobInfo.svelte +++ b/web/frontend/src/generic/joblist/JobInfo.svelte @@ -18,6 +18,8 @@ export let username = null; export let authlevel= null; export let roles = null; + export let isSelected = null; + export let showSelect = false; function formatDuration(duration) { const hours = Math.floor(duration / 3600); @@ -76,18 +78,39 @@ {job.jobId} ({job.cluster}) - + + { 'Add or Remove Job to/from Comparison Selection' } + {/if} - - - { displayCheck ? 'Copied!' : 'Copy Job ID to Clipboard' } - + + + { displayCheck ? 'Copied!' : 'Copy Job ID to Clipboard' } + + {#if job.metaData?.jobName} {#if job.metaData?.jobName.length <= 25} diff --git a/web/frontend/src/generic/joblist/JobListRow.svelte b/web/frontend/src/generic/joblist/JobListRow.svelte index 82cf2ed..7d94943 100644 --- a/web/frontend/src/generic/joblist/JobListRow.svelte +++ b/web/frontend/src/generic/joblist/JobListRow.svelte @@ -12,7 +12,7 @@