From e69815f6172692d00240e411267b941696ef552f Mon Sep 17 00:00:00 2001 From: matthewpeterkort Date: Tue, 31 Dec 2024 15:05:14 -0800 Subject: [PATCH] cleanup, fix dockerfile --- Dockerfile | 10 +- Makefile | 3 - README.md | 2 +- graphql_gen3/README.md | 47 -- graphql_gen3/builder.go | 816 ----------------------------------- graphql_gen3/filter_build.go | 137 ------ graphql_gen3/gen3_test.go | 294 ------------- graphql_gen3/handler.go | 279 ------------ graphql_gen3/mongo.yml | 5 - graphql_gen3/schema.json | 92 ---- 10 files changed, 4 insertions(+), 1681 deletions(-) delete mode 100644 graphql_gen3/README.md delete mode 100644 graphql_gen3/builder.go delete mode 100644 graphql_gen3/filter_build.go delete mode 100644 graphql_gen3/gen3_test.go delete mode 100644 graphql_gen3/handler.go delete mode 100644 graphql_gen3/mongo.yml delete mode 100644 graphql_gen3/schema.json diff --git a/Dockerfile b/Dockerfile index 872eedd..8a0b023 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,18 +6,14 @@ ENV PATH="/go/bin:${PATH}" ADD ./ /go/src/github.com/bmeg/grip-graphql WORKDIR /go/src/github.com/bmeg/grip-graphql - -RUN go install github.com/bmeg/grip@v0.0.0-20241130225536-67d787e0b831 -RUN go build --buildmode=plugin ./graphql_gen3 -RUN go build --buildmode=plugin ./gen3_writer -RUN go build --buildmode=plugin ./grip-graphql-endpoint - +RUN go install github.com/bmeg/grip@v0.0.0-20241211235035-b772edec00b9 +RUN make all FROM alpine WORKDIR /data VOLUME /data ENV PATH="/app:${PATH}" -COPY --from=build-env /go/src/github.com/bmeg/grip-graphql/graphql_gen3.so /data/ +COPY --from=build-env /go/src/github.com/bmeg/grip-graphql/gql-gen.so /data/ COPY --from=build-env /go/src/github.com/bmeg/grip-graphql/gen3_writer.so /data/ COPY --from=build-env /go/src/github.com/bmeg/grip-graphql/grip-graphql-endpoint.so /data/ COPY --from=build-env /go/src/github.com/bmeg/grip-graphql/config/gen3.js /data/config/ diff --git a/Makefile b/Makefile index 3c18afe..f235990 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,6 @@ grip-graphql-endpoint.so : $(shell find grip-graphql-endpoint -name "*.go") grip-graphql-proxy : $(shell find cmd/grip-graphql-proxy -name "*.go") go build ./cmd/grip-graphql-proxy -graphql_gen3 : $(shell find graphql_gen3 -name "*.go") - go build --buildmode=plugin ./graphql_gen3 - gql-gen : $(shell find gql-gen -name "*.go") go build --buildmode=plugin ./gql-gen diff --git a/README.md b/README.md index af8e6a9..c0f8557 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ gen3_writer directory contains a [Gin](https://github.com/gin-gonic/gin) go serv gripgraphql directory contains a graphql based read query plugin that uses a [goja](https://github.com/dop251/goja) engine to read from a static schema defined as a config file to create custom graphql queries that can be used to abstract Grip's complex query language into a more digestible query format for the frontend to use. -graphql_gen3 is a legacy implementation of a reader reader plugin using a more traditional graphql schema builder. +gql-gen directory contains a graphql schema based read plugin that leverages [gql-gen](https://github.com/99designs/gqlgen) to autogenerate go structs and for schema introspection See ./gen3_writer for tests and additional documentation diff --git a/graphql_gen3/README.md b/graphql_gen3/README.md deleted file mode 100644 index 626635a..0000000 --- a/graphql_gen3/README.md +++ /dev/null @@ -1,47 +0,0 @@ -This directory is a legacy graphql reader that Isn't currently operational. Below are some archive docs. - -# Graphql Grip Endpoint Legacy Deveveloment Setup Instructions - -These instructions show how to load data into grip before there was an ETL image that could be run with g3t. - -In addition to cloning this repo you will also need to have a running [gen3 helm deployment](https://github.com/ACED-IDP/gen3-helm/tree/feature/grip). - -Once you have followed the gen3helm deployment instructions, and have running grip and mongodb pods -you will need to exec into the grip pod to load the data into mongo and start the server: - -Get a list of all running pods to make sure grip pod is running - -``` -kubectl get pods -``` - -copy the config, data, and files into the grip pod with: - -``` -kubectl cp graphql_gen3.so local-grip-your_unique_hash:/data -kubectl cp mongo.yml local-grip-your_unique_hash:/data -``` - -The shared object file should have been built with the image and should already be in /data - -Exec into grip pod with: - -``` -kubectl exec --stdin --tty deployment/local-grip -- /bin/bash -cd data -grip server -w api/graphql=graphql_gen3.so -c mongo.yml -``` - -Create a new tab and exec into the same pod with the same command above, then run the below commands to -import data into mongo, generate a schema from the populated data in mongo and post it to the graphql endpoint: - -``` -grip create synthea -grip server load --vertex output/Observation_new.ndjson -grip server load --vertex output/Patient_new.ndjson -grip server load --vertex output/DocumentReference_new.ndjson -grip schema sample synthea2 > synthea2.schema.json -grip schema post --json synthea2.schema.json -``` - -Note: output/ is the directory that contains the bare minimum 3 vertex data files that are needed to display data on the exploration page. diff --git a/graphql_gen3/builder.go b/graphql_gen3/builder.go deleted file mode 100644 index d3e016f..0000000 --- a/graphql_gen3/builder.go +++ /dev/null @@ -1,816 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "reflect" - "sort" - "strings" - "time" - "unicode" - - "google.golang.org/protobuf/encoding/protojson" - - "github.com/bmeg/grip/gripql" - "github.com/bmeg/grip/log" - "github.com/graphql-go/graphql" - "github.com/graphql-go/graphql/language/ast" -) - -const ARG_LIMIT = "first" -const ARG_OFFSET = "offset" -const ARG_ID = "id" -const ARG_IDS = "ids" -const ARG_FILTER = "filter" -const ARG_ACCESS = "accessibility" -const ARG_SORT = "sort" - -type Accessibility string - -const ( - all Accessibility = "all" - accessible Accessibility = "accessible" - unaccessible Accessibility = "unaccessible" -) - -var JSONScalar = graphql.NewScalar(graphql.ScalarConfig{ - Name: "JSON", - Serialize: func(value interface{}) interface{} { - return fmt.Sprintf("Serialize %v", value) - }, - ParseValue: func(value interface{}) interface{} { - //fmt.Printf("Unmarshal JSON: %v %T\n", value, value) - return value - }, - ParseLiteral: func(valueAST ast.Value) interface{} { - fmt.Printf("ParseLiteral: %#v\n", valueAST) - /* - switch valueAST := valueAST.(type) { - case *ast.StringValue: - id, _ := models.IDFromString(valueAST.Value) - return id - default: - return nil - }*/ - return nil - }, -}) - -// buildGraphQLSchema reads a GRIP graph schema (which is stored as a graph) and creates -// a GraphQL-GO based schema. The GraphQL-GO schema all wraps the request functions that use -// the gripql.Client to find the requested data -func buildGraphQLSchema(schema *gripql.Graph, client gripql.Client, graph string, resourceList []any) (*graphql.Schema, error) { - if schema == nil { - return nil, fmt.Errorf("graphql.NewSchema error: nil gripql.Graph for graph: %s", graph) - } - // Build the set of objects for all vertex labels - objectMap, err := buildObjectMap(client, graph, schema) - //fmt.Println("OBJ MAP: ", objectMap) - if err != nil { - return nil, fmt.Errorf("graphql.NewSchema error: %v", err) - } - - // Build the set of objects that exist in the query structuer - queryObj := buildQueryObject(client, graph, objectMap, resourceList) - schemaConfig := graphql.SchemaConfig{ - Query: queryObj, - } - - // Setup the GraphQL schema based on the objects there have been created - gqlSchema, err := graphql.NewSchema(schemaConfig) - if err != nil { - return nil, fmt.Errorf("graphql.NewSchema error: %v", err) - } - - return &gqlSchema, nil -} - -func buildField(x string) (*graphql.Field, error) { - var o *graphql.Field - switch x { - case "NUMERIC": - o = &graphql.Field{Type: graphql.Float} - case "STRING": - o = &graphql.Field{Type: graphql.String} - case "BOOL": - o = &graphql.Field{Type: graphql.Boolean} - case "STRLIST": - o = &graphql.Field{Type: graphql.String} - default: - return nil, fmt.Errorf("%s does not map to a GQL type", x) - } - return o, nil -} - -func buildSliceField(name string, s []interface{}) (*graphql.Field, error) { - var f *graphql.Field - var err error - - if len(s) > 0 { - val := s[0] - if x, ok := val.(map[string]interface{}); ok { - f, err = buildObjectField(name, x) - } else if x, ok := val.([]interface{}); ok { - f, err = buildSliceField(name, x) - - } else if x, ok := val.(string); ok { - f, err = buildField(x) - } else { - err = fmt.Errorf("unhandled type: %T %v", val, val) - } - - } else { - err = fmt.Errorf("slice is empty") - } - - if err != nil { - return nil, fmt.Errorf("buildSliceField error: %v", err) - } - - return &graphql.Field{Type: graphql.NewList(f.Type)}, nil -} - -// buildObjectField wraps the result of buildObject in a graphql.Field so it can be -// a child of slice of another -func buildObjectField(name string, obj map[string]interface{}) (*graphql.Field, error) { - o, err := buildObject(name, obj) - if err != nil { - return nil, err - } - if len(o.Fields()) == 0 { - return nil, fmt.Errorf("no fields in object") - } - return &graphql.Field{Type: o}, nil -} - -func buildObject(name string, obj map[string]interface{}) (*graphql.Object, error) { - objFields := graphql.Fields{} - - for key, val := range obj { - var err error - - // handle map - if x, ok := val.(map[string]interface{}); ok { - // make object name parent_field - var f *graphql.Field - f, err = buildObjectField(name+"_"+key, x) - if err == nil { - objFields[key] = f - } - // handle slice - } else if x, ok := val.([]interface{}); ok { - var f *graphql.Field - f, err = buildSliceField(key, x) - if err == nil { - objFields[key] = f - } - // handle string - } else if x, ok := val.(string); ok { - if f, err := buildField(x); err == nil { - objFields[key] = f - } else { - log.WithFields(log.Fields{"object": name, "field": key, "error": err}).Error("graphql: buildField ignoring field") - } - // handle other cases - } else { - err = fmt.Errorf("unhandled type: %T %v", val, val) - } - - if err != nil { - log.WithFields(log.Fields{"object": name, "field": key, "error": err}).Error("graphql: buildObject") - // return nil, fmt.Errorf("object: %s: field: %s: error: %v", name, key, err) - } - } - - return graphql.NewObject( - graphql.ObjectConfig{ - Name: name, - Fields: objFields, - }, - ), nil -} - -type objectMap struct { - objects map[string]*graphql.Object - edgeLabel map[string]map[string]string - edgeDstType map[string]map[string]string -} - -// buildObjectMap scans the GripQL schema and turns all of the vertex types into different objects -func buildObjectMap(client gripql.Client, graph string, schema *gripql.Graph) (*objectMap, error) { - objects := map[string]*graphql.Object{} - edgeLabel := map[string]map[string]string{} - edgeDstType := map[string]map[string]string{} - - for _, obj := range schema.Vertices { - if obj.Label == "Vertex" { - props := obj.GetDataMap() - if props == nil { - continue - } - props["id"] = "STRING" - - obj.Gid = lower_first_char(obj.Gid) - gqlObj, err := buildObject(obj.Gid, props) - if err != nil { - return nil, err - } - if len(gqlObj.Fields()) > 0 { - objects[obj.Gid] = gqlObj - } - } - edgeLabel[obj.Gid] = map[string]string{} - edgeDstType[obj.Gid] = map[string]string{} - } - - fmt.Println("THE VALUE OF OBJECTS: ", objects) - // Setup outgoing edge fields - // Note: edge properties are not accessible in this model - for i, obj := range schema.Edges { - // The froms and tos are empty for some reason - obj.From = lower_first_char(obj.From) - if _, ok := objects[obj.From]; ok { - obj.To = lower_first_char(obj.To) - if _, ok := objects[obj.To]; ok { - obj := obj // This makes an inner loop copy of the variable that is used by the Resolve function - fname := obj.Label - - //ensure the fname is unique - for j := range schema.Edges { - if i != j { - if schema.Edges[i].From == schema.Edges[j].From && schema.Edges[i].Label == schema.Edges[j].Label { - fname = obj.Label + "_to_" + obj.To - } - } - } - //fmt.Println("OBJ.FROM: ", obj.From, "OBJ.TO: ", obj.To, "FNAME: ", fname, "OBJ.LABEL: ", obj.Label, "OBJ.DATA: ", obj.Data, "OBJ.GID: ", obj.Gid) - edgeLabel[obj.From][fname] = obj.Label - edgeDstType[obj.From][fname] = obj.To - - f := &graphql.Field{ - Name: fname, - Type: graphql.NewList(objects[obj.To]), - /* - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - srcMap, ok := p.Source.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("source conversion failed: %v", p.Source) - } - srcGid, ok := srcMap["id"].(string) - if !ok { - return nil, fmt.Errorf("source gid conversion failed: %+v", srcMap) - } - fmt.Printf("Field resolve: %s\n", srcGid) - q := gripql.V(srcGid).HasLabel(obj.From).Out(obj.Label).HasLabel(obj.To) - result, err := client.Traversal(&gripql.GraphQuery{Graph: graph, Query: q.Statements}) - if err != nil { - return nil, err - } - out := []interface{}{} - for r := range result { - d := r.GetVertex().GetDataMap() - d["id"] = r.GetVertex().Gid - out = append(out, d) - } - return out, nil - }, - */ - } - //fmt.Printf("building: %#v %s %s\n", f, obj.From, fname) - objects[obj.From].AddFieldConfig(fname, f) - } - } - } - - return &objectMap{objects: objects, edgeLabel: edgeLabel, edgeDstType: edgeDstType}, nil -} - -func buildFieldConfigArgument(obj *graphql.Object) graphql.FieldConfigArgument { - args := graphql.FieldConfigArgument{ - ARG_ID: &graphql.ArgumentConfig{Type: graphql.String}, - ARG_IDS: &graphql.ArgumentConfig{Type: graphql.NewList(graphql.String)}, - ARG_LIMIT: &graphql.ArgumentConfig{Type: graphql.Int, DefaultValue: 100}, - ARG_OFFSET: &graphql.ArgumentConfig{Type: graphql.Int, DefaultValue: 0}, - ARG_FILTER: &graphql.ArgumentConfig{Type: JSONScalar}, - ARG_ACCESS: &graphql.ArgumentConfig{Type: graphql.EnumValueType, DefaultValue: all}, - ARG_SORT: &graphql.ArgumentConfig{Type: JSONScalar}, - } - if obj == nil { - return args - } - for k, v := range obj.Fields() { - switch v.Type { - case graphql.String, graphql.Int, graphql.Float, graphql.Boolean: - args[k] = &graphql.ArgumentConfig{Type: v.Type} - default: - continue - } - } - return args -} - -func lower_first_char(name string) string { - //temp := []rune(name) - temp := strings.ToLower(name) - return string(temp) -} -func upper_first_char(name string) string { - temp := []rune(name) - temp[0] = unicode.ToUpper(temp[0]) - return string(temp) -} - -func buildMappingField(client gripql.Client, graph string, objects *objectMap) *graphql.Field { - mappingFields := graphql.Fields{} - for objName, obj := range objects.objects { - fieldNames := []string{} - for fieldName := range obj.Fields() { - fieldNames = append(fieldNames, fieldName) - } - mappingFields[objName] = &graphql.Field{ - Name: objName, - Type: graphql.NewList(graphql.String), - Resolve: func(params graphql.ResolveParams) (interface{}, error) { - return fieldNames, nil - }, - } - } - - mappingObjectType := graphql.NewObject(graphql.ObjectConfig{ - Name: "_mapping", - Fields: mappingFields, - }) - - return &graphql.Field{ - Name: "_mapping", - Type: mappingObjectType, - Resolve: func(params graphql.ResolveParams) (interface{}, error) { - // Return an empty map just to fulfill the GraphQL response structure - return map[string]interface{}{}, nil - }, - } -} - -func buildAggregationField(client gripql.Client, graph string, objects *objectMap, resourceList []any) *graphql.Field { - stringBucket := graphql.NewObject(graphql.ObjectConfig{ - Name: "BucketsForString", - Fields: graphql.Fields{ - "key": &graphql.Field{Name: "key", Type: graphql.String}, //EnumValueType - "count": &graphql.Field{Name: "count", Type: graphql.Int}, - }, - }) - - histogram := graphql.NewObject(graphql.ObjectConfig{ - Name: "Histogram", - Fields: graphql.Fields{ - "histogram": &graphql.Field{ - Type: graphql.NewList(stringBucket), - }, - }, - }) - - // Need to pass a float bucket/ float histogram so that don't have to do string conversions later on - FloatBucket := graphql.NewObject(graphql.ObjectConfig{ - Name: "BucketsForFloat", - Fields: graphql.Fields{ - "key": &graphql.Field{Name: "key", Type: graphql.NewList(graphql.Float)}, - "count": &graphql.Field{Name: "count", Type: graphql.Int}, - }, - }) - - Floathistogram := graphql.NewObject(graphql.ObjectConfig{ - Name: "HistogramFloat", - Fields: graphql.Fields{ - "histogram": &graphql.Field{ - Type: graphql.NewList(FloatBucket), - }, - }, - }) - - // need to add this to adapt grip to current data portal queries - queryFields := graphql.Fields{} - for k, obj := range objects.objects { - if len(obj.Fields()) > 0 { - label := upper_first_char(obj.Name()) - - aggFields := graphql.Fields{ - "_totalCount": &graphql.Field{Name: "_totalCount", Type: graphql.Int}, - } - for k, v := range obj.Fields() { - switch v.Type { - case graphql.String: - aggFields[k] = - &graphql.Field{ - Name: k, - Type: histogram, - } - // add this for x_adjusted_life_years, Float values - case graphql.Float: - aggFields[k] = - &graphql.Field{ - Name: k, - Type: Floathistogram, - } - } - } - - ao := graphql.NewObject(graphql.ObjectConfig{ - Name: k + "Aggregation", - Fields: aggFields, - }) - // higher scoped vars to keep track of min and max when moving through individual property logic - var max float64 = 0.0 - var min float64 = 0.0 - queryFields[k] = &graphql.Field{ - Name: k + "Aggregation", - Type: ao, - Args: graphql.FieldConfigArgument{ - "filter": &graphql.ArgumentConfig{Type: JSONScalar}, - "accessibility": &graphql.ArgumentConfig{Type: graphql.EnumValueType, DefaultValue: all}, - "filterSelf": &graphql.ArgumentConfig{Type: graphql.Boolean, DefaultValue: false}, - }, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - T_0 := time.Now() - aggs := []*gripql.Aggregate{ - {Name: "_totalCount", Aggregation: &gripql.Aggregate_Count{}}, - } - - counts := map[string][]any{} - for _, i := range p.Info.FieldASTs { - if i.SelectionSet != nil { - for _, j := range i.SelectionSet.Selections { - if k, ok := j.(*ast.Field); ok { - if k.Name.Value != "_totalCount" { - aggs = append(aggs, &gripql.Aggregate{ - Name: k.Name.Value, - Aggregation: &gripql.Aggregate_Term{ - Term: &gripql.TermAggregation{ - Field: k.Name.Value, - }, - }, - }) - counts[k.Name.Value] = []any{} - } - } - } - } - } - - queries := []any{} - if filterSelf, ok := p.Args["filterSelf"].(bool); ok { - if !filterSelf && p.Args[ARG_FILTER] != nil { - - var err error - var filter *FilterBuilder - if filterArg, ok := p.Args[ARG_FILTER].(map[string]any); ok { - fmt.Printf("Filter: %#v\n", filterArg) - filter = NewFilterBuilder(filterArg) - } - for _, val := range aggs { - q := gripql.V().HasLabel(label).Has(gripql.Within("auth_resource_path", resourceList...)) - q, err = filter.ExtendGrip(q, val.Name) - queries = append(queries, q) - if err != nil { - return nil, err - } - } - - } - } - //fmt.Println("VALUE OF Q: ", q, "VALUE OF STATEMENTS: ", q.Statements) - out := map[string]any{} - if len(queries) > 0 { - for i := range queries { - lister := []*gripql.Aggregate{aggs[i]} - queries[i] = queries[i].(*gripql.Query).Aggregate(lister) - result, err := client.Traversal(p.Context, &gripql.GraphQuery{Graph: graph, Query: queries[i].(*gripql.Query).Statements}) - if err != nil { - return nil, err - } - // if nothing returns from grip set totalcount to 0 so that dataportal doesn't panic - if len(result) == 0 { - out["_totalCount"] = 0 - /* - was messing around with populating the output with a base case but it didn't end up being needed - for k, value := range out { - if reflect.TypeOf(value) != reflect.TypeOf(287) && len(value.(map[string]any)["histogram"].([]any)) == 0 { - value.(map[string]any)["histogram"] = []any{map[string]any{"key": k, "count": 0}} - } - } - */ - } - for i := range result { - agg := i.GetAggregations() - if agg.Name == "_totalCount" { - out["_totalCount"] = int(agg.Value) - - } else { - marshal, _ := protojson.Marshal(agg) - var unmarhsal map[string]any - json.Unmarshal(marshal, &unmarhsal) - counts[agg.Name] = append(counts[agg.Name], map[string]any{ - "key": unmarhsal["key"], - // setup count key value so that it can support float64 data - "count": int(unmarhsal["value"].(float64)), - }) - } - } - - for k, v := range counts { - out[k] = map[string]any{"histogram": v} - } - for key, value := range out { - if key != "_totalCount" { - // keep track of a min and a max so that initial ranges are accurate for the slider - // After the slider is moved new ranges are calculated. - if t, ok := value.(map[string]any)["histogram"].([]any); ok { - if len(t) > 0 { - if reflect.TypeOf(t[0].(map[string]any)["key"]) == reflect.TypeOf(3.14) { - max := t[0].(map[string]interface{})["key"].(float64) - min := t[0].(map[string]interface{})["key"].(float64) - for i := 1; i < len(t); i++ { - if val, ok := t[i].(map[string]interface{})["key"].(float64); ok { - if val > max { - max = val - } - if val < min { - min = val - } - } - } - t[0].(map[string]any)["key"] = []float64{min, max} - // for loop counts up all of the results to create the total count. - // converts underlying 'checkbox' style output to 'slider' output - for ind := 1; ind < len(t); { - if val, ok := t[ind].(map[string]any)["key"].(float64); ok { - t[ind].(map[string]any)["key"] = []float64{val, max} - t[ind].(map[string]any)["count"] = t[ind-1].(map[string]any)["count"].(int) + 1 - t = t[1:] - } - } - t[0].(map[string]any)["key"] = []float64{min, max} - value.(map[string]any)["histogram"] = t - - // Some of the data also expects a list of floats - } else if reflect.TypeOf(t[0].(map[string]any)["key"]) == reflect.TypeOf([]float64{54.22, 23.22}) { - max := t[0].(map[string]interface{})["key"].([]float64)[0] - min := t[0].(map[string]interface{})["key"].([]float64)[0] - for i := 1; i < len(t); i++ { - if val, ok := t[i].(map[string]interface{})["key"].([]float64); ok { - if val[0] > max { - max = val[0] - } - if val[0] < min { - min = val[0] - } - } - } - t[0].(map[string]any)["key"] = []float64{min, max} - for ind := 1; ind < len(t); { - if _, ok := t[ind].(map[string]any)["key"].([]float64); ok { - t[ind].(map[string]any)["count"] = t[ind-1].(map[string]any)["count"].(int) + 1 - t = t[1:] - } - } - t[0].(map[string]any)["key"] = []float64{min, max} - value.(map[string]any)["histogram"] = t - } - } - sort.Slice(t, func(i, j int) bool { - return t[i].(map[string]any)["count"].(int) > t[j].(map[string]any)["count"].(int) - }) - } - } - } - } - // this else is needed to differentiate filtered aggregations and non filtered aggregations - } else { - q := gripql.V().HasLabel(label).Has(gripql.Within("auth_resource_path", resourceList...)) - q = q.Aggregate(aggs) - result, err := client.Traversal(p.Context, &gripql.GraphQuery{Graph: graph, Query: q.Statements}) - if err != nil { - return nil, err - } - out := map[string]any{} - for i := range result { - agg := i.GetAggregations() - if agg.Name == "_totalCount" { - out["_totalCount"] = int(agg.Value) - } else { - marshal, _ := protojson.Marshal(agg) - var unmarhsal map[string]any - json.Unmarshal(marshal, &unmarhsal) - counts[agg.Name] = append(counts[agg.Name], map[string]any{ - "key": unmarhsal["key"], - "count": int(unmarhsal["value"].(float64)), - }) - } - } - for k, v := range counts { - out[k] = map[string]any{"histogram": v} - } - for key, value := range out { - if key != "_totalCount" { - if t, ok := value.(map[string]any)["histogram"].([]any); ok { - if len(t) > 0 && reflect.TypeOf(t[0].(map[string]any)["key"]) == reflect.TypeOf(3.14) { - t[0].(map[string]any)["key"] = []float64{min, max} - for ind := 1; ind < len(t); { - if val, ok := t[ind].(map[string]any)["key"].(float64); ok { - // min, max uses global variable. - t[ind].(map[string]any)["key"] = []float64{val, max} - t[ind].(map[string]any)["count"] = t[ind-1].(map[string]any)["count"].(int) + 1 - t = t[1:] - } - } - t[0].(map[string]any)["key"] = []float64{min, max} - value.(map[string]any)["histogram"] = t - } - sort.Slice(t, func(i, j int) bool { - return int(t[i].(map[string]any)["count"].(int)) > int(t[j].(map[string]any)["count"].(int)) - }) - } - } - } - } - fmt.Println("TOTAL TIME RESOLVER DONE IN: ", time.Since(T_0)) - return out, nil - }, - } - // add back in the name appendage after the &graphql.Field block so that it doesn't get picked up in the front end - queryFields[k+"AggregationObject"] = queryFields[k] - } - } - - aggregationObject := graphql.NewObject(graphql.ObjectConfig{ - Name: "AggregationObject", - Fields: queryFields, - }) - - return &graphql.Field{ - Name: "_aggregation", - Type: aggregationObject, - Resolve: func(p graphql.ResolveParams) (interface{}, error) { - // top level resolve doesn't do anything - // but it needs to return an empty object so that the GraphQL - // library will go to the child fields and call their resolvers - return map[string]any{}, nil - }, - } -} - -type renderTree struct { - fields []string - parent map[string]string - fieldName map[string]string -} - -func (rt *renderTree) NewElement(cur string, fieldName string) string { - rName := fmt.Sprintf("f%d", len(rt.fields)) - rt.fields = append(rt.fields, rName) - rt.parent[rName] = cur - rt.fieldName[rName] = fieldName - return rName -} - -func (om *objectMap) traversalBuild(query *gripql.Query, vertLabel string, field *ast.Field, curElement string, rt *renderTree, limit int, offset int) *gripql.Query { - vertLabel = lower_first_char(vertLabel) - moved := false - for _, s := range field.SelectionSet.Selections { - if k, ok := s.(*ast.Field); ok { - if _, ok := om.edgeLabel[vertLabel][k.Name.Value]; ok { - if dstLabel, ok := om.edgeDstType[vertLabel][k.Name.Value]; ok { - if moved { - query = query.Select(curElement) - } - rName := rt.NewElement(curElement, k.Name.Value) - query = query.OutNull(k.Name.Value).As(rName) - - // Additionally have to control the number of outputs on the results of each traversal - // otherwise there are instances when you get all of the results for each traversal node - query = query.Skip(uint32(offset)).Limit(uint32(limit)) - query = om.traversalBuild(query, dstLabel, k, rName, rt, limit, offset) - moved = true - } - } - } - } - return query -} - -// buildQueryObject scans the built objects, which were derived from the list of vertex types -// found in the schema. It then build a query object that will take search parameters -// and create lists of objects of that type -func buildQueryObject(client gripql.Client, graph string, objects *objectMap, resourceList []any) *graphql.Object { - - queryFields := graphql.Fields{} - // For each of the objects that have been listed in the objectMap build a query entry point - for objName, obj := range objects.objects { - fmt.Println("UPPER CASE: ", obj.Name()) - label := upper_first_char(obj.Name()) - f := &graphql.Field{ - Name: objName, - Type: graphql.NewList(obj), - Args: buildFieldConfigArgument(obj), - Resolve: func(params graphql.ResolveParams) (interface{}, error) { - - q := gripql.V().HasLabel(label).Has(gripql.Within("auth_resource_path", resourceList...)) - if id, ok := params.Args[ARG_ID].(string); ok { - fmt.Printf("Doing %s id=%s query", label, id) - q = gripql.V(id).HasLabel(label) - } - if ids, ok := params.Args[ARG_IDS].([]string); ok { - fmt.Printf("Doing %s ids=%s queries", label, ids) - q = gripql.V(ids...).HasLabel(label) - } - var filter *FilterBuilder - if filterArg, ok := params.Args[ARG_FILTER].(map[string]any); ok { - fmt.Printf("Filter: %#v\n", filterArg) - filter = NewFilterBuilder(filterArg) - } - for key, val := range params.Args { - switch key { - case ARG_ID, ARG_IDS, ARG_LIMIT, ARG_OFFSET, ARG_ACCESS, ARG_SORT, ARG_FILTER: - default: - q = q.Has(gripql.Eq(key, val)) - } - } - - if filter != nil { - var err error - // extend grip calls the filter functions to add filters - q, err = filter.ExtendGrip(q, "") - if err != nil { - return nil, err - } - } - - q = q.As("f0") - limit := params.Args[ARG_LIMIT].(int) - offset := params.Args[ARG_OFFSET].(int) - q = q.Skip(uint32(offset)).Limit(uint32(limit)) - - rt := &renderTree{ - fields: []string{"f0"}, - parent: map[string]string{}, - fieldName: map[string]string{}, - } - //fmt.Println("Q1: ", q) - - for _, f := range params.Info.FieldASTs { - q = objects.traversalBuild(q, label, f, "f0", rt, limit, offset) - } - - render := map[string]any{} - for _, i := range rt.fields { - render[i+"_gid"] = "$" + i + "._gid" - render[i+"_data"] = "$" + i + "._data" - } - q = q.Render(render) - result, err := client.Traversal(params.Context, &gripql.GraphQuery{Graph: graph, Query: q.Statements}) - if err != nil { - return nil, err - } - - out := []any{} - for r := range result { - values := r.GetRender().GetStructValue().AsMap() - - data := map[string]map[string]any{} - for _, r := range rt.fields { - v := values[r+"_data"] - if d, ok := v.(map[string]any); ok { - d["id"] = values[r+"_gid"] - if d["id"] != "" { - data[r] = d - } - } - } - for _, r := range rt.fields { - if parent, ok := rt.parent[r]; ok { - fieldName := rt.fieldName[r] - if data[r] != nil { - data[parent][fieldName] = []any{data[r]} - } - } - } - out = append(out, data["f0"]) - } - fmt.Println("OUT: ", out) - return out, nil - }, - } - queryFields[objName] = f - } - - queryFields["_aggregation"] = buildAggregationField(client, graph, objects, resourceList) - queryFields["_mapping"] = buildMappingField(client, graph, objects) - - query := graphql.NewObject( - graphql.ObjectConfig{ - Name: "Query", - Fields: queryFields, - }, - ) - //fmt.Printf("Query fields: %#v\n", queryFields) - return query -} diff --git a/graphql_gen3/filter_build.go b/graphql_gen3/filter_build.go deleted file mode 100644 index 9c5c854..0000000 --- a/graphql_gen3/filter_build.go +++ /dev/null @@ -1,137 +0,0 @@ -package main - -import ( - "fmt" - "github.com/bmeg/grip/gripql" - "github.com/bmeg/grip/log" -) - -type FilterBuilder struct { - filter map[string]any -} - -func NewFilterBuilder(i map[string]any) *FilterBuilder { - return &FilterBuilder{i} -} - -func isFilterEQ(q map[string]any) (any, bool) { - if val, ok := q["IN"]; ok { - return val, ok - } - return q, false -} - -func isFilter(q map[string]any) (any, bool) { - // this first and doesn't seem to suit any purpose - // but it is consistant across exploration page queries - if val, ok := q["AND"]; ok { - return val, ok - } - return nil, false -} - -func isFilterGT(q map[string]any) (any, bool) { - for _, i := range []string{">=", "gt", "GT"} { - if val, ok := q[i]; ok { - return val, ok - } - } - return nil, false -} - -func isFilterLT(q map[string]any) (any, bool) { - for _, i := range []string{"<=", "lt", "LT"} { - if val, ok := q[i]; ok { - return val, ok - } - } - return nil, false -} - -func fieldMap(s string) string { - if s == "id" { - return "_gid" - } - return s -} - -func (fb *FilterBuilder) ExtendGrip(q *gripql.Query, filterSelfName string) (*gripql.Query, error) { - // isFilter filters out a top level "AND" that seems to be consistant across all queries in the exploration page - if is_filter, ok := isFilter(fb.filter); ok { - fmt.Println("FILTER: ", is_filter) - for _, array_filter := range is_filter.([]any) { - // 'Checkbox' filter logic - if map_array_filter, ok := array_filter.(map[string]any); ok { - if mis_filter, ok := isFilterEQ(map_array_filter); ok { - if map_eq_arr_filter, ok := mis_filter.(map[string]any); ok { - for filter_key, arr_filter_values := range map_eq_arr_filter { - filter_key = fieldMap(filter_key) - if filter_values, ok := arr_filter_values.([]any); ok { - - // This is where the 'filterSelf' like Guppy parameter is implemented: - // If the current property that is being passed into the filter function - // is the same as the current interated key then return early and skip filtering self - if filterSelfName != "" && filter_key == filterSelfName { - log.Infof("FilterSelf Query Condition Hit %s", q.String()) - return q, nil - - // otherwise split filtering by 1 checked box or multiple checked boxes - // build the query with ORs like it is done in the current data portal - } else if len(filter_values) == 1 { - q = q.Has(gripql.Within(filter_key, filter_values[0])) - - } else if len(filter_values) > 1 { - final_expr := gripql.Or(gripql.Within(filter_key, filter_values[0]), gripql.Within(filter_key, filter_values[1])) - for i := 2; i < len(filter_values); i++ { - final_expr = gripql.Or(final_expr, gripql.Within(filter_key, filter_values[i])) - } - q = q.Has(final_expr) - } else { - log.Error("Error state checkbox filter not populated but list was created") - } - - } - - } - } - } - - } - // 'Slider' filter logic. Don't think filter self is needed - // for slider since it accepts a range of values - if map_array_filter, ok := array_filter.(map[string]any); ok { - if is_filter, ok := isFilter(map_array_filter); ok { - if map_eq_arr_filter, ok := is_filter.([]any); ok { - for _, v := range map_eq_arr_filter { - if map_array_filter, ok := v.(map[string]any); ok { - if val, ok := isFilterGT(map_array_filter); ok { - if vMap, ok := val.(map[string]any); ok { - for k, v := range vMap { - k = fieldMap(k) - q = q.Has(gripql.Gt(k, v)) - } - } - } - - if val, ok := isFilterLT(map_array_filter); ok { - if vMap, ok := val.(map[string]any); ok { - for k, v := range vMap { - k = fieldMap(k) - q = q.Has(gripql.Lt(k, v)) - } - } - } - - } else { - log.Error("Error state slider filter not populated but list was created") - } - } - } - } - } - - } - } - log.Infof("Filter Query %s", q.String()) - return q, nil -} diff --git a/graphql_gen3/gen3_test.go b/graphql_gen3/gen3_test.go deleted file mode 100644 index 8af4496..0000000 --- a/graphql_gen3/gen3_test.go +++ /dev/null @@ -1,294 +0,0 @@ -package main - -//grip query synthea 'V().hasLabel("DocumentReference").out("subject")' -/*documentReference (filter:$filter) { - subject{ - id - } -}*/ -import ( - "bytes" - "encoding/json" - "net/http" - "os/exec" - "reflect" - "strings" - "testing" -) - -func HTTP_REQUEST(graph_name string, url string, payload []byte, t *testing.T) (response_json map[string]any, status bool) { - req, err := http.NewRequest("POST", url+graph_name, bytes.NewBuffer(payload)) - if err != nil { - t.Error("Error creating request:", err) - return - } - - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - t.Error("Error sending request:", err) - return nil, false - } - defer resp.Body.Close() - - t.Log("Response Status:", resp.Status) - - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(resp.Body) - if err != nil { - t.Error("Error reading response:", err) - return nil, false - } - - var data map[string]interface{} - errors := json.Unmarshal([]byte(buf.String()), &data) - t.Log("DATA: ", data) - if errors != nil { - t.Error("Error:", errors) - return nil, false - } - return data, true -} -func Test_Filters(t *testing.T) { - tests := []struct { - name string - }{ - {name: "Slider and CheckBox"}, - {name: "Aggregation and Filter"}, - {name: "Combo_test"}, - {name: "NullOps"}, - {name: "GraphQL_NullOps"}, - {name: "NullOP_Results"}, - } - for _, tt := range tests { - if tt.name == "Slider and CheckBox" { - t.Run(tt.name, func(t *testing.T) { - payload := []byte(`{ - "query": "query ($filter: JSON) {\n patient(filter: $filter) {\n quality_adjusted_life_years_valueDecimal\n maritalStatus\n }\n}\n", - "variables": { - "filter": { - "AND": [ - { - "AND": [ - { - ">=": { - "quality_adjusted_life_years_valueDecimal": 66 - } - }, - { - "<=": { - "quality_adjusted_life_years_valueDecimal": 70 - } - } - ] - }, - { - "IN": { - "maritalStatus": [ - "M" - ] - } - } - ] - } - } - }`) - data, status := HTTP_REQUEST("synthea", "http://localhost:8201/api/graphql/", payload, t) - if status == false { - t.Error("HTTP Request failed") - } - if data, ok := data["data"].(map[string]any); ok { - if data, ok := data["patient"]; ok { - if data, ok := data.([]any); ok { - for _, value := range data { - if data, ok := value.(map[string]any); ok { - if upper_bound, ok := data["quality_adjusted_life_years_valueDecimal"].(float64); ok { - if data["maritalStatus"] == "M" && upper_bound >= 66 && upper_bound <= 70 { - continue - } else { - t.Error("Row Has failed filter") - } - } else { - t.Error("Row has failed type check") - } - } - } - } - } - } - - }) - } - if tt.name == "Aggregation and Filter" { - payload := []byte(`{ - "query": "query ($filter: JSON) {\n _aggregation {\n observation (filter: $filter) {\n code {\n histogram {\n count\n key\n }\n }\n\n }}\n}\n", - "variables": { - "filter": { - "AND": [ - { - "IN": { - "code": [ - "Creatinine" - ] - } - } - ] - } - } - }`) - data, status := HTTP_REQUEST("synthea", "http://localhost:8201/api/graphql/", payload, t) - if status == false { - t.Error("test failed") - } - if data, ok := data["data"].(map[string]any)["_aggregation"].(map[string]any)["observation"].(map[string]any)["code"].(map[string]any)["histogram"].([]any); ok { - for _, values := range data { - key := values.(map[string]any)["key"].(string) - count := values.(map[string]any)["count"].(float64) - if key != `string_value:"Creatinine"` || count != 5377 { - t.Error("Aggregation test failed. Did data change?") - } - } - } else { - t.Error("indexing failed. Did query change?") - } - } - if tt.name == "Combo_test" { - payload := []byte(`{ - "query": "query ($filter: JSON) {\n _aggregation {\n documentReference {\n category {\n histogram {\n count\n key\n }\n }\n }\n }\n observation(filter: $filter) {\n subject\n }\n}\n", - "variables": { - "filter": { - "AND": [ - { - "IN": { - "subject": [ - "Patient/5b13b8fc-f387-4a95-bb80-5c22eeed7697" - ] - } - } - ] - } - } - }`) - data, status := HTTP_REQUEST("synthea", "http://localhost:8201/api/graphql/", payload, t) - if status == false { - t.Error("test failed on HTTP Request") - } - if data, ok := data["data"].(map[string]any); ok { - if aggregation, ok := data["_aggregation"].(map[string]any)["documentReference"].(map[string]any)["category"].(map[string]any)["histogram"].([]any); ok { - for i, values := range aggregation { - if map_values, ok := values.(map[string]any); ok { - t.Log("MAP VALUES: ", map_values) - switch i { - case 0: - if map_values["key"].(string) == `string_value:"Clinical Note"` && map_values["count"].(float64) == 37378 { - continue - } else { - t.Error("test failed, values don't match") - } - case 1: - if map_values["key"].(string) == `string_value:"Image"` && map_values["count"].(float64) == 125 { - continue - } else { - t.Error("test failed, values don't match") - } - case 2: - if map_values["key"].(string) == `string_value:"Cancer related multigene analysis Molgen Doc (cfDNA)"` && map_values["count"].(float64) == 9 { - continue - } else { - t.Error("test failed, values don't match") - } - } - } - } - if res, ok := data["observation"].([]any); ok { - for _, val := range res { - t.Log("INFO: ", val) - if val.(map[string]any)["subject"] != "Patient/5b13b8fc-f387-4a95-bb80-5c22eeed7697" { - t.Error("filter test failed, values don't match") - } - } - } - } - - } - - } - if tt.name == "NullOps" { - cmd := exec.Command("grip", "query", "outNull", `V(["875e3325-c2ad-4d63-82ad-be8432bd415b","1842609e-7a40-4ba3-8a82-2fa061fcf30f"]).outNull("subject_Patient")`) - output, err := cmd.Output() - - if err != nil { - t.Error("Error:", err) - } - - json_string := strings.Split(string(output), "\n") - var jsonMap map[string]interface{} - var NullOp map[string]interface{} - - json.Unmarshal([]byte(json_string[0]), &jsonMap) - json.Unmarshal([]byte(json_string[1]), &NullOp) - - if val, ok := NullOp["vertex"]; ok { - if val == nil || reflect.DeepEqual(val, reflect.Zero(reflect.TypeOf(val)).Interface()) { - t.Error("Null Op Test Failed", val) - } - } - - //t.Log("NULL OP", NullOp) - } - if tt.name == "GraphQL_NullOps" { - payload := []byte(`{ - "query": "query ($filter: JSON) {\n documentReference (filter:$filter, first: 1) {\n file_name\n subject {\n id\n birthDate\n subject_observation {\n code\n }\n } \n }\n}\n", - "variables": {} - }`) - data, status := HTTP_REQUEST("synthea", "http://localhost:8201/api/graphql/", payload, t) - if status == false { - t.Error("test failed on HTTP Request") - } - if data, ok := data["data"].(map[string]any)["documentReference"].([]any); ok { - if data, ok := data[0].(map[string]any); ok { - t.Log("CHECK: ", data["file_name"] == "output/clinical_reports/53fefa32-fcbb-4ff8-8a92-55ee120877b7") - if data["file_name"] != "output/clinical_reports/53fefa32-fcbb-4ff8-8a92-55ee120877b7" { - t.Error() - } - if data, ok := data["subject"].([]any); ok { - if data, ok := data[0].(map[string]any); ok { - t.Log("CHECK: ", data["birthDate"] == "1913-10-29" || data["id"] != "fb60e763-e799-4d59-82a3-66977cc6696c") - if data["birthDate"] != "1913-10-29" || data["id"] != "fb60e763-e799-4d59-82a3-66977cc6696c" { - t.Error() - } - if data, ok := data["subject_observation"].([]any); ok { - if data, ok := data[0].(map[string]any); ok { - t.Log("CHECK: ", data["code"] == "Bilirubin.total [Mass/volume] in Serum or Plasma") - if data["code"] != "Bilirubin.total [Mass/volume] in Serum or Plasma" { - t.Error() - } - - } - } - } - } - } - } - } - if tt.name == "NullOP_Results" { - payload := []byte(`{ - "query": "query ($filter: JSON) {\n documentReference (filter:$filter, first: 7) {\n file_name\n subject {\n id\n birthDate\n subject_observation {\n code\n }\n } \n }\n}\n", - "variables": {} - }`) - data, status := HTTP_REQUEST("synthea", "http://localhost:8201/api/graphql/", payload, t) - if status == false { - t.Error("test failed on HTTP Request") - } - if data, ok := data["data"].(map[string]any)["documentReference"].([]any); ok { - if len(data) != 7 { - t.Error("Unexpected output length") - } - } else { - t.Error("Unexpected output structure") - } - } - } -} diff --git a/graphql_gen3/handler.go b/graphql_gen3/handler.go deleted file mode 100644 index c1a60b5..0000000 --- a/graphql_gen3/handler.go +++ /dev/null @@ -1,279 +0,0 @@ -/* -GraphQL Web endpoint -*/ - -package main - -import ( - "fmt" - "net/http" - "sync" - "time" - "io" - "encoding/json" - "errors" - - "github.com/bmeg/grip/gripql" - "github.com/bmeg/grip/log" - "github.com/graphql-go/handler" -) - - type UserAuth struct { - ExpiresAt time.Time - AuthorizedResources []any - } - - type TokenCache struct { - mu sync.Mutex - cache map[string]UserAuth - } - - func NewTokenCache() *TokenCache { - return &TokenCache{ - cache: make(map[string]UserAuth), - } - } - -// handle the graphql queries for a single endpoint -type graphHandler struct { - graph string - gqlHandler *handler.Handler - timestamp string - client gripql.Client - tokenCache *TokenCache - //schema *gripql.Graph -} - -// Handler is a GraphQL endpoint to query the Grip database -type Handler struct { - handlers map[string]*graphHandler - client gripql.Client -} - -type ServerError struct { - StatusCode int - Message string -} - -func (e *ServerError) Error() string { - return e.Message -} - -func getAuthMappings(url string, token string) (any, error) { - GetRequest, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Error(err) - return nil, err - } - - GetRequest.Header.Set("Authorization", token) - GetRequest.Header.Set("Accept", "application/json") - fetchedData, err := http.DefaultClient.Do(GetRequest) - if err != nil { - log.Error(err) - return nil, err - } - defer fetchedData.Body.Close() - - if fetchedData.StatusCode == http.StatusOK { - bodyBytes, err := io.ReadAll(fetchedData.Body) - if err != nil { - log.Error(err) - } - - var parsedData any - err = json.Unmarshal(bodyBytes, &parsedData) - if err != nil { - log.Error(err) - return nil, err - } - return parsedData, nil - - } - // code must be nonNull to get here, probably don't want to cache a failed state - empty_map := make(map[string]any) - err = errors.New("Arborist auth/mapping GET returned a non-200 status code: " + fetchedData.Status) - return empty_map, err - } - - func hasPermission(permissions []any) bool { - for _, permission := range permissions { - permission := permission.(map[string]any) - if (permission["service"] == "*" || permission["service"] == "peregrine") && - (permission["method"] == "*" || permission["method"] == "read") { - // fmt.Println("PERMISSIONS: ", permission) - return true - } - } - return false - } - - func getAllowedProjects(url string, token string) ([]any, error) { - var readAccessResources []string - authMappings, err := getAuthMappings(url, token) - if err != nil { - return nil, err - } - - // Iterate through /auth/mapping resultant dict checking for valid read permissions - for resourcePath, permissions := range authMappings.(map[string]any) { - if hasPermission(permissions.([]any)) { - readAccessResources = append(readAccessResources, resourcePath) - } - } - - s := make([]interface{}, len(readAccessResources)) - for i, v := range readAccessResources { - s[i] = v - } - return s, nil - } - - func handleError(err error, writer http.ResponseWriter) { - if ae, ok := err.(*ServerError); ok { - response := ServerError{StatusCode: ae.StatusCode, Message: ae.Message} - jsonResponse, _ := json.Marshal(response) - writer.WriteHeader(ae.StatusCode) - writer.Write(jsonResponse) - }else { - response := ServerError{StatusCode: http.StatusInternalServerError, Message: fmt.Sprintf("General error occured while setting up graphql handler")} - jsonResponse, _ := json.Marshal(response) - writer.WriteHeader(http.StatusInternalServerError) - writer.Write(jsonResponse) - } -} - -// NewClientHTTPHandler initilizes a new GraphQLHandler -func NewHTTPHandler(client gripql.Client, config map[string]string) (http.Handler, error) { - h := &Handler{ - client: client, - handlers: map[string]*graphHandler{}, - } - return h, nil -} - -// Static HTML that links to Apollo GraphQL query editor -var sandBox = ` -
- -` - -// ServeHTTP responds to HTTP graphql requests -func (gh *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - //log.Infof("Request for %s", request.URL.Path) - //If no graph provided, return the Query Editor page - if request.URL.Path == "" || request.URL.Path == "/" { - writer.Write([]byte(sandBox)) - return - } - //pathRE := regexp.MustCompile("/(.+)$") - //graphName := pathRE.FindStringSubmatch(request.URL.Path)[1] - graphName := request.URL.Path - var handler *graphHandler - var ok bool - if handler, ok = gh.handlers[graphName]; ok { - //Call the setup function. If nothing has changed it will return without doing anything - err := handler.setup(request.Header) - if err != nil{ - handleError(err, writer) - return - } - } else { - tokenCache := NewTokenCache() - //Graph handler was not found, so we'll need to set it up - var err error - handler, err = newGraphHandler(graphName, gh.client, request.Header, tokenCache) - if err != nil{ - handleError(err, writer) - return - } - gh.handlers[graphName] = handler - } - if handler != nil && handler.gqlHandler != nil { - handler.gqlHandler.ServeHTTP(writer, request) - } else { - response := ServerError{StatusCode: http.StatusInternalServerError, Message: fmt.Sprintf("General error occured while setting up graphql handler")} - jsonResponse, _ := json.Marshal(response) - writer.Write(jsonResponse) - } -} - -// newGraphHandler creates a new graphql handler from schema -func newGraphHandler(graph string, client gripql.Client, headers http.Header, userCache *TokenCache) (*graphHandler, error) { - o := &graphHandler{ - graph: graph, - client: client, - tokenCache: userCache, - } - err := o.setup(headers) - if err != nil { - return nil, err - } - return o, nil -} - -// LookupToken looks up a user token in the cache based on the token string. -// but resource lists can change when users are given new permissions so it's probably better not to cache resourceLists -func (tc *TokenCache) LookupResourceList(token string) ([]any) { - tc.mu.Lock() - defer tc.mu.Unlock() - auth, _:= tc.cache[token] - var resourceList []any - if auth.AuthorizedResources != nil { - resourceList = auth.AuthorizedResources - } - return resourceList -} - -// Check timestamp to see if schema needs to be updated or if the access token has changed -// If so rebuild the schema -func (gh *graphHandler) setup(headers http.Header) error { - // Check if Authorization header is present - authHeaders, ok := headers["Authorization"] - if !ok || len(authHeaders) == 0 { - return &ServerError{StatusCode: http.StatusUnauthorized, Message: "No authorization header provided."} - } - authToken := authHeaders[0] - - ts, _ := gh.client.GetTimestamp(gh.graph) - fmt.Println("HEADERS: ", headers) - - resourceList, err := getAllowedProjects("http://arborist-service/auth/mapping", authToken) - if err != nil { - log.WithFields(log.Fields{"graph": gh.graph, "error": err}).Error("auth/mapping fetch and processing step failed") - return &ServerError{StatusCode: http.StatusUnauthorized, Message: fmt.Sprintf("%s", err)} - } - - if ts == nil || ts.Timestamp != gh.timestamp || resourceList != nil { - fmt.Println("YOU ARE HERE +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++", resourceList) - log.WithFields(log.Fields{"graph": gh.graph}).Info("Reloading GraphQL schema") - schema, err := gh.client.GetSchema(gh.graph) - if err != nil { - log.WithFields(log.Fields{"graph": gh.graph, "error": err}).Error("GetSchema error") - return &ServerError{StatusCode: http.StatusInternalServerError, Message: fmt.Sprintf("%s", err)} - } - gqlSchema, err := buildGraphQLSchema(schema, gh.client, gh.graph, resourceList) - if err != nil { - log.WithFields(log.Fields{"graph": gh.graph, "error": err}).Error("GraphQL schema build failed") - gh.gqlHandler = nil - gh.timestamp = "" - return &ServerError{StatusCode: http.StatusInternalServerError, Message: "GraphQL schema build failed"} - } else { - log.WithFields(log.Fields{"graph": gh.graph}).Info("Built GraphQL schema") - gh.gqlHandler = handler.New(&handler.Config{ - Schema: gqlSchema, - }) - gh.timestamp = ts.Timestamp - } - } - - return nil -} diff --git a/graphql_gen3/mongo.yml b/graphql_gen3/mongo.yml deleted file mode 100644 index 7ee7b16..0000000 --- a/graphql_gen3/mongo.yml +++ /dev/null @@ -1,5 +0,0 @@ -Default: mongo -Drivers: - mongo: - MongoDB: - URL: mongodb://local-mongodb diff --git a/graphql_gen3/schema.json b/graphql_gen3/schema.json deleted file mode 100644 index 622d878..0000000 --- a/graphql_gen3/schema.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "graph": "test", - "vertices": [ - { - "gid": "File", - "label": "Vertex", - "data": { - "author": "STRING", - "category": "STRING", - "category_coding": "STRING", - "content_attachment_size": "STRING", - "content_attachment_url": "STRING", - "content_url": "STRING", - "context": "STRING", - "data_format": "STRING", - "data_type": "STRING", - "date": "STRING", - "file_name": "STRING", - "file_size": "NUMERIC", - "identifier": "STRING", - "md5sum": "STRING", - "object_id": "STRING", - "patient_id": "STRING", - "project_id": "STRING", - "status": "STRING", - "type": "STRING", - "type_coding": "STRING" - } - }, - { - "gid": "Observation", - "label": "Vertex", - "data": { - "bodySite": "STRING", - "category": "STRING", - "category_coding": "STRING", - "code": "STRING", - "code_coding": "STRING", - "effectiveDateTime": "STRING", - "id": "STRING", - "interpretation": "STRING", - "issued": "STRING", - "patient_id": "STRING", - "project_id": "STRING", - "status": "STRING", - "subject": "STRING", - "valueBoolean": "STRING", - "valueCodeableConcept": "STRING", - "valueDateTime": "STRING", - "valueInteger": "STRING", - "valueQuantity": "STRING", - "valueQuantity_unit": "STRING", - "valueQuantity_value": "NUMERIC", - "valueRange": "NUMERIC", - "valueRatio": "NUMERIC", - "valueSampledData": "STRING", - "valueString": "STRING" - } - }, - { - "gid": "Patient", - "label": "Vertex", - "data": { - "address": "STRING", - "address_geolocation_valueDecimal": [ - "NUMERIC" - ], - "birthDate": "STRING", - "communication": "STRING", - "disability_adjusted_life_years": "NUMERIC", - "gender": "STRING", - "identifier": [ - "STRING" - ], - "maritalStatus": "STRING", - "maritalStatus_coding": "STRING", - "patient_birthPlace_valueAddress": "STRING", - "patient_mothersMaidenName": "STRING", - "project_id": "STRING", - "quality_adjusted_life_years": "NUMERIC", - "telecom": "STRING", - "us_core_birthsex_code": "STRING", - "us_core_ethnicity": "STRING", - "us_core_ethnicity_coding": "STRING", - "us_core_ethnicity_coding_coding": "STRING", - "us_core_race": "STRING", - "us_core_race_coding": "STRING", - "us_core_race_coding_coding": "STRING" - } - } - ] -}