mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-25 12:59:06 +01:00
feat: make quick select starttimes url copyable
This commit is contained in:
parent
37f4ed7770
commit
2f0460d6ec
@ -253,7 +253,7 @@ type Mutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IntRangeOutput { from: Int!, to: Int! }
|
type IntRangeOutput { from: Int!, to: Int! }
|
||||||
type TimeRangeOutput { from: Time!, to: Time! }
|
type TimeRangeOutput { range: String, from: Time!, to: Time! }
|
||||||
|
|
||||||
input JobFilter {
|
input JobFilter {
|
||||||
tags: [ID!]
|
tags: [ID!]
|
||||||
@ -300,8 +300,8 @@ input StringInput {
|
|||||||
in: [String!]
|
in: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
input IntRange { from: Int!, to: Int! }
|
input IntRange { from: Int!, to: Int! }
|
||||||
input TimeRange { from: Time, to: Time }
|
input TimeRange { range: String, from: Time, to: Time }
|
||||||
|
|
||||||
input FloatRange {
|
input FloatRange {
|
||||||
from: Float!
|
from: Float!
|
||||||
|
@ -318,8 +318,9 @@ type ComplexityRoot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TimeRangeOutput struct {
|
TimeRangeOutput struct {
|
||||||
From func(childComplexity int) int
|
From func(childComplexity int) int
|
||||||
To func(childComplexity int) int
|
Range func(childComplexity int) int
|
||||||
|
To func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeWeights struct {
|
TimeWeights struct {
|
||||||
@ -1668,6 +1669,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.TimeRangeOutput.From(childComplexity), true
|
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":
|
case "TimeRangeOutput.to":
|
||||||
if e.complexity.TimeRangeOutput.To == nil {
|
if e.complexity.TimeRangeOutput.To == nil {
|
||||||
break
|
break
|
||||||
@ -2141,7 +2149,7 @@ type Mutation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type IntRangeOutput { from: Int!, to: Int! }
|
type IntRangeOutput { from: Int!, to: Int! }
|
||||||
type TimeRangeOutput { from: Time!, to: Time! }
|
type TimeRangeOutput { range: String, from: Time!, to: Time! }
|
||||||
|
|
||||||
input JobFilter {
|
input JobFilter {
|
||||||
tags: [ID!]
|
tags: [ID!]
|
||||||
@ -2188,8 +2196,8 @@ input StringInput {
|
|||||||
in: [String!]
|
in: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
input IntRange { from: Int!, to: Int! }
|
input IntRange { from: Int!, to: Int! }
|
||||||
input TimeRange { from: Time, to: Time }
|
input TimeRange { range: String, from: Time, to: Time }
|
||||||
|
|
||||||
input FloatRange {
|
input FloatRange {
|
||||||
from: Float!
|
from: Float!
|
||||||
@ -10914,6 +10922,47 @@ func (ec *executionContext) fieldContext_Tag_scope(_ context.Context, field grap
|
|||||||
return fc, nil
|
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) {
|
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)
|
fc, err := ec.fieldContext_TimeRangeOutput_from(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -13781,13 +13830,20 @@ func (ec *executionContext) unmarshalInputTimeRange(ctx context.Context, obj int
|
|||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"from", "to"}
|
fieldsInOrder := [...]string{"range", "from", "to"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch k {
|
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":
|
case "from":
|
||||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("from"))
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("from"))
|
||||||
data, err := ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v)
|
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 {
|
switch field.Name {
|
||||||
case "__typename":
|
case "__typename":
|
||||||
out.Values[i] = graphql.MarshalString("TimeRangeOutput")
|
out.Values[i] = graphql.MarshalString("TimeRangeOutput")
|
||||||
|
case "range":
|
||||||
|
out.Values[i] = ec._TimeRangeOutput_range(ctx, field, obj)
|
||||||
case "from":
|
case "from":
|
||||||
out.Values[i] = ec._TimeRangeOutput_from(ctx, field, obj)
|
out.Values[i] = ec._TimeRangeOutput_from(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
|
@ -172,8 +172,9 @@ type StringInput struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TimeRangeOutput struct {
|
type TimeRangeOutput struct {
|
||||||
From time.Time `json:"from"`
|
Range *string `json:"range,omitempty"`
|
||||||
To time.Time `json:"to"`
|
From time.Time `json:"from"`
|
||||||
|
To time.Time `json:"to"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TimeWeights struct {
|
type TimeWeights struct {
|
||||||
|
@ -268,7 +268,6 @@ func (r *queryResolver) Job(ctx context.Context, id string) (*schema.Job, error)
|
|||||||
|
|
||||||
// JobMetrics is the resolver for the jobMetrics field.
|
// 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) {
|
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 resolution == nil { // Load from Config
|
||||||
if config.Keys.EnableResampling != nil {
|
if config.Keys.EnableResampling != nil {
|
||||||
defaultRes := slices.Max(config.Keys.EnableResampling.Resolutions)
|
defaultRes := slices.Max(config.Keys.EnableResampling.Resolutions)
|
||||||
|
@ -218,6 +218,23 @@ func buildTimeCondition(field string, cond *schema.TimeRange, query sq.SelectBui
|
|||||||
return query.Where("? <= "+field, cond.From.Unix())
|
return query.Where("? <= "+field, cond.From.Unix())
|
||||||
} else if cond.To != nil {
|
} else if cond.To != nil {
|
||||||
return query.Where(field+" <= ?", cond.To.Unix())
|
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 {
|
} else {
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,7 @@ func buildFilterPresets(query url.Values) map[string]interface{} {
|
|||||||
}
|
}
|
||||||
if query.Get("startTime") != "" {
|
if query.Get("startTime") != "" {
|
||||||
parts := strings.Split(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)
|
a, e1 := strconv.ParseInt(parts[0], 10, 64)
|
||||||
b, e2 := strconv.ParseInt(parts[1], 10, 64)
|
b, e2 := strconv.ParseInt(parts[1], 10, 64)
|
||||||
if e1 == nil && e2 == nil {
|
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),
|
"to": time.Unix(b, 0).Format(time.RFC3339),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else { // named range
|
||||||
|
filterPresets["startTime"] = map[string]string{
|
||||||
|
"range": query.Get("startTime"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,8 +53,9 @@ type IntRange struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TimeRange struct {
|
type TimeRange struct {
|
||||||
From *time.Time `json:"from"`
|
Range string `json:"range,omitempty"` // Optional, e.g. 'last6h'
|
||||||
To *time.Time `json:"to"`
|
From *time.Time `json:"from"`
|
||||||
|
To *time.Time `json:"to"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterRanges struct {
|
type FilterRanges struct {
|
||||||
|
@ -40,15 +40,9 @@
|
|||||||
if (filterPresets?.startTime == null) {
|
if (filterPresets?.startTime == null) {
|
||||||
if (filterPresets == null) filterPresets = {};
|
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 = {
|
filterPresets.startTime = {
|
||||||
from: lastMonth,
|
range: "last30d",
|
||||||
to: now,
|
|
||||||
text: "Last 30 Days",
|
text: "Last 30 Days",
|
||||||
url: "last30d",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +106,10 @@
|
|||||||
items.push({
|
items.push({
|
||||||
startTime: { from: filters.startTime.from, to: filters.startTime.to },
|
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.tags.length != 0) items.push({ tags: filters.tags });
|
||||||
if (filters.duration.from || filters.duration.to)
|
if (filters.duration.from || filters.duration.to)
|
||||||
items.push({
|
items.push({
|
||||||
@ -167,13 +171,12 @@
|
|||||||
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}`);
|
||||||
if (filters.startTime.from && filters.startTime.to)
|
if (filters.startTime.from && filters.startTime.to)
|
||||||
// if (filters.startTime.url) {
|
|
||||||
// opts.push(`startTime=${filters.startTime.url}`)
|
|
||||||
// } else {
|
|
||||||
opts.push(
|
opts.push(
|
||||||
`startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`,
|
`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.jobId.length != 0)
|
||||||
if (filters.jobIdMatch != "in") {
|
if (filters.jobIdMatch != "in") {
|
||||||
opts.push(`jobId=${filters.jobId}`);
|
opts.push(`jobId=${filters.jobId}`);
|
||||||
@ -259,14 +262,11 @@
|
|||||||
{#if startTimeQuickSelect}
|
{#if startTimeQuickSelect}
|
||||||
<DropdownItem divider />
|
<DropdownItem divider />
|
||||||
<DropdownItem disabled>Start Time Quick Selection</DropdownItem>
|
<DropdownItem disabled>Start Time Quick Selection</DropdownItem>
|
||||||
{#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 }}
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
filters.startTime.from = new Date(
|
filters.startTime.range = range;
|
||||||
Date.now() - seconds * 1000,
|
filters.startTime.text = text;
|
||||||
).toISOString();
|
|
||||||
filters.startTime.to = new Date(Date.now()).toISOString();
|
|
||||||
(filters.startTime.text = text), (filters.startTime.url = url);
|
|
||||||
updateFilters();
|
updateFilters();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -302,13 +302,15 @@
|
|||||||
|
|
||||||
{#if filters.startTime.from || filters.startTime.to}
|
{#if filters.startTime.from || filters.startTime.to}
|
||||||
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
||||||
{#if filters.startTime.text}
|
{new Date(filters.startTime.from).toLocaleString()} - {new Date(
|
||||||
{filters.startTime.text}
|
filters.startTime.to,
|
||||||
{:else}
|
).toLocaleString()}
|
||||||
{new Date(filters.startTime.from).toLocaleString()} - {new Date(
|
</Info>
|
||||||
filters.startTime.to,
|
{/if}
|
||||||
).toLocaleString()}
|
|
||||||
{/if}
|
{#if filters.startTime.range}
|
||||||
|
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
|
||||||
|
{filters?.startTime?.text ? filters.startTime.text : filters.startTime.range }
|
||||||
</Info>
|
</Info>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -406,9 +408,10 @@
|
|||||||
bind:isOpen={isStartTimeOpen}
|
bind:isOpen={isStartTimeOpen}
|
||||||
bind:from={filters.startTime.from}
|
bind:from={filters.startTime.from}
|
||||||
bind:to={filters.startTime.to}
|
bind:to={filters.startTime.to}
|
||||||
|
bind:range={filters.startTime.range}
|
||||||
on:set-filter={() => {
|
on:set-filter={() => {
|
||||||
delete filters.startTime["text"];
|
delete filters.startTime["text"];
|
||||||
delete filters.startTime["url"];
|
delete filters.startTime["range"];
|
||||||
updateFilters();
|
updateFilters();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
- `isOpen Bool?`: Is this filter component opened [Default: false]
|
||||||
- `from Object?`: The currently selected from startime [Default: null]
|
- `from Object?`: The currently selected from startime [Default: null]
|
||||||
- `to Object?`: The currently selected to starttime (i.e. subCluster) [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:
|
Events:
|
||||||
- `set-filter, {String?, String?}`: Set 'from, to' filter in upstream component
|
- `set-filter, {String?, String?}`: Set 'from, to' filter in upstream component
|
||||||
@ -16,6 +17,7 @@
|
|||||||
import { parse, format, sub } from "date-fns";
|
import { parse, format, sub } from "date-fns";
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
|
Col,
|
||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
@ -31,6 +33,7 @@
|
|||||||
export let isOpen = false;
|
export let isOpen = false;
|
||||||
export let from = null;
|
export let from = null;
|
||||||
export let to = null;
|
export let to = null;
|
||||||
|
export let range = "";
|
||||||
|
|
||||||
let pendingFrom, pendingTo;
|
let pendingFrom, pendingTo;
|
||||||
|
|
||||||
@ -86,6 +89,14 @@
|
|||||||
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
<Modal {isOpen} toggle={() => (isOpen = !isOpen)}>
|
||||||
<ModalHeader>Select Start Time</ModalHeader>
|
<ModalHeader>Select Start Time</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
{#if range !== ""}
|
||||||
|
<h4>Current Range</h4>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Input type="text" value={range} disabled/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
<h4>From</h4>
|
<h4>From</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<FormGroup class="col">
|
<FormGroup class="col">
|
||||||
|
Loading…
Reference in New Issue
Block a user