Skip to content

Commit

Permalink
Adds support for root node nested filters
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewpeterkort committed Jan 6, 2025
1 parent 0bb60aa commit d32293b
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 67 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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@c2dbbc7c623061153da0ae572333d2b1ec12f6af
RUN go install github.com/bmeg/grip@7a9207d1ea5f6a3c9e762729faa9fd2b117157bf
RUN make all

#FROM alpine
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22.5

require (
github.com/99designs/gqlgen v0.17.60
github.com/bmeg/grip v0.0.0-20250102222412-9654ae9fc7a2
github.com/bmeg/grip v0.0.0-20250106184429-7a9207d1ea5f
github.com/dop251/goja v0.0.0-20240707163329-b1681fb2a2f5
github.com/gin-gonic/gin v1.8.1
github.com/golang-jwt/jwt/v5 v5.2.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ github.com/bmeg/grip v0.0.0-20250102220137-c2dbbc7c6230 h1:SFEtSyntXBfvzUVJYfRcX
github.com/bmeg/grip v0.0.0-20250102220137-c2dbbc7c6230/go.mod h1:xBPjee1i7ZnwR9/UsHtblHyf7X7vAr5tFWTNtmtqmWg=
github.com/bmeg/grip v0.0.0-20250102222412-9654ae9fc7a2 h1:+lkAd7gbc+LQSL8BPxN68N85ovoNJ/7G9MnhphA/+Vk=
github.com/bmeg/grip v0.0.0-20250102222412-9654ae9fc7a2/go.mod h1:xBPjee1i7ZnwR9/UsHtblHyf7X7vAr5tFWTNtmtqmWg=
github.com/bmeg/grip v0.0.0-20250106184429-7a9207d1ea5f h1:HYanlfKeDg/ex23HeB1mrYTSNXv5C4wYrYcowpzmE2c=
github.com/bmeg/grip v0.0.0-20250106184429-7a9207d1ea5f/go.mod h1:xBPjee1i7ZnwR9/UsHtblHyf7X7vAr5tFWTNtmtqmWg=
github.com/bmeg/jsonpath v0.0.0-20210207014051-cca5355553ad h1:ICgBexeLB7iv/IQz4rsP+MimOXFZUwWSPojEypuOaQ8=
github.com/bmeg/jsonpath v0.0.0-20210207014051-cca5355553ad/go.mod h1:ft96Irkp72C7ZrUWRenG7LrF0NKMxXdRvsypo5Njhm4=
github.com/bmeg/jsonschema/v5 v5.3.4-0.20241111204732-55db82022a92 h1:Myx/j+WxfEg+P3nDaizR1hBpjKSLgvr4ydzgp1/1pAU=
Expand Down
18 changes: 15 additions & 3 deletions gql-gen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,23 @@ To generate new codegen after changing config or schema
go run github.com/99designs/gqlgen generate
```

## Local dev no auth

To start server with no auth checks for local development run:

```
grip server -w graphql=gql-gen.so -l graphql:auth=false
```

## Filters

Filters only currently supported on the first node that is queried. Ex: specimen for the example query below

## Example FHIR query:

```
query($filter: JSON){
specimen(filter: $filter first:10){
specimen(filter: $filter first:100){
id
subject{
... on PatientType{
Expand All @@ -49,8 +61,8 @@ query($filter: JSON){
{
"filter": {
"=": {
"id":
"example-uuid"
"processing.method.coding.display":
"WhateverFieldYouWant"
}
}
}
Expand Down
79 changes: 40 additions & 39 deletions gql-gen/graph/collectFields.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@ package graph
import (
"context"
"fmt"
"os"

"reflect"
"strings"
"strconv"
"strings"

"github.com/99designs/gqlgen/graphql"
"github.com/bmeg/grip/gripql"
"github.com/vektah/gqlparser/v2/ast"
"github.com/bmeg/grip/gripql/inspect"
"github.com/vektah/gqlparser/v2/ast"
//"google.golang.org/protobuf/types/known/structpb"

)

type Resolver struct {
GripDb gripql.Client
Schema *ast.Schema
}

type renderTreePath struct{
path []string
type renderTreePath struct {
path []string
unwindPath []string
}

Expand Down Expand Up @@ -67,8 +68,8 @@ func queryBuild(query **gripql.Query, selSet ast.SelectionSet, curElement string
}
if !exists {
rPath := rt.rFieldPaths[curElement]
rPath.path = append(rPath.path, firstTerm)
rt.rFieldPaths[curElement] = rPath
rPath.path = append(rPath.path, firstTerm)
rt.rFieldPaths[curElement] = rPath
}
currentTree[curElement] = rt.rFieldPaths[curElement]
} else {
Expand All @@ -77,7 +78,7 @@ func queryBuild(query **gripql.Query, selSet ast.SelectionSet, curElement string
fmt.Printf("DEF TYPE: %#v\n", sel.Definition.Type)
fmt.Printf("DEF TYPE ELEM: %#v\n", sel.Definition.Type.Elem)
fmt.Println("PARENT PATH: ", newParentPath)*/
if sel.Definition.Type.Elem != nil{
if sel.Definition.Type.Elem != nil {
rPath := rt.rFieldPaths[curElement]
rPath.unwindPath = append(rPath.unwindPath, newParentPath)
rt.rFieldPaths[curElement] = rPath
Expand Down Expand Up @@ -120,51 +121,53 @@ func (r *queryResolver) GetSelectedFieldsAst(ctx context.Context, sourceType str
render[path+"_data"] = "$" + checkpoint + "." + path
}
}

// Traverse back to f0 since only filters on the root node are applied currently
q = q.Select("f0")

// apply the unwinds on f0 before the filters so that the filters work properly.
applyUnwinds(&q, rt)
fmt.Println("QUERY BEFORE: ",q)

q = q.As("f0")
fmt.Printf("ARGS: %#v\n", resctx.Args)
applyFilters(&q, resctx.Args)

authList, ok := ctx.Value("auth_list").([]interface{})
if !ok {
return nil, fmt.Errorf("auth_list not found or invalid")
}

//fmt.Println("AUTHLIST: ", authList)
Has_Statement := &gripql.GraphStatement{Statement: &gripql.GraphStatement_Has{gripql.Within("auth_resource_path", authList...)}}
steps := inspect.PipelineSteps(q.Statements)
FilteredGS := []*gripql.GraphStatement{}
for i, v := range q.Statements{
steps_index, _ := strconv.Atoi(steps[i])
if i == 0{
FilteredGS = append(FilteredGS, v)
continue
}else if i == steps_index {
FilteredGS = append(FilteredGS, v, Has_Statement)
}else{
FilteredGS = append(FilteredGS, v)
}
err := applyFilters(&q, resctx.Args)
if err != nil {
return nil, err
}
err = applyRewinds(&q, rt)
q = q.As("f0")

q.Statements = FilteredGS
if os.Getenv("AUTH_ENABLED") == "true" {
authList, ok := ctx.Value("auth_list").([]interface{})
if !ok {
return nil, fmt.Errorf("auth_list not found or invalid")
}

Has_Statement := &gripql.GraphStatement{Statement: &gripql.GraphStatement_Has{gripql.Within("auth_resource_path", authList...)}}
steps := inspect.PipelineSteps(q.Statements)
FilteredGS := []*gripql.GraphStatement{}
for i, v := range q.Statements {
steps_index, _ := strconv.Atoi(steps[i])
if i == 0 {
FilteredGS = append(FilteredGS, v)
continue
} else if i == steps_index {
FilteredGS = append(FilteredGS, v, Has_Statement)
} else {
FilteredGS = append(FilteredGS, v)
}
}

q.Statements = FilteredGS
}
q = q.Render(render)
fmt.Println("QUERY AFTER: ", q)

result, err := r.GripDb.Traversal(context.Background(), &gripql.GraphQuery{Graph: "CALIPER", Query: q.Statements})
if err != nil {
fmt.Println("HELLO WE HERE: ", err)
return nil, fmt.Errorf("Traversal Error: %s", err)
}

out := []any{}
for r := range result {
values := r.GetRender().GetStructValue().AsMap()
//fmt.Printf("VALUES: %#v\n", values)
fmt.Printf("VALUES: %#v\n", values)
data := buildOutputTree(rt.rTree, values)
//fmt.Printf("DATA: %#v\n", data)
out = append(out, data)
Expand All @@ -177,9 +180,7 @@ func buildOutputTree(renderTree map[string]interface{}, values map[string]interf
for key, val := range renderTree {
switch v := val.(type) {
case renderTreePath:
fmt.Println("V: ",v)
for _, fieldPath := range v.path {
fmt.Println("FIELD PATH: ", fieldPath)
segments := strings.Split(fieldPath, ".")
current := output
for i := 0; i < len(segments)-1; i++ {
Expand Down
56 changes: 53 additions & 3 deletions gql-gen/graph/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,70 @@ import (
"strings"

"github.com/bmeg/grip/gripql"
"google.golang.org/protobuf/types/known/structpb"
)

func applyUnwinds(query **gripql.Query, rt *renderTree){
func applyUnwinds(query **gripql.Query, rt *renderTree) {
/* Assumes query is at f0 and only applies unwinds to that node currently*/
for _, val := range rt.rFieldPaths["f0"].unwindPath{
for _, val := range rt.rFieldPaths["f0"].unwindPath {
*query = (*query).Unwind(val)
fmt.Println(*query)
}
}

/*
Note since this function only has access to one row at a time it cannot merge
the rows and do a full rewind it can only add list objects to make it valid with the existing schema.
TODO: create proper merge function in grip
*/
func applyRewinds(query **gripql.Query, rt *renderTree) error {
/*
Applies a JS function to every row in the grip query
Args:
x - input row object
args - user defined function args used in the function
*/
jsfunc := `
function RewindObj(x, args) {
args.paths.sort((a, b) => b.split('.').length - a.split('.').length);
args.paths.forEach(path => {
const keys = path.split('.');
let current = x;
for (let i = 0; i < keys.length - 1; i++) {
if (current[keys[i]] === undefined) {
return;
}
current = current[keys[i]];
}
const lastKey = keys[keys.length - 1];
if (current[lastKey] !== undefined && !Array.isArray(current[lastKey])) {
current[lastKey] = [current[lastKey]];
}
});
return [x];
}
`

fields := make(map[string]*structpb.Value)
values := make([]*structpb.Value, len(rt.rFieldPaths["f0"].unwindPath))
for i, p := range rt.rFieldPaths["f0"].unwindPath {
values[i] = structpb.NewStringValue(p)
}

fields["paths"] = structpb.NewListValue(&structpb.ListValue{Values: values})
pbStruct := &structpb.Struct{Fields: fields}
*query = (*query).FlatMap(&gripql.Code{Function: "RewindObj", Source: jsfunc, Args: pbStruct})
return nil
}

func applyFilters(query **gripql.Query, args map[string]any) error {
//Todo: support "sort" operations
//fmt.Printf("FIRST: %v, TYPE: %T\n", args, args["first"])
if filter, ok := args["filter"]; ok {
if filter != nil && len(filter.(map[string]any)) > 0{
if filter != nil && len(filter.(map[string]any)) > 0 {
chainedFilter, err := applyJsonFilter(filter.(map[string]any))
if err != nil {
fmt.Println("ERR != NIL: ", err)
Expand Down
Loading

0 comments on commit d32293b

Please sign in to comment.