From 3004e2909a7f9a87720fedaa81f955696e09a700 Mon Sep 17 00:00:00 2001 From: Lou Knauer Date: Wed, 21 Apr 2021 10:12:19 +0200 Subject: [PATCH] Add tags field to Job --- gqlgen.yml | 3 + graph/generated/generated.go | 321 +++++++++++++++++------------------ graph/model/models.go | 1 + graph/model/models_gen.go | 2 + graph/resolver.go | 81 ++++++--- graph/schema.graphqls | 9 +- 6 files changed, 232 insertions(+), 185 deletions(-) diff --git a/gqlgen.yml b/gqlgen.yml index 3ff3833..b2279ca 100644 --- a/gqlgen.yml +++ b/gqlgen.yml @@ -56,6 +56,9 @@ models: - github.com/99designs/gqlgen/graphql.Int32 Job: model: "github.com/ClusterCockpit/cc-jobarchive/graph/model.Job" + fields: + tags: + resolver: true JobTag: model: "github.com/ClusterCockpit/cc-jobarchive/graph/model.JobTag" Timestamp: diff --git a/graph/generated/generated.go b/graph/generated/generated.go index 7f43eed..3d571d4 100644 --- a/graph/generated/generated.go +++ b/graph/generated/generated.go @@ -36,6 +36,7 @@ type Config struct { } type ResolverRoot interface { + Job() JobResolver Query() QueryResolver } @@ -62,6 +63,7 @@ type ComplexityRoot struct { NumNodes func(childComplexity int) int ProjectID func(childComplexity int) int StartTime func(childComplexity int) int + Tags func(childComplexity int) int UserID func(childComplexity int) int } @@ -114,20 +116,21 @@ type ComplexityRoot struct { Query struct { JobByID func(childComplexity int, jobID string) int JobMetrics func(childComplexity int, jobID string, metrics []*string) int - JobTags func(childComplexity int, jobID *string) int Jobs func(childComplexity int, filter *model.JobFilterList, page *model.PageRequest, order *model.OrderByInput) int - JobsByTag func(childComplexity int, tag string) int JobsStatistics func(childComplexity int, filter *model.JobFilterList) int + Tags func(childComplexity int, jobID *string) int } } +type JobResolver interface { + Tags(ctx context.Context, obj *model.Job) ([]*model.JobTag, error) +} type QueryResolver interface { JobByID(ctx context.Context, jobID string) (*model.Job, error) Jobs(ctx context.Context, filter *model.JobFilterList, page *model.PageRequest, order *model.OrderByInput) (*model.JobResultList, error) JobsStatistics(ctx context.Context, filter *model.JobFilterList) (*model.JobsStatistics, error) JobMetrics(ctx context.Context, jobID string, metrics []*string) ([]*model.JobMetricWithName, error) - JobTags(ctx context.Context, jobID *string) ([]*model.JobTag, error) - JobsByTag(ctx context.Context, tag string) ([]string, error) + Tags(ctx context.Context, jobID *string) ([]*model.JobTag, error) } type executableSchema struct { @@ -250,6 +253,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Job.StartTime(childComplexity), true + case "Job.tags": + if e.complexity.Job.Tags == nil { + break + } + + return e.complexity.Job.Tags(childComplexity), true + case "Job.userId": if e.complexity.Job.UserID == nil { break @@ -456,18 +466,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.JobMetrics(childComplexity, args["jobId"].(string), args["metrics"].([]*string)), true - case "Query.jobTags": - if e.complexity.Query.JobTags == nil { - break - } - - args, err := ec.field_Query_jobTags_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Query.JobTags(childComplexity, args["jobId"].(*string)), true - case "Query.jobs": if e.complexity.Query.Jobs == nil { break @@ -480,18 +478,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Jobs(childComplexity, args["filter"].(*model.JobFilterList), args["page"].(*model.PageRequest), args["order"].(*model.OrderByInput)), true - case "Query.jobsByTag": - if e.complexity.Query.JobsByTag == nil { - break - } - - args, err := ec.field_Query_jobsByTag_args(context.TODO(), rawArgs) - if err != nil { - return 0, false - } - - return e.complexity.Query.JobsByTag(childComplexity, args["tag"].(string)), true - case "Query.jobsStatistics": if e.complexity.Query.JobsStatistics == nil { break @@ -504,6 +490,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.JobsStatistics(childComplexity, args["filter"].(*model.JobFilterList)), true + case "Query.tags": + if e.complexity.Query.Tags == nil { + break + } + + args, err := ec.field_Query_tags_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.Tags(childComplexity, args["jobId"].(*string)), true + } return 0, false } @@ -571,6 +569,8 @@ var sources = []*ast.Source{ memBw_avg: Float netBw_avg: Float fileBw_avg: Float + + tags: [JobTag!] } type JobMetric { @@ -611,10 +611,7 @@ type Query { jobMetrics(jobId: String!, metrics: [String]): [JobMetricWithName]! # Return all known tags or, if jobId is specified, only tags from this job - jobTags(jobId: String): [JobTag!]! - - # For a tag ID, return the ID's of all jobs with that tag - jobsByTag(tag: ID!): [ID!]! + tags(jobId: String): [JobTag!]! } input StartJobInput { @@ -646,6 +643,8 @@ input JobFilterList { } input JobFilter { + tagName: String + tagType: String jobId: StringInput userId: StringInput projectId: StringInput @@ -782,36 +781,6 @@ func (ec *executionContext) field_Query_jobMetrics_args(ctx context.Context, raw return args, nil } -func (ec *executionContext) field_Query_jobTags_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 *string - if tmp, ok := rawArgs["jobId"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jobId")) - arg0, err = ec.unmarshalOString2ᚖstring(ctx, tmp) - if err != nil { - return nil, err - } - } - args["jobId"] = arg0 - return args, nil -} - -func (ec *executionContext) field_Query_jobsByTag_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 string - if tmp, ok := rawArgs["tag"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tag")) - arg0, err = ec.unmarshalNID2string(ctx, tmp) - if err != nil { - return nil, err - } - } - args["tag"] = arg0 - return args, nil -} - func (ec *executionContext) field_Query_jobsStatistics_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -860,6 +829,21 @@ func (ec *executionContext) field_Query_jobs_args(ctx context.Context, rawArgs m return args, nil } +func (ec *executionContext) field_Query_tags_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *string + if tmp, ok := rawArgs["jobId"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jobId")) + arg0, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["jobId"] = arg0 + return args, nil +} + func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1443,6 +1427,38 @@ func (ec *executionContext) _Job_fileBw_avg(ctx context.Context, field graphql.C return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res) } +func (ec *executionContext) _Job_tags(ctx context.Context, field graphql.CollectedField, obj *model.Job) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Job", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Job().Tags(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*model.JobTag) + fc.Result = res + return ec.marshalOJobTag2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑjobarchiveᚋgraphᚋmodelᚐJobTagᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _JobMetric_unit(ctx context.Context, field graphql.CollectedField, obj *model.JobMetric) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -2471,7 +2487,7 @@ func (ec *executionContext) _Query_jobMetrics(ctx context.Context, field graphql return ec.marshalNJobMetricWithName2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑjobarchiveᚋgraphᚋmodelᚐJobMetricWithName(ctx, field.Selections, res) } -func (ec *executionContext) _Query_jobTags(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { +func (ec *executionContext) _Query_tags(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -2488,7 +2504,7 @@ func (ec *executionContext) _Query_jobTags(ctx context.Context, field graphql.Co ctx = graphql.WithFieldContext(ctx, fc) rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_jobTags_args(ctx, rawArgs) + args, err := ec.field_Query_tags_args(ctx, rawArgs) if err != nil { ec.Error(ctx, err) return graphql.Null @@ -2496,7 +2512,7 @@ func (ec *executionContext) _Query_jobTags(ctx context.Context, field graphql.Co fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().JobTags(rctx, args["jobId"].(*string)) + return ec.resolvers.Query().Tags(rctx, args["jobId"].(*string)) }) if err != nil { ec.Error(ctx, err) @@ -2513,48 +2529,6 @@ func (ec *executionContext) _Query_jobTags(ctx context.Context, field graphql.Co return ec.marshalNJobTag2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑjobarchiveᚋgraphᚋmodelᚐJobTagᚄ(ctx, field.Selections, res) } -func (ec *executionContext) _Query_jobsByTag(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "Query", - Field: field, - Args: nil, - IsMethod: true, - IsResolver: true, - } - - ctx = graphql.WithFieldContext(ctx, fc) - rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.field_Query_jobsByTag_args(ctx, rawArgs) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - fc.Args = args - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().JobsByTag(rctx, args["tag"].(string)) - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.([]string) - fc.Result = res - return ec.marshalNID2ᚕstringᚄ(ctx, field.Selections, res) -} - func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -3843,6 +3817,22 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int for k, v := range asMap { switch k { + case "tagName": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tagName")) + it.TagName, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + case "tagType": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("tagType")) + it.TagType, err = ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } case "jobId": var err error @@ -4199,47 +4189,47 @@ func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj case "id": out.Values[i] = ec._Job_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "jobId": out.Values[i] = ec._Job_jobId(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "userId": out.Values[i] = ec._Job_userId(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "projectId": out.Values[i] = ec._Job_projectId(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "clusterId": out.Values[i] = ec._Job_clusterId(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "startTime": out.Values[i] = ec._Job_startTime(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "duration": out.Values[i] = ec._Job_duration(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "numNodes": out.Values[i] = ec._Job_numNodes(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "hasProfile": out.Values[i] = ec._Job_hasProfile(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "memUsed_max": out.Values[i] = ec._Job_memUsed_max(ctx, field, obj) @@ -4251,6 +4241,17 @@ func (ec *executionContext) _Job(ctx context.Context, sel ast.SelectionSet, obj out.Values[i] = ec._Job_netBw_avg(ctx, field, obj) case "fileBw_avg": out.Values[i] = ec._Job_fileBw_avg(ctx, field, obj) + case "tags": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Job_tags(ctx, field, obj) + return res + }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -4597,7 +4598,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) - case "jobTags": + case "tags": field := field out.Concurrently(i, func() (res graphql.Marshaler) { defer func() { @@ -4605,21 +4606,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_jobTags(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } - return res - }) - case "jobsByTag": - field := field - out.Concurrently(i, func() (res graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - } - }() - res = ec._Query_jobsByTag(ctx, field) + res = ec._Query_tags(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } @@ -4997,36 +4984,6 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec return res } -func (ec *executionContext) unmarshalNID2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) { - var vSlice []interface{} - if v != nil { - if tmp1, ok := v.([]interface{}); ok { - vSlice = tmp1 - } else { - vSlice = []interface{}{v} - } - } - var err error - res := make([]string, len(vSlice)) - for i := range vSlice { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalNID2string(ctx, vSlice[i]) - if err != nil { - return nil, err - } - } - return res, nil -} - -func (ec *executionContext) marshalNID2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { - ret := make(graphql.Array, len(v)) - for i := range v { - ret[i] = ec.marshalNID2string(ctx, sel, v[i]) - } - - return ret -} - func (ec *executionContext) unmarshalNInt2int(ctx context.Context, v interface{}) (int, error) { res, err := graphql.UnmarshalInt(v) return res, graphql.ErrorOnPath(ctx, err) @@ -5644,6 +5601,46 @@ func (ec *executionContext) marshalOJobMetricWithName2ᚖgithubᚗcomᚋClusterC return ec._JobMetricWithName(ctx, sel, v) } +func (ec *executionContext) marshalOJobTag2ᚕᚖgithubᚗcomᚋClusterCockpitᚋccᚑjobarchiveᚋgraphᚋmodelᚐJobTagᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.JobTag) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNJobTag2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑjobarchiveᚋgraphᚋmodelᚐJobTag(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + func (ec *executionContext) unmarshalOOrderByInput2ᚖgithubᚗcomᚋClusterCockpitᚋccᚑjobarchiveᚋgraphᚋmodelᚐOrderByInput(ctx context.Context, v interface{}) (*model.OrderByInput, error) { if v == nil { return nil, nil diff --git a/graph/model/models.go b/graph/model/models.go index d652434..32be091 100644 --- a/graph/model/models.go +++ b/graph/model/models.go @@ -22,6 +22,7 @@ type Job struct { MemBw_avg *float64 `json:"memBwAvg" db:"mem_bw_avg"` NetBw_avg *float64 `json:"netBwAvg" db:"net_bw_avg"` FileBw_avg *float64 `json:"fileBwAvg" db:"file_bw_avg"` + Tags []JobTag `json:"tags"` } type JobTag struct { diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index 25f42a8..68e0e3f 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -35,6 +35,8 @@ type IntRange struct { } type JobFilter struct { + TagName *string `json:"tagName"` + TagType *string `json:"tagType"` JobID *StringInput `json:"jobId"` UserID *StringInput `json:"userId"` ProjectID *StringInput `json:"projectId"` diff --git a/graph/resolver.go b/graph/resolver.go index c81858f..f6aefde 100644 --- a/graph/resolver.go +++ b/graph/resolver.go @@ -7,7 +7,6 @@ import ( "log" "strings" "os" - "errors" "strconv" "encoding/json" @@ -58,12 +57,26 @@ func addTimeCondition(conditions []string, field string, input *model.TimeRange) return conditions } -func buildQueryConditions(filterList *model.JobFilterList) string { +func buildQueryConditions(filterList *model.JobFilterList) (string, string) { var conditions []string + var join string + + joinJobtags := ` + JOIN jobtag ON jobtag.job_id = job.id + JOIN tag ON tag.id = jobtag.tag_id + ` for _, condition := range filterList.List { + if condition.TagName != nil { + conditions = append(conditions, fmt.Sprintf("tag.tag_name = '%s'", *condition.TagName)) + join = joinJobtags + } + if condition.TagType != nil { + conditions = append(conditions, fmt.Sprintf("tag.tag_type = '%s'", *condition.TagType)) + join = joinJobtags + } if condition.JobID != nil { - conditions = addStringCondition(conditions, `job_id`, condition.JobID) + conditions = addStringCondition(conditions, `job.job_id`, condition.JobID) } if condition.UserID != nil { conditions = addStringCondition(conditions, `user_id`, condition.UserID) @@ -85,7 +98,7 @@ func buildQueryConditions(filterList *model.JobFilterList) string { } } - return strings.Join(conditions, " AND ") + return strings.Join(conditions, " AND "), join } func readJobDataFile(jobId string) ([]byte, error) { @@ -141,7 +154,7 @@ func (r *queryResolver) Jobs( var jobs []*model.Job var limit, offset int - var qc, ob string + var qc, ob, jo string if page != nil { limit = *page.ItemsPerPage @@ -152,18 +165,22 @@ func (r *queryResolver) Jobs( } if filterList != nil { - qc = buildQueryConditions(filterList) + qc, jo = buildQueryConditions(filterList) if qc != "" { qc = `WHERE ` + qc } + + if jo != "" { + qc = jo + qc + } } if orderBy != nil { ob = fmt.Sprintf("ORDER BY %s %s", orderBy.Field, *orderBy.Order) } - qstr := `SELECT * ` + qstr := `SELECT job.* ` qstr += fmt.Sprintf("FROM job %s %s LIMIT %d OFFSET %d", qc, ob, limit, offset) log.Printf("%s", qstr) @@ -201,14 +218,18 @@ func (r *queryResolver) Jobs( func (r *queryResolver) JobsStatistics( ctx context.Context, filterList *model.JobFilterList) (*model.JobsStatistics, error) { - var qc string + var qc, jo string if filterList != nil { - qc = buildQueryConditions(filterList) + qc, jo = buildQueryConditions(filterList) if qc != "" { qc = `WHERE ` + qc } + + if jo != "" { + qc = jo + qc + } } // TODO Change current node hours to core hours @@ -305,7 +326,7 @@ func (r *queryResolver) JobMetrics( return list, nil } -func (r *queryResolver) JobTags( +func (r *queryResolver) Tags( ctx context.Context, jobId *string) ([]*model.JobTag, error) { if jobId == nil { @@ -316,20 +337,22 @@ func (r *queryResolver) JobTags( tags := []*model.JobTag{} for rows.Next() { - var tag *model.JobTag + var tag model.JobTag err = rows.StructScan(&tag) if err != nil { return nil, err } - tags = append(tags, tag) + tags = append(tags, &tag) } return tags, nil } + /* TODO: Use cluster id? */ query := ` - SELECT id, tag_name, tag_type FROM tag + SELECT tag.id, tag.tag_name, tag.tag_type FROM tag JOIN jobtag ON tag.id = jobtag.tag_id - WHERE jobtag.job_id = $1 + JOIN job ON job.id = jobtag.job_id + WHERE job.job_id = $1 ` rows, err := r.DB.Queryx(query, jobId) if err != nil { @@ -338,23 +361,43 @@ func (r *queryResolver) JobTags( tags := []*model.JobTag{} for rows.Next() { - var tag *model.JobTag + var tag model.JobTag err = rows.StructScan(&tag) if err != nil { return nil, err } - tags = append(tags, tag) + tags = append(tags, &tag) } return tags, nil } -func (r *queryResolver) JobsByTag( - ctx context.Context, jobId string) ([]string, error) { +func (r *jobResolver) Tags(ctx context.Context, job *model.Job) ([]*model.JobTag, error) { + query := ` + SELECT tag.id, tag.tag_name, tag.tag_type FROM tag + JOIN jobtag ON tag.id = jobtag.tag_id + WHERE jobtag.job_id = $1 + ` + rows, err := r.DB.Queryx(query, job.ID) + if err != nil { + return nil, err + } - return nil, errors.New("unimplemented") + tags := []*model.JobTag{} + for rows.Next() { + var tag model.JobTag + err = rows.StructScan(&tag) + if err != nil { + return nil, err + } + tags = append(tags, &tag) + } + + return tags, nil } +func (r *Resolver) Job() generated.JobResolver { return &jobResolver{r} } func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } +type jobResolver struct{ *Resolver } type queryResolver struct{ *Resolver } diff --git a/graph/schema.graphqls b/graph/schema.graphqls index 448509c..bca2c61 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -15,6 +15,8 @@ type Job { memBw_avg: Float netBw_avg: Float fileBw_avg: Float + + tags: [JobTag!] } type JobMetric { @@ -55,10 +57,7 @@ type Query { jobMetrics(jobId: String!, metrics: [String]): [JobMetricWithName]! # Return all known tags or, if jobId is specified, only tags from this job - jobTags(jobId: String): [JobTag!]! - - # For a tag ID, return the ID's of all jobs with that tag - jobsByTag(tag: ID!): [ID!]! + tags(jobId: String): [JobTag!]! } input StartJobInput { @@ -90,6 +89,8 @@ input JobFilterList { } input JobFilter { + tagName: String + tagType: String jobId: StringInput userId: StringInput projectId: StringInput