console, cli, cli-ext: query support for actions (close #4032) (#4318)

Co-authored-by: Aravind Shankar <aravind@hasura.io>
This commit is contained in:
Rishichandra Wawhal
2020-04-13 15:51:53 +05:30
committed by GitHub
parent 491a50b1e9
commit 9b66724b41
21 changed files with 179 additions and 1099 deletions

View File

@@ -11,6 +11,8 @@ The order and collapsed state of columns is now persisted across page navigation
### Bug fixes and improvements
- console: query support for actions (#4318)
- cli: query support for actions (#4318)
- cli: add retry_conf in event trigger for squashing migrations (close #4296) (#4324)
- cli: allow customization of server api paths (close #4016)
- cli: clean up migration files created during a failed migrate api (close #4312) (#4319)

View File

@@ -22,7 +22,7 @@ const handlePayload = (payload) => {
if (actions) {
try {
actions.forEach(a => {
actionSdl += getActionDefinitionSdl(a.name, a.definition.arguments, a.definition.output_type) + '\n';
actionSdl += getActionDefinitionSdl(a.name, a.definition.type, a.definition.arguments, a.definition.output_type) + '\n';
})
} catch (e) {
actionSdlError = e;
@@ -42,7 +42,7 @@ const handlePayload = (payload) => {
if (toDeriveOperation) {
try {
const derivation = deriveAction(toDeriveOperation, introspectionSchema, actionName);
const derivedActionSdl = getActionDefinitionSdl(derivation.action.name, derivation.action.arguments, derivation.action.output_type);
const derivedActionSdl = getActionDefinitionSdl(derivation.action.name, derivation.action.type, derivation.action.arguments, derivation.action.output_type);
const derivedTypesSdl = getTypesSdl(derivation.types);
sdl = `${derivedActionSdl}\n\n${derivedTypesSdl}\n\n${sdl}`
} catch (e) {

View File

@@ -21,7 +21,6 @@ require (
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/sessions v1.2.0 // indirect
github.com/gosimple/slug v1.9.0 // indirect
github.com/graphql-go/graphql v0.7.8
github.com/jinzhu/configor v1.1.1 // indirect
github.com/jinzhu/gorm v1.9.11 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0

View File

@@ -151,8 +151,6 @@ github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/z
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
github.com/graphql-go/graphql v0.7.8 h1:769CR/2JNAhLG9+aa8pfLkKdR0H+r5lsQqling5WwpU=
github.com/graphql-go/graphql v0.7.8/go.mod h1:k6yrAYQaSP59DC5UVxbgxESlmVyojThKdORUqGDGmrI=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=

View File

@@ -5,13 +5,9 @@ import (
"io/ioutil"
"path/filepath"
"github.com/graphql-go/graphql/language/ast"
"github.com/graphql-go/graphql/language/kinds"
"github.com/graphql-go/graphql/language/parser"
"github.com/hasura/graphql-engine/cli"
cliextension "github.com/hasura/graphql-engine/cli/metadata/actions/cli_extension"
"github.com/hasura/graphql-engine/cli/metadata/actions/editor"
"github.com/hasura/graphql-engine/cli/metadata/actions/printer"
"github.com/hasura/graphql-engine/cli/metadata/actions/types"
"github.com/hasura/graphql-engine/cli/plugins"
"github.com/hasura/graphql-engine/cli/util"
@@ -61,54 +57,21 @@ func (a *ActionConfig) Create(name string, introSchema interface{}, deriveFrom s
if err != nil {
return errors.Wrapf(err, "error in reading %s file", graphqlFileName)
}
doc, err := parser.Parse(parser.ParseParams{
Source: graphqlFileContent,
})
// Read actions.yaml
oldAction, err := a.GetActionsFileContent()
if err != nil {
return errors.Wrap(err, "unable to parse graphql")
return errors.Wrapf(err, "error in reading %s file", actionsFileName)
}
currentActionNames := make([]string, 0)
// Check if the action already exists, if yes throw error
for _, def := range doc.Definitions {
switch obj := def.(type) {
case *ast.ObjectDefinition:
if obj.Kind == kinds.ObjectDefinition && obj.Name.Kind == kinds.Name && obj.Name.Value == "Mutation" {
for _, field := range obj.Fields {
currentActionNames = append(currentActionNames, field.Name.Value)
}
}
case *ast.TypeExtensionDefinition:
if obj.Kind == kinds.TypeExtensionDefinition && obj.Definition.Name.Kind == kinds.Name && obj.Definition.Name.Value == "Mutation" {
for _, field := range obj.Definition.Fields {
currentActionNames = append(currentActionNames, field.Name.Value)
}
}
}
}
for _, currAction := range currentActionNames {
if currAction == name {
// check if action already present
for _, currAction := range oldAction.Actions {
if currAction.Name == name {
return fmt.Errorf("action %s already exists in %s", name, graphqlFileName)
}
}
// add extend type mutation
for index, def := range doc.Definitions {
switch obj := def.(type) {
case *ast.ObjectDefinition:
if obj.Kind == kinds.ObjectDefinition && obj.Name.Kind == kinds.Name && obj.Name.Value == "Mutation" {
newObj := &ast.TypeExtensionDefinition{
Kind: kinds.TypeExtensionDefinition,
Definition: obj,
}
doc.Definitions[index] = newObj
}
}
}
var defaultSDL string
if introSchema == nil {
defaultSDL = `
extend type Mutation {
defaultSDL = `type Mutation {
# Define your action as a mutation here
` + name + ` (arg1: SampleInput!): SampleOutput
}
@@ -136,54 +99,8 @@ input SampleInput {
}
defaultSDL = sdlToResp.SDL.Complete
}
newDoc, err := parser.Parse(parser.ParseParams{
Source: defaultSDL,
})
if err != nil {
return errors.Wrap(err, "error in parsing default sdl")
}
doc.Definitions = append(newDoc.Definitions, doc.Definitions...)
inputDupData := map[string][]int{}
objDupData := map[string][]int{}
for index, def := range doc.Definitions {
switch obj := def.(type) {
case *ast.ObjectDefinition:
if obj.GetName().Value != "Mutation" {
// check if name already exists
_, ok := objDupData[obj.GetName().Value]
if !ok {
objDupData[obj.GetName().Value] = []int{index}
} else {
objDupData[obj.GetName().Value] = append(objDupData[obj.GetName().Value], index)
}
}
case *ast.InputObjectDefinition:
_, ok := inputDupData[obj.GetName().Value]
if !ok {
inputDupData[obj.GetName().Value] = []int{index}
} else {
inputDupData[obj.GetName().Value] = append(inputDupData[obj.GetName().Value], index)
}
}
}
for _, indexes := range inputDupData {
if len(indexes) > 0 {
indexes = indexes[:len(indexes)-1]
}
for _, index := range indexes {
doc.Definitions = append(doc.Definitions[:index], doc.Definitions[index+1:]...)
}
}
for _, indexes := range objDupData {
if len(indexes) > 0 {
indexes = indexes[:len(indexes)-1]
}
for _, index := range indexes {
doc.Definitions = append(doc.Definitions[:index], doc.Definitions[index+1:]...)
}
}
defaultText := printer.Print(doc).(string)
data, err := editor.CaptureInputFromEditor(editor.GetPreferredEditorFromEnvironment, defaultText)
graphqlFileContent = defaultSDL + "\n" + graphqlFileContent
data, err := editor.CaptureInputFromEditor(editor.GetPreferredEditorFromEnvironment, graphqlFileContent)
if err != nil {
return errors.Wrap(err, "error in getting input from editor")
}
@@ -196,12 +113,7 @@ input SampleInput {
if err != nil {
return errors.Wrap(err, "error in converting sdl to metadata")
}
// Read actions.yaml
oldAction, err := a.GetActionsFileContent()
if err != nil {
return errors.Wrapf(err, "error in reading %s file", actionsFileName)
}
currentActionNames = make([]string, 0)
currentActionNames := make([]string, 0)
for actionIndex, action := range sdlFromResp.Actions {
for _, currAction := range currentActionNames {
if currAction == action.Name {
@@ -213,6 +125,7 @@ input SampleInput {
if action.Name == oldActionObj.Name {
sdlFromResp.Actions[actionIndex].Permissions = oldAction.Actions[oldActionIndex].Permissions
sdlFromResp.Actions[actionIndex].Definition.Kind = oldAction.Actions[oldActionIndex].Definition.Kind
sdlFromResp.Actions[actionIndex].Definition.Type = oldAction.Actions[oldActionIndex].Definition.Type
sdlFromResp.Actions[actionIndex].Definition.Handler = oldAction.Actions[oldActionIndex].Definition.Handler
sdlFromResp.Actions[actionIndex].Definition.ForwardClientHeaders = oldAction.Actions[oldActionIndex].Definition.ForwardClientHeaders
sdlFromResp.Actions[actionIndex].Definition.Headers = oldAction.Actions[oldActionIndex].Definition.Headers
@@ -500,12 +413,6 @@ func (a *ActionConfig) Export(metadata yaml.MapSlice) (map[string][]byte, error)
if err != nil {
return nil, errors.Wrap(err, "error in converting metadata to sdl")
}
doc, err := parser.Parse(parser.ParseParams{
Source: sdlToResp.SDL.Complete,
})
if err != nil {
return nil, errors.Wrap(err, "error in parsing sdl")
}
common.SetExportDefault()
commonByt, err := yaml.Marshal(common)
if err != nil {
@@ -513,7 +420,7 @@ func (a *ActionConfig) Export(metadata yaml.MapSlice) (map[string][]byte, error)
}
return map[string][]byte{
filepath.Join(a.MetadataDir, actionsFileName): commonByt,
filepath.Join(a.MetadataDir, graphqlFileName): []byte(printer.Print(doc).(string)),
filepath.Join(a.MetadataDir, graphqlFileName): []byte(sdlToResp.SDL.Complete),
}, nil
}

View File

@@ -1,952 +0,0 @@
package printer
import (
"fmt"
"strings"
"reflect"
"github.com/graphql-go/graphql/language/ast"
"github.com/graphql-go/graphql/language/visitor"
)
func getMapValue(m map[string]interface{}, key string) interface{} {
tokens := strings.Split(key, ".")
valMap := m
for _, token := range tokens {
v, ok := valMap[token]
if !ok {
return nil
}
switch v := v.(type) {
case []interface{}:
return v
case map[string]interface{}:
valMap = v
continue
default:
return v
}
}
return valMap
}
func getMapSliceValue(m map[string]interface{}, key string) []interface{} {
tokens := strings.Split(key, ".")
valMap := m
for _, token := range tokens {
v, ok := valMap[token]
if !ok {
return []interface{}{}
}
switch v := v.(type) {
case []interface{}:
return v
default:
return []interface{}{}
}
}
return []interface{}{}
}
func getMapValueString(m map[string]interface{}, key string) string {
tokens := strings.Split(key, ".")
valMap := m
for _, token := range tokens {
v, ok := valMap[token]
if !ok {
return ""
}
if v == nil {
return ""
}
switch v := v.(type) {
case map[string]interface{}:
valMap = v
continue
case string:
return v
default:
return fmt.Sprintf("%v", v)
}
}
return ""
}
func getDescription(raw interface{}) string {
var desc string
switch node := raw.(type) {
case ast.DescribableNode:
if sval := node.GetDescription(); sval != nil {
desc = sval.Value
}
case map[string]interface{}:
desc = getMapValueString(node, "Description.Value")
}
if desc != "" {
sep := ""
if strings.ContainsRune(desc, '\n') {
sep = "\n"
}
desc = join([]string{`"""`, desc, `"""`}, sep)
}
return desc
}
func toSliceString(slice interface{}) []string {
if slice == nil {
return []string{}
}
res := []string{}
switch reflect.TypeOf(slice).Kind() {
case reflect.Slice:
s := reflect.ValueOf(slice)
for i := 0; i < s.Len(); i++ {
elem := s.Index(i)
elemInterface := elem.Interface()
if elem, ok := elemInterface.(string); ok {
res = append(res, elem)
}
}
return res
default:
return res
}
}
func join(str []string, sep string) string {
ss := []string{}
// filter out empty strings
for _, s := range str {
if s == "" {
continue
}
ss = append(ss, s)
}
return strings.Join(ss, sep)
}
func wrap(start, maybeString, end string) string {
if maybeString == "" {
return maybeString
}
return start + maybeString + end
}
// Given array, print each item on its own line, wrapped in an indented "{ }" block.
func block(maybeArray interface{}) string {
s := toSliceString(maybeArray)
if len(s) == 0 {
return "{}"
}
return indent("{\n"+join(s, "\n")) + "\n}"
}
func indent(maybeString interface{}) string {
if maybeString == nil {
return ""
}
switch str := maybeString.(type) {
case string:
return strings.Replace(str, "\n", "\n ", -1)
}
return ""
}
var printDocASTReducer = map[string]visitor.VisitFunc{
"Name": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.Name:
return visitor.ActionUpdate, node.Value
case map[string]interface{}:
return visitor.ActionUpdate, getMapValue(node, "Value")
}
return visitor.ActionNoChange, nil
},
"Variable": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.Variable:
return visitor.ActionUpdate, fmt.Sprintf("$%v", node.Name)
case map[string]interface{}:
return visitor.ActionUpdate, "$" + getMapValueString(node, "Name")
}
return visitor.ActionNoChange, nil
},
// Document
"Document": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.Document:
definitions := toSliceString(node.Definitions)
return visitor.ActionUpdate, join(definitions, "\n\n") + "\n"
case map[string]interface{}:
definitions := toSliceString(getMapValue(node, "Definitions"))
return visitor.ActionUpdate, join(definitions, "\n\n") + "\n"
}
return visitor.ActionNoChange, nil
},
"OperationDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.OperationDefinition:
op := string(node.Operation)
name := fmt.Sprintf("%v", node.Name)
varDefs := wrap("(", join(toSliceString(node.VariableDefinitions), ", "), ")")
directives := join(toSliceString(node.Directives), " ")
selectionSet := fmt.Sprintf("%v", node.SelectionSet)
// Anonymous queries with no directives or variable definitions can use
// the query short form.
str := ""
if name == "" && directives == "" && varDefs == "" && op == ast.OperationTypeQuery {
str = selectionSet
} else {
str = join([]string{
op,
join([]string{name, varDefs}, ""),
directives,
selectionSet,
}, " ")
}
return visitor.ActionUpdate, str
case map[string]interface{}:
op := getMapValueString(node, "Operation")
name := getMapValueString(node, "Name")
varDefs := wrap("(", join(toSliceString(getMapValue(node, "VariableDefinitions")), ", "), ")")
directives := join(toSliceString(getMapValue(node, "Directives")), " ")
selectionSet := getMapValueString(node, "SelectionSet")
str := ""
if name == "" && directives == "" && varDefs == "" && op == ast.OperationTypeQuery {
str = selectionSet
} else {
str = join([]string{
op,
join([]string{name, varDefs}, ""),
directives,
selectionSet,
}, " ")
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"VariableDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.VariableDefinition:
variable := fmt.Sprintf("%v", node.Variable)
ttype := fmt.Sprintf("%v", node.Type)
defaultValue := fmt.Sprintf("%v", node.DefaultValue)
return visitor.ActionUpdate, variable + ": " + ttype + wrap(" = ", defaultValue, "")
case map[string]interface{}:
variable := getMapValueString(node, "Variable")
ttype := getMapValueString(node, "Type")
defaultValue := getMapValueString(node, "DefaultValue")
return visitor.ActionUpdate, variable + ": " + ttype + wrap(" = ", defaultValue, "")
}
return visitor.ActionNoChange, nil
},
"SelectionSet": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.SelectionSet:
str := block(node.Selections)
return visitor.ActionUpdate, str
case map[string]interface{}:
selections := getMapValue(node, "Selections")
str := block(selections)
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"Field": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.Argument:
name := fmt.Sprintf("%v", node.Name)
value := fmt.Sprintf("%v", node.Value)
return visitor.ActionUpdate, name + ": " + value
case map[string]interface{}:
alias := getMapValueString(node, "Alias")
name := getMapValueString(node, "Name")
args := toSliceString(getMapValue(node, "Arguments"))
directives := toSliceString(getMapValue(node, "Directives"))
selectionSet := getMapValueString(node, "SelectionSet")
str := join(
[]string{
wrap("", alias, ": ") + name + wrap("(", join(args, ", "), ")"),
join(directives, " "),
selectionSet,
},
" ",
)
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"Argument": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.FragmentSpread:
name := fmt.Sprintf("%v", node.Name)
directives := toSliceString(node.Directives)
return visitor.ActionUpdate, "..." + name + wrap(" ", join(directives, " "), "")
case map[string]interface{}:
name := getMapValueString(node, "Name")
value := getMapValueString(node, "Value")
return visitor.ActionUpdate, name + ": " + value
}
return visitor.ActionNoChange, nil
},
// Fragments
"FragmentSpread": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.InlineFragment:
typeCondition := fmt.Sprintf("%v", node.TypeCondition)
directives := toSliceString(node.Directives)
selectionSet := fmt.Sprintf("%v", node.SelectionSet)
return visitor.ActionUpdate, "... on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet
case map[string]interface{}:
name := getMapValueString(node, "Name")
directives := toSliceString(getMapValue(node, "Directives"))
return visitor.ActionUpdate, "..." + name + wrap(" ", join(directives, " "), "")
}
return visitor.ActionNoChange, nil
},
"InlineFragment": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case map[string]interface{}:
typeCondition := getMapValueString(node, "TypeCondition")
directives := toSliceString(getMapValue(node, "Directives"))
selectionSet := getMapValueString(node, "SelectionSet")
return visitor.ActionUpdate,
join([]string{
"...",
wrap("on ", typeCondition, ""),
join(directives, " "),
selectionSet,
}, " ")
}
return visitor.ActionNoChange, nil
},
"FragmentDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.FragmentDefinition:
name := fmt.Sprintf("%v", node.Name)
typeCondition := fmt.Sprintf("%v", node.TypeCondition)
directives := toSliceString(node.Directives)
selectionSet := fmt.Sprintf("%v", node.SelectionSet)
return visitor.ActionUpdate, "fragment " + name + " on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet
case map[string]interface{}:
name := getMapValueString(node, "Name")
typeCondition := getMapValueString(node, "TypeCondition")
directives := toSliceString(getMapValue(node, "Directives"))
selectionSet := getMapValueString(node, "SelectionSet")
return visitor.ActionUpdate, "fragment " + name + " on " + typeCondition + " " + wrap("", join(directives, " "), " ") + selectionSet
}
return visitor.ActionNoChange, nil
},
// Value
"IntValue": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.IntValue:
return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value)
case map[string]interface{}:
return visitor.ActionUpdate, getMapValueString(node, "Value")
}
return visitor.ActionNoChange, nil
},
"FloatValue": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.FloatValue:
return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value)
case map[string]interface{}:
return visitor.ActionUpdate, getMapValueString(node, "Value")
}
return visitor.ActionNoChange, nil
},
"StringValue": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.StringValue:
return visitor.ActionUpdate, `"` + fmt.Sprintf("%v", node.Value) + `"`
case map[string]interface{}:
return visitor.ActionUpdate, `"` + getMapValueString(node, "Value") + `"`
}
return visitor.ActionNoChange, nil
},
"BooleanValue": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.BooleanValue:
return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value)
case map[string]interface{}:
return visitor.ActionUpdate, getMapValueString(node, "Value")
}
return visitor.ActionNoChange, nil
},
"EnumValue": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.EnumValue:
return visitor.ActionUpdate, fmt.Sprintf("%v", node.Value)
case map[string]interface{}:
return visitor.ActionUpdate, getMapValueString(node, "Value")
}
return visitor.ActionNoChange, nil
},
"ListValue": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.ListValue:
return visitor.ActionUpdate, "[" + join(toSliceString(node.Values), ", ") + "]"
case map[string]interface{}:
return visitor.ActionUpdate, "[" + join(toSliceString(getMapValue(node, "Values")), ", ") + "]"
}
return visitor.ActionNoChange, nil
},
"ObjectValue": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.ObjectValue:
return visitor.ActionUpdate, "{" + join(toSliceString(node.Fields), ", ") + "}"
case map[string]interface{}:
return visitor.ActionUpdate, "{" + join(toSliceString(getMapValue(node, "Fields")), ", ") + "}"
}
return visitor.ActionNoChange, nil
},
"ObjectField": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.ObjectField:
name := fmt.Sprintf("%v", node.Name)
value := fmt.Sprintf("%v", node.Value)
return visitor.ActionUpdate, name + ": " + value
case map[string]interface{}:
name := getMapValueString(node, "Name")
value := getMapValueString(node, "Value")
return visitor.ActionUpdate, name + ": " + value
}
return visitor.ActionNoChange, nil
},
// Directive
"Directive": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.Directive:
name := fmt.Sprintf("%v", node.Name)
args := toSliceString(node.Arguments)
return visitor.ActionUpdate, "@" + name + wrap("(", join(args, ", "), ")")
case map[string]interface{}:
name := getMapValueString(node, "Name")
args := toSliceString(getMapValue(node, "Arguments"))
return visitor.ActionUpdate, "@" + name + wrap("(", join(args, ", "), ")")
}
return visitor.ActionNoChange, nil
},
// Type
"Named": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.Named:
return visitor.ActionUpdate, fmt.Sprintf("%v", node.Name)
case map[string]interface{}:
return visitor.ActionUpdate, getMapValueString(node, "Name")
}
return visitor.ActionNoChange, nil
},
"List": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.List:
return visitor.ActionUpdate, "[" + fmt.Sprintf("%v", node.Type) + "]"
case map[string]interface{}:
return visitor.ActionUpdate, "[" + getMapValueString(node, "Type") + "]"
}
return visitor.ActionNoChange, nil
},
"NonNull": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.NonNull:
return visitor.ActionUpdate, fmt.Sprintf("%v", node.Type) + "!"
case map[string]interface{}:
return visitor.ActionUpdate, getMapValueString(node, "Type") + "!"
}
return visitor.ActionNoChange, nil
},
// Type System Definitions
"SchemaDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.SchemaDefinition:
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
"schema",
join(directives, " "),
block(node.OperationTypes),
}, " ")
return visitor.ActionUpdate, str
case map[string]interface{}:
operationTypes := toSliceString(getMapValue(node, "OperationTypes"))
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
"schema",
join(directives, " "),
block(operationTypes),
}, " ")
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"OperationTypeDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.OperationTypeDefinition:
str := fmt.Sprintf("%v: %v", node.Operation, node.Type)
return visitor.ActionUpdate, str
case map[string]interface{}:
operation := getMapValueString(node, "Operation")
ttype := getMapValueString(node, "Type")
str := fmt.Sprintf("%v: %v", operation, ttype)
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"ScalarDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.ScalarDefinition:
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
"scalar",
fmt.Sprintf("%v", node.Name),
join(directives, " "),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
"scalar",
name,
join(directives, " "),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"ObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.ObjectDefinition:
name := fmt.Sprintf("%v", node.Name)
interfaces := toSliceString(node.Interfaces)
fields := node.Fields
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
"type",
name,
wrap("implements ", join(interfaces, " & "), ""),
join(directives, " "),
block(fields),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
interfaces := toSliceString(getMapValue(node, "Interfaces"))
fields := getMapValue(node, "Fields")
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
"type",
name,
wrap("implements ", join(interfaces, " & "), ""),
join(directives, " "),
block(fields),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"FieldDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.FieldDefinition:
name := fmt.Sprintf("%v", node.Name)
ttype := fmt.Sprintf("%v", node.Type)
args := toSliceString(node.Arguments)
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
hasArgDesc := false
for _, arg := range node.Arguments {
if arg.Description != nil && arg.Description.Value != "" {
hasArgDesc = true
break
}
}
var argsStr string
if hasArgDesc {
argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)")
} else {
argsStr = wrap("(", join(args, ", "), ")")
}
str := name + argsStr + ": " + ttype + wrap(" ", join(directives, " "), "")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
ttype := getMapValueString(node, "Type")
args := toSliceString(getMapValue(node, "Arguments"))
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
hasArgDesc := false
for _, arg := range args {
if strings.HasPrefix(strings.TrimSpace(arg), `"""`) {
hasArgDesc = true
break
}
}
var argsStr string
if hasArgDesc {
argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)")
} else {
argsStr = wrap("(", join(args, ", "), ")")
}
str := name + argsStr + ": " + ttype + wrap(" ", join(directives, " "), "")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"InputValueDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.InputValueDefinition:
name := fmt.Sprintf("%v", node.Name)
ttype := fmt.Sprintf("%v", node.Type)
defaultValue := fmt.Sprintf("%v", node.DefaultValue)
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
name + ": " + ttype,
wrap("= ", defaultValue, ""),
join(directives, " "),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("\n%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
ttype := getMapValueString(node, "Type")
defaultValue := getMapValueString(node, "DefaultValue")
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
name + ": " + ttype,
wrap("= ", defaultValue, ""),
join(directives, " "),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("\n%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"InterfaceDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.InterfaceDefinition:
name := fmt.Sprintf("%v", node.Name)
fields := node.Fields
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
"interface",
name,
join(directives, " "),
block(fields),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
fields := getMapValue(node, "Fields")
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
"interface",
name,
join(directives, " "),
block(fields),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"UnionDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.UnionDefinition:
name := fmt.Sprintf("%v", node.Name)
types := toSliceString(node.Types)
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
"union",
name,
join(directives, " "),
"= " + join(types, " | "),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
types := toSliceString(getMapValue(node, "Types"))
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
"union",
name,
join(directives, " "),
"= " + join(types, " | "),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"EnumDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.EnumDefinition:
name := fmt.Sprintf("%v", node.Name)
values := node.Values
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
"enum",
name,
join(directives, " "),
block(values),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
values := getMapValue(node, "Values")
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
"enum",
name,
join(directives, " "),
block(values),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"EnumValueDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.EnumValueDefinition:
name := fmt.Sprintf("%v", node.Name)
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
name,
join(directives, " "),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("\n%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
name,
join(directives, " "),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("\n%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"InputObjectDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.InputObjectDefinition:
name := fmt.Sprintf("%v", node.Name)
fields := node.Fields
directives := []string{}
for _, directive := range node.Directives {
directives = append(directives, fmt.Sprintf("%v", directive.Name))
}
str := join([]string{
"input",
name,
join(directives, " "),
block(fields),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
fields := getMapValue(node, "Fields")
directives := []string{}
for _, directive := range getMapSliceValue(node, "Directives") {
directives = append(directives, fmt.Sprintf("%v", directive))
}
str := join([]string{
"input",
name,
join(directives, " "),
block(fields),
}, " ")
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"TypeExtensionDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.TypeExtensionDefinition:
definition := fmt.Sprintf("%v", node.Definition)
str := "extend " + definition
return visitor.ActionUpdate, str
case map[string]interface{}:
definition := getMapValueString(node, "Definition")
str := "extend " + definition
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
"DirectiveDefinition": func(p visitor.VisitFuncParams) (string, interface{}) {
switch node := p.Node.(type) {
case *ast.DirectiveDefinition:
args := toSliceString(node.Arguments)
hasArgDesc := false
for _, arg := range node.Arguments {
if arg.Description != nil && arg.Description.Value != "" {
hasArgDesc = true
break
}
}
var argsStr string
if hasArgDesc {
argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)")
} else {
argsStr = wrap("(", join(args, ", "), ")")
}
str := fmt.Sprintf("directive @%v%v on %v", node.Name, argsStr, join(toSliceString(node.Locations), " | "))
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
case map[string]interface{}:
name := getMapValueString(node, "Name")
locations := toSliceString(getMapValue(node, "Locations"))
args := toSliceString(getMapValue(node, "Arguments"))
hasArgDesc := false
for _, arg := range args {
if strings.HasPrefix(strings.TrimSpace(arg), `"""`) {
hasArgDesc = true
break
}
}
var argsStr string
if hasArgDesc {
argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)")
} else {
argsStr = wrap("(", join(args, ", "), ")")
}
str := fmt.Sprintf("directive @%v%v on %v", name, argsStr, join(locations, " | "))
if desc := getDescription(node); desc != "" {
str = fmt.Sprintf("%s\n%s", desc, str)
}
return visitor.ActionUpdate, str
}
return visitor.ActionNoChange, nil
},
}
func Print(astNode ast.Node) (printed interface{}) {
defer func() interface{} {
if r := recover(); r != nil {
return fmt.Sprintf("%v", astNode)
}
return printed
}()
printed = visitor.Visit(astNode, &visitor.VisitorOptions{
LeaveKindMap: printDocASTReducer,
}, nil)
return printed
}

View File

@@ -29,6 +29,7 @@ type Common struct {
func (c *Common) SetExportDefault() {
for index := range c.Actions {
c.Actions[index].Definition.Arguments = nil
c.Actions[index].Definition.Type = ""
c.Actions[index].Definition.OutputType = ""
}
@@ -55,8 +56,18 @@ type Action struct {
Permissions []yaml.MapSlice `json:"-" yaml:"permissions,omitempty"`
}
type ActionType string
const (
// For query type
ActionTypeQuery ActionType = "query"
// For mutation type
ActionTypeMutation = "mutation"
)
type ActionDef struct {
Kind string `json:"kind" yaml:"kind"`
Type ActionType `json:"type" yaml:"type,omitempty"`
Handler string `json:"handler" yaml:"handler"`
Arguments []yaml.MapSlice `json:"arguments" yaml:"arguments,omitempty"`
OutputType string `json:"output_type" yaml:"output_type,omitempty"`

View File

@@ -18,6 +18,7 @@ import {
resetDerivedActionParentOperation,
} from './reducer';
import { createAction } from '../ServerIO';
import { getActionDefinitionFromSdl } from '../../../../shared/utils/sdlUtils';
const AddAction = ({
handler,
@@ -82,6 +83,15 @@ const AddAction = ({
!actionParseTimer &&
!typedefParseTimer;
let actionType;
if (!actionDefinitionError) {
// TODO optimise
const { type, error } = getActionDefinitionFromSdl(actionDefinitionSdl);
if (!error) {
actionType = type;
}
}
return (
<div>
<Helmet title={'Add Action - Actions | Hasura'} />
@@ -110,12 +120,18 @@ const AddAction = ({
service="create-action"
/>
<hr />
<KindEditor
value={kind}
onChange={kindOnChange}
className={styles.add_mar_bottom_mid}
/>
<hr />
{
actionType === "query" ? null : (
<React.Fragment>
<KindEditor
value={kind}
onChange={kindOnChange}
className={styles.add_mar_bottom_mid}
/>
<hr />
</React.Fragment>
)
}
<HeadersConfEditor
forwardClientHeaders={forwardClientHeaders}
toggleForwardClientHeaders={toggleForwardClientHeaders}

View File

@@ -7,7 +7,7 @@ import SDLEditor from '../../../../Common/AceEditor/SDLEditor';
const editorLabel = 'Action definition';
const editorTooltip =
'Define the action as mutation using GraphQL SDL. You can use the custom types already defined by you or define new types in the new types definition editor below.';
'Define the action as a query or a mutation using GraphQL SDL. You can use the custom types already defined by you or define new types in the new types definition editor below.';
const ActionDefinitionEditor = ({
value,

View File

@@ -31,6 +31,7 @@ export const generateActionDefinition = ({
outputType,
kind = 'synchronous',
handler,
actionType,
headers,
forwardClientHeaders,
}) => {
@@ -39,6 +40,7 @@ export const generateActionDefinition = ({
kind,
output_type: outputType,
handler,
type: actionType,
headers: transformHeaders(headers),
forward_client_headers: forwardClientHeaders,
};
@@ -290,3 +292,4 @@ export const getOverlappingTypeConfirmation = (
return isOk;
};

View File

@@ -23,7 +23,7 @@ class Landing extends React.Component {
// imgUrl={`${globals.assetsPath}/common/img/remote_schema.png`} // TODO: update image & description
imgUrl={actionsArchDiagram}
imgAlt="Actions"
description="Actions are custom mutations that are resolved via HTTP handlers. Actions can be used to carry out complex data validations, data enrichment from external sources or execute just about any custom business logic."
description="Actions are custom queries or mutations that are resolved via HTTP handlers. Actions can be used to carry out complex data validations, data enrichment from external sources or execute just about any custom business logic."
/>
<hr className={styles.clear_fix} />
</div>

View File

@@ -18,6 +18,7 @@ import {
toggleForwardClientHeaders as toggleFCH,
} from './reducer';
import { saveAction, deleteAction } from '../ServerIO';
import { getActionDefinitionFromSdl } from '../../../../shared/utils/sdlUtils';
const ActionEditor = ({
currentAction,
@@ -85,6 +86,15 @@ const ActionEditor = ({
!actionDefinitionTimer &&
!typeDefinitionTimer;
let actionType;
if (!actionDefinitionError) {
// TODO optimise
const { type, error } = getActionDefinitionFromSdl(actionDefinitionSdl);
if (!error) {
actionType = type;
}
}
return (
<div>
<Helmet title={`Modify Action - ${actionName} - Actions | Hasura`} />
@@ -112,8 +122,18 @@ const ActionEditor = ({
service="create-action"
/>
<hr />
<KindEditor value={kind} onChange={kindOnChange} />
<hr />
{
actionType === "query" ? null : (
<React.Fragment>
<KindEditor
value={kind}
onChange={kindOnChange}
className={styles.add_mar_bottom_mid}
/>
<hr />
</React.Fragment>
)
}
<HeadersConfEditor
forwardClientHeaders={forwardClientHeaders}
toggleForwardClientHeaders={toggleForwardClientHeaders}

View File

@@ -8,6 +8,7 @@ import {
getActionName,
getActionOutputType,
getActionComment,
getActionType
} from '../utils';
import { getActionTypes } from '../Common/utils';
@@ -18,6 +19,7 @@ export const getModifyState = (currentAction, allTypes) => {
actionDefinition: {
sdl: getActionDefinitionSdl(
getActionName(currentAction),
getActionType(currentAction),
getActionArguments(currentAction),
getActionOutputType(currentAction),
getActionComment(currentAction)

View File

@@ -119,6 +119,7 @@ export const createAction = () => (dispatch, getState) => {
outputType,
error: actionDefError,
comment: actionDescription,
type: actionType
} = getActionDefinitionFromSdl(rawState.actionDefinition.sdl);
if (actionDefError) {
return dispatch(
@@ -140,6 +141,7 @@ export const createAction = () => (dispatch, getState) => {
handler: rawState.handler,
kind: rawState.kind,
types,
actionType,
name: actionName,
arguments: args,
outputType,
@@ -235,6 +237,7 @@ export const saveAction = currentAction => (dispatch, getState) => {
name: actionName,
arguments: args,
outputType,
type: actionType,
error: actionDefError,
comment: actionDescription,
} = getActionDefinitionFromSdl(rawState.actionDefinition.sdl);
@@ -258,6 +261,7 @@ export const saveAction = currentAction => (dispatch, getState) => {
handler: rawState.handler,
kind: rawState.kind,
types,
actionType,
name: actionName,
arguments: args,
outputType,

View File

@@ -26,6 +26,10 @@ export const getActionArguments = action => {
return action.action_defn.arguments;
};
export const getActionType = action => {
return action.action_defn.type
};
export const getAllActions = getState => {
return getState().actions.common.actions;
};

View File

@@ -114,6 +114,7 @@ class GraphiQLWrapper extends Component {
const { action, types } = derivedOperationMetadata;
const actionsSdl = getActionDefinitionSdl(
action.name,
action.type,
action.arguments,
action.output_type
);

View File

@@ -9,7 +9,12 @@ import {
} from 'graphql';
import { wrapTypename, getAstTypeMetadata } from './wrappingTypeUtils';
import { inbuiltTypes } from './hasuraCustomTypeUtils';
import { getTypeFields, getUnderlyingType } from './graphqlSchemaUtils';
import {
getTypeFields,
getUnderlyingType,
getOperationType,
} from './graphqlSchemaUtils';
import { isValidOperationName } from './sdlUtils';
export const validateOperation = (operationString, clientSchema) => {
// parse operation string
@@ -27,16 +32,16 @@ export const validateOperation = (operationString, clientSchema) => {
);
}
if (operationAst.definitions.find(d => d.kind === 'FragmentDefinition')) {
if (operationAst.definitions.some(d => d.kind === 'FragmentDefinition')) {
throw Error('fragments are not supported');
}
if (operationAst.definitions.find(d => d.operation !== 'mutation')) {
throw Error('queries cannot be derived into actions');
if (operationAst.definitions.some(d => !isValidOperationName(d.operation))) {
throw Error('subscriptions cannot be derived into actions');
}
operationAst.definitions = operationAst.definitions.filter(
d => d.operation === 'mutation'
operationAst.definitions = operationAst.definitions.filter(d =>
isValidOperationName(d.operation)
);
// throw error if the AST is empty
@@ -88,6 +93,8 @@ const deriveAction = (
throw e;
}
const operation = operationAst.definitions[0].operation;
const variables = operationAst.definitions[0].variableDefinitions;
// get operation name
@@ -108,7 +115,7 @@ const deriveAction = (
};
const allHasuraTypes = clientSchema._typeMap;
const operationType = clientSchema._mutationType; // TODO better handling for queries
const operationType = getOperationType(clientSchema, operation);
const actionArguments = [];
const newTypes = {};
@@ -232,6 +239,7 @@ const deriveAction = (
types: Object.values(newTypes),
action: {
name: actionName,
type: operation,
arguments: actionArguments,
output_type: actionOutputTypename,
},

View File

@@ -1,5 +1,15 @@
import { isWrappingType, isListType, isNonNullType } from 'graphql';
export const getOperationType = (schema, operation) => {
if (operation === 'query') {
return schema._queryType;
}
if (operation === 'subscription') {
return schema._subscriptionType
}
return schema._mutationType;
};
export const getMutationType = schema => {
return schema._mutationType;
};

View File

@@ -6,6 +6,28 @@ import {
hydrateTypeRelationships,
} from './hasuraCustomTypeUtils';
export const isValidOperationName = operationName => {
return operationName === 'query' || operationName === 'mutation';
};
const isValidOperationType = operationType => {
return operationType === 'Mutation' || operationType === 'Query';
};
const getActionTypeFromOperationType = operationType => {
if (operationType === 'Query') {
return 'query';
}
return 'mutation';
};
const getOperationTypeFromActionType = operationType => {
if (operationType === 'query') {
return 'Query';
}
return 'Mutation';
};
const getAstEntityDescription = def => {
return def.description ? def.description.value.trim() : null;
};
@@ -118,7 +140,7 @@ export const getTypesFromSdl = sdl => {
return typeDefinition;
};
const getActionFromMutationAstDef = astDef => {
const getActionFromOperationAstDef = astDef => {
const definition = {
name: '',
arguments: [],
@@ -146,7 +168,6 @@ const getActionFromMutationAstDef = astDef => {
};
export const getActionDefinitionFromSdl = sdl => {
const schemaAst = sdlParse(sdl);
const definition = {
name: '',
arguments: [],
@@ -154,18 +175,30 @@ export const getActionDefinitionFromSdl = sdl => {
comment: '',
error: null,
};
let schemaAst;
try {
schemaAst = sdlParse(sdl);
} catch {
definition.error = 'Invalid SDL';
return definition;
}
if (schemaAst.definitions.length > 1) {
definition.error = 'Action must be defined under a single "Mutation" type';
definition.error =
'Action must be defined under a single "Mutation" type or a "Query" type';
return definition;
}
const sdlDef = schemaAst.definitions[0];
if (sdlDef.name.value !== 'Mutation') {
definition.error = 'Action must be defined under a "Mutation" type';
if (!isValidOperationType(sdlDef.name.value)) {
definition.error =
'Action must be defined under a "Mutation" or a "Query" type';
return definition;
}
const actionType = getActionTypeFromOperationType(sdlDef.name.value);
if (sdlDef.fields.length > 1) {
const definedActions = sdlDef.fields
.map(f => `"${f.name.value}"`)
@@ -176,7 +209,8 @@ export const getActionDefinitionFromSdl = sdl => {
return {
...definition,
...getActionFromMutationAstDef(sdlDef.fields[0]),
type: actionType,
...getActionFromOperationAstDef(sdlDef.fields[0]),
};
};
@@ -250,9 +284,15 @@ export const getTypesSdl = _types => {
return sdl;
};
export const getActionDefinitionSdl = (name, args, outputType, description) => {
export const getActionDefinitionSdl = (
name,
actionType,
args,
outputType,
description
) => {
return getObjectTypeSdl({
name: 'Mutation',
name: getOperationTypeFromActionType(actionType),
fields: [
{
name,
@@ -276,27 +316,31 @@ export const getServerTypesFromSdl = (sdl, existingTypes) => {
export const getAllActionsFromSdl = sdl => {
const ast = sdlParse(sdl);
ast.definitions = ast.definitions.filter(d => d.name.value === 'Mutation');
const actions = [];
ast.definitions.forEach(d => {
d.fields.forEach(f => {
const action = getActionFromMutationAstDef(f);
actions.push({
name: action.name,
definition: {
arguments: action.arguments,
output_type: action.outputType,
},
ast.definitions
.filter(d => isValidOperationType(d.name.value))
.forEach(d => {
d.fields.forEach(f => {
const action = getActionFromOperationAstDef(f);
actions.push({
name: action.name,
definition: {
type: getActionTypeFromOperationType(d.name.value),
arguments: action.arguments,
output_type: action.outputType,
},
});
});
});
});
return actions;
};
export const getAllTypesFromSdl = sdl => {
const ast = sdlParse(sdl);
ast.definitions = ast.definitions.filter(d => d.name.value !== 'Mutation');
ast.definitions = ast.definitions.filter(
d => !isValidOperationType(d.name.value)
);
const types = ast.definitions.map(d => {
return getTypeFromAstDef(d);
});
@@ -308,6 +352,7 @@ export const getSdlComplete = (allActions, allTypes) => {
allActions.forEach(a => {
sdl += `extend ${getActionDefinitionSdl(
a.action_name,
a.action_defn.type,
a.action_defn.arguments,
a.action_defn.output_type,
a.comment