From 2f0460d6ecccb3e07b941b818864120e3d249340 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Thu, 10 Oct 2024 18:35:53 +0200 Subject: [PATCH] feat: make quick select starttimes url copyable --- api/schema.graphqls | 6 +- internal/graph/generated/generated.go | 70 +++++++++++++++++-- internal/graph/model/models_gen.go | 5 +- internal/graph/schema.resolvers.go | 1 - internal/repository/jobQuery.go | 17 +++++ internal/routerConfig/routes.go | 6 +- pkg/schema/config.go | 5 +- web/frontend/src/List.root.svelte | 8 +-- web/frontend/src/generic/Filters.svelte | 39 ++++++----- .../src/generic/filters/StartTime.svelte | 11 +++ 10 files changed, 128 insertions(+), 40 deletions(-) diff --git a/api/schema.graphqls b/api/schema.graphqls index 994d94d..e62fb0a 100644 --- a/api/schema.graphqls +++ b/api/schema.graphqls @@ -253,7 +253,7 @@ type Mutation { } type IntRangeOutput { from: Int!, to: Int! } -type TimeRangeOutput { from: Time!, to: Time! } +type TimeRangeOutput { range: String, from: Time!, to: Time! } input JobFilter { tags: [ID!] @@ -300,8 +300,8 @@ input StringInput { in: [String!] } -input IntRange { from: Int!, to: Int! } -input TimeRange { from: Time, to: Time } +input IntRange { from: Int!, to: Int! } +input TimeRange { range: String, from: Time, to: Time } input FloatRange { from: Float! diff --git a/internal/graph/generated/generated.go b/internal/graph/generated/generated.go index 614f9c1..00609ac 100644 --- a/internal/graph/generated/generated.go +++ b/internal/graph/generated/generated.go @@ -318,8 +318,9 @@ type ComplexityRoot struct { } TimeRangeOutput struct { - From func(childComplexity int) int - To func(childComplexity int) int + From func(childComplexity int) int + Range func(childComplexity int) int + To func(childComplexity int) int } TimeWeights struct { @@ -1668,6 +1669,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TimeRangeOutput.From(childComplexity), true + case "TimeRangeOutput.range": + if e.complexity.TimeRangeOutput.Range == nil { + break + } + + return e.complexity.TimeRangeOutput.Range(childComplexity), true + case "TimeRangeOutput.to": if e.complexity.TimeRangeOutput.To == nil { break @@ -2141,7 +2149,7 @@ type Mutation { } type IntRangeOutput { from: Int!, to: Int! } -type TimeRangeOutput { from: Time!, to: Time! } +type TimeRangeOutput { range: String, from: Time!, to: Time! } input JobFilter { tags: [ID!] @@ -2188,8 +2196,8 @@ input StringInput { in: [String!] } -input IntRange { from: Int!, to: Int! } -input TimeRange { from: Time, to: Time } +input IntRange { from: Int!, to: Int! } +input TimeRange { range: String, from: Time, to: Time } input FloatRange { from: Float! @@ -10914,6 +10922,47 @@ func (ec *executionContext) fieldContext_Tag_scope(_ context.Context, field grap return fc, nil } +func (ec *executionContext) _TimeRangeOutput_range(ctx context.Context, field graphql.CollectedField, obj *model.TimeRangeOutput) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_TimeRangeOutput_range(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Range, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_TimeRangeOutput_range(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "TimeRangeOutput", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _TimeRangeOutput_from(ctx context.Context, field graphql.CollectedField, obj *model.TimeRangeOutput) (ret graphql.Marshaler) { fc, err := ec.fieldContext_TimeRangeOutput_from(ctx, field) if err != nil { @@ -13781,13 +13830,20 @@ func (ec *executionContext) unmarshalInputTimeRange(ctx context.Context, obj int asMap[k] = v } - fieldsInOrder := [...]string{"from", "to"} + fieldsInOrder := [...]string{"range", "from", "to"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { continue } switch k { + case "range": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("range")) + data, err := ec.unmarshalOString2string(ctx, v) + if err != nil { + return it, err + } + it.Range = data case "from": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("from")) data, err := ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v) @@ -16166,6 +16222,8 @@ func (ec *executionContext) _TimeRangeOutput(ctx context.Context, sel ast.Select switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("TimeRangeOutput") + case "range": + out.Values[i] = ec._TimeRangeOutput_range(ctx, field, obj) case "from": out.Values[i] = ec._TimeRangeOutput_from(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/internal/graph/model/models_gen.go b/internal/graph/model/models_gen.go index 99841fd..7f0db5f 100644 --- a/internal/graph/model/models_gen.go +++ b/internal/graph/model/models_gen.go @@ -172,8 +172,9 @@ type StringInput struct { } type TimeRangeOutput struct { - From time.Time `json:"from"` - To time.Time `json:"to"` + Range *string `json:"range,omitempty"` + From time.Time `json:"from"` + To time.Time `json:"to"` } type TimeWeights struct { diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index 14f71e1..58d664b 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -268,7 +268,6 @@ func (r *queryResolver) Job(ctx context.Context, id string) (*schema.Job, error) // JobMetrics is the resolver for the jobMetrics field. func (r *queryResolver) JobMetrics(ctx context.Context, id string, metrics []string, scopes []schema.MetricScope, resolution *int) ([]*model.JobMetricWithName, error) { - if resolution == nil { // Load from Config if config.Keys.EnableResampling != nil { defaultRes := slices.Max(config.Keys.EnableResampling.Resolutions) diff --git a/internal/repository/jobQuery.go b/internal/repository/jobQuery.go index 8c16bb3..5458043 100644 --- a/internal/repository/jobQuery.go +++ b/internal/repository/jobQuery.go @@ -218,6 +218,23 @@ func buildTimeCondition(field string, cond *schema.TimeRange, query sq.SelectBui return query.Where("? <= "+field, cond.From.Unix()) } else if cond.To != nil { return query.Where(field+" <= ?", cond.To.Unix()) + } else if cond.Range != "" { + now := time.Now().Unix() + var then int64 + switch cond.Range { + case "last6h": + then = now - (60 * 60 * 6) + case "last24h": + then = now - (60 * 60 * 24) + case "last7d": + then = now - (60 * 60 * 24 * 7) + case "last30d": + then = now - (60 * 60 * 24 * 30) + default: + log.Debugf("No known named timeRange: startTime.range = %s", cond.Range) + return query + } + return query.Where(field+" BETWEEN ? AND ?", then, now) } else { return query } diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index ba63ad3..05b316d 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -255,7 +255,7 @@ func buildFilterPresets(query url.Values) map[string]interface{} { } if query.Get("startTime") != "" { parts := strings.Split(query.Get("startTime"), "-") - if len(parts) == 2 { + if len(parts) == 2 { // Time in seconds, from - to a, e1 := strconv.ParseInt(parts[0], 10, 64) b, e2 := strconv.ParseInt(parts[1], 10, 64) if e1 == nil && e2 == nil { @@ -264,6 +264,10 @@ func buildFilterPresets(query url.Values) map[string]interface{} { "to": time.Unix(b, 0).Format(time.RFC3339), } } + } else { // named range + filterPresets["startTime"] = map[string]string{ + "range": query.Get("startTime"), + } } } diff --git a/pkg/schema/config.go b/pkg/schema/config.go index 10fb728..ccd848b 100644 --- a/pkg/schema/config.go +++ b/pkg/schema/config.go @@ -53,8 +53,9 @@ type IntRange struct { } type TimeRange struct { - From *time.Time `json:"from"` - To *time.Time `json:"to"` + Range string `json:"range,omitempty"` // Optional, e.g. 'last6h' + From *time.Time `json:"from"` + To *time.Time `json:"to"` } type FilterRanges struct { diff --git a/web/frontend/src/List.root.svelte b/web/frontend/src/List.root.svelte index 9425db5..ef57e27 100644 --- a/web/frontend/src/List.root.svelte +++ b/web/frontend/src/List.root.svelte @@ -40,15 +40,9 @@ if (filterPresets?.startTime == null) { if (filterPresets == null) filterPresets = {}; - const lastMonth = new Date( - Date.now() - 30 * 24 * 60 * 60 * 1000, - ).toISOString(); - const now = new Date(Date.now()).toISOString(); filterPresets.startTime = { - from: lastMonth, - to: now, + range: "last30d", text: "Last 30 Days", - url: "last30d", }; } diff --git a/web/frontend/src/generic/Filters.svelte b/web/frontend/src/generic/Filters.svelte index 5b829ba..9580f17 100644 --- a/web/frontend/src/generic/Filters.svelte +++ b/web/frontend/src/generic/Filters.svelte @@ -106,6 +106,10 @@ items.push({ startTime: { from: filters.startTime.from, to: filters.startTime.to }, }); + if (filters.startTime.range) + items.push({ + startTime: { range: filters.startTime.range }, + }); if (filters.tags.length != 0) items.push({ tags: filters.tags }); if (filters.duration.from || filters.duration.to) items.push({ @@ -167,13 +171,12 @@ if (filters.states.length != allJobStates.length) for (let state of filters.states) opts.push(`state=${state}`); if (filters.startTime.from && filters.startTime.to) - // if (filters.startTime.url) { - // opts.push(`startTime=${filters.startTime.url}`) - // } else { opts.push( `startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`, ); - // } + if (filters.startTime.range) { + opts.push(`startTime=${filters.startTime.range}`) + } if (filters.jobId.length != 0) if (filters.jobIdMatch != "in") { opts.push(`jobId=${filters.jobId}`); @@ -259,14 +262,11 @@ {#if startTimeQuickSelect} Start Time Quick Selection - {#each [{ text: "Last 6hrs", url: "last6h", seconds: 6 * 60 * 60 }, { text: "Last 24hrs", url: "last24h", seconds: 24 * 60 * 60 }, { text: "Last 7 days", url: "last7d", seconds: 7 * 24 * 60 * 60 }, { text: "Last 30 days", url: "last30d", seconds: 30 * 24 * 60 * 60 }] as { text, url, seconds }} + {#each [{ text: "Last 6hrs", range: "last6h" }, { text: "Last 24hrs", range: "last24h" }, { text: "Last 7 days", range: "last7d" }, { text: "Last 30 days", range: "last30d" }] as { text, range }} { - filters.startTime.from = new Date( - Date.now() - seconds * 1000, - ).toISOString(); - filters.startTime.to = new Date(Date.now()).toISOString(); - (filters.startTime.text = text), (filters.startTime.url = url); + filters.startTime.range = range; + filters.startTime.text = text; updateFilters(); }} > @@ -302,13 +302,15 @@ {#if filters.startTime.from || filters.startTime.to} (isStartTimeOpen = true)}> - {#if filters.startTime.text} - {filters.startTime.text} - {:else} - {new Date(filters.startTime.from).toLocaleString()} - {new Date( - filters.startTime.to, - ).toLocaleString()} - {/if} + {new Date(filters.startTime.from).toLocaleString()} - {new Date( + filters.startTime.to, + ).toLocaleString()} + +{/if} + +{#if filters.startTime.range} + (isStartTimeOpen = true)}> + {filters?.startTime?.text ? filters.startTime.text : filters.startTime.range } {/if} @@ -406,9 +408,10 @@ bind:isOpen={isStartTimeOpen} bind:from={filters.startTime.from} bind:to={filters.startTime.to} + bind:range={filters.startTime.range} on:set-filter={() => { delete filters.startTime["text"]; - delete filters.startTime["url"]; + delete filters.startTime["range"]; updateFilters(); }} /> diff --git a/web/frontend/src/generic/filters/StartTime.svelte b/web/frontend/src/generic/filters/StartTime.svelte index c781077..bc842f5 100644 --- a/web/frontend/src/generic/filters/StartTime.svelte +++ b/web/frontend/src/generic/filters/StartTime.svelte @@ -6,6 +6,7 @@ - `isOpen Bool?`: Is this filter component opened [Default: false] - `from Object?`: The currently selected from startime [Default: null] - `to Object?`: The currently selected to starttime (i.e. subCluster) [Default: null] + - `range String?`: The currently selected starttime range as string [Default: ""] Events: - `set-filter, {String?, String?}`: Set 'from, to' filter in upstream component @@ -16,6 +17,7 @@ import { parse, format, sub } from "date-fns"; import { Row, + Col, Button, Input, Modal, @@ -31,6 +33,7 @@ export let isOpen = false; export let from = null; export let to = null; + export let range = ""; let pendingFrom, pendingTo; @@ -86,6 +89,14 @@ (isOpen = !isOpen)}> Select Start Time + {#if range !== ""} +

Current Range

+ + + + + + {/if}

From