diff --git a/tools/generator.go b/tools/generator.go index 56bbdb08c..a6a7b1ae7 100644 --- a/tools/generator.go +++ b/tools/generator.go @@ -153,6 +153,16 @@ func (g *Generator) decorateTools() error { } } } + + // for all responses that have a link for to-many fields, + // decorate the data mapping now that we have all inputs and responses generated + for _, response := range tool.Config.Response { + if !tool.Action.IsArbitraryFunction() && response.Link != nil && response.Link.ToolId != "" && response.Link.Data[0].Path == nil { + response.Link.Data[0].Path = &toolsproto.JsonPath{ + Path: tool.getIDResponseFieldPath(), + } + } + } } return nil @@ -571,7 +581,9 @@ func (g *Generator) makeResponsesForModel(model *proto.Model, pathPrefix string, fields = append(fields, embeddedFields...) } - continue + if !f.IsHasMany() { + continue + } } config := &toolsproto.ResponseFieldConfig{ @@ -618,6 +630,19 @@ func (g *Generator) makeResponsesForModel(model *proto.Model, pathPrefix string, } } + if f.IsHasMany() { + if getToolID, input := g.findListByForeignID(f.Type.ModelName.Value, f.InverseFieldName.Value); getToolID != "" { + config.Link = &toolsproto.ActionLink{ + ToolId: getToolID, + Data: []*toolsproto.DataMapping{ + { + Key: input.FieldLocation.Path, + }, + }, + } + } + } + fields = append(fields, config) } @@ -691,6 +716,18 @@ func (g *Generator) findGetByIDTool(modelName string) string { return "" } +// findListByForeignID will search for a list tool for the given model which takes a specific foreign key as an input +// It will also return the request input field for that tool +func (g *Generator) findListByForeignID(modelName string, inverseFieldName string) (string, *toolsproto.RequestFieldConfig) { + for id, tool := range g.Tools { + if input := tool.getInput("$.where." + inverseFieldName + ".id.equals"); tool.Model.Name == modelName && tool.Action.Type == proto.ActionType_ACTION_TYPE_LIST && input != nil { + return id, input + } + } + + return "", nil +} + // findAllByIDTools searches for the tools that operate on the given model and take in an ID as an input; Returns a map of // tool IDs and the path of the input field; e.g. getPost: $.id. Results will omit the given tool id (ignoreID). // diff --git a/tools/proto/tools.pb.go b/tools/proto/tools.pb.go index ae97c45f3..8ebf94632 100644 --- a/tools/proto/tools.pb.go +++ b/tools/proto/tools.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v4.25.0 +// protoc-gen-go v1.31.0 +// protoc v4.24.4 // source: tools.proto package proto @@ -490,6 +490,7 @@ type ResponseFieldConfig struct { HelpText *StringTemplate `protobuf:"bytes,8,opt,name=help_text,json=helpText,proto3,oneof" json:"help_text,omitempty"` Sortable bool `protobuf:"varint,9,opt,name=sortable,proto3" json:"sortable,omitempty"` // Based on @sortable() // Set if this field is a FK and link to a get/list action on the target model + // Or set to an appropriate list action if this field is a to-many field - for example, linking to listSaleItems(sale.id) Link *ActionLink `protobuf:"bytes,10,opt,name=link,proto3,oneof" json:"link,omitempty"` // for file fields only, display images inline ImagePreview bool `protobuf:"varint,11,opt,name=image_preview,json=imagePreview,proto3" json:"image_preview,omitempty"` @@ -1473,7 +1474,7 @@ func file_tools_proto_rawDescGZIP() []byte { } var file_tools_proto_msgTypes = make([]protoimpl.MessageInfo, 13) -var file_tools_proto_goTypes = []any{ +var file_tools_proto_goTypes = []interface{}{ (*Capabilities)(nil), // 0: tools.Capabilities (*ActionConfig)(nil), // 1: tools.ActionConfig (*RequestFieldConfig)(nil), // 2: tools.RequestFieldConfig @@ -1543,7 +1544,7 @@ func file_tools_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_tools_proto_msgTypes[0].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Capabilities); i { case 0: return &v.state @@ -1555,7 +1556,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[1].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ActionConfig); i { case 0: return &v.state @@ -1567,7 +1568,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[2].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RequestFieldConfig); i { case 0: return &v.state @@ -1579,7 +1580,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[3].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ResponseFieldConfig); i { case 0: return &v.state @@ -1591,7 +1592,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[4].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DefaultValue); i { case 0: return &v.state @@ -1603,7 +1604,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[5].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*StringTemplate); i { case 0: return &v.state @@ -1615,7 +1616,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[6].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*JsonPath); i { case 0: return &v.state @@ -1627,7 +1628,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[7].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ExternalLink); i { case 0: return &v.state @@ -1639,7 +1640,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[8].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ActionLink); i { case 0: return &v.state @@ -1651,7 +1652,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[9].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CursorPaginationConfig); i { case 0: return &v.state @@ -1663,7 +1664,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[10].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DataMapping); i { case 0: return &v.state @@ -1675,7 +1676,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[11].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CursorPaginationConfig_FieldConfig); i { case 0: return &v.state @@ -1687,7 +1688,7 @@ func file_tools_proto_init() { return nil } } - file_tools_proto_msgTypes[12].Exporter = func(v any, i int) any { + file_tools_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CursorPaginationConfig_PageSizeConfig); i { case 0: return &v.state @@ -1700,18 +1701,18 @@ func file_tools_proto_init() { } } } - file_tools_proto_msgTypes[1].OneofWrappers = []any{} - file_tools_proto_msgTypes[2].OneofWrappers = []any{} - file_tools_proto_msgTypes[3].OneofWrappers = []any{} - file_tools_proto_msgTypes[4].OneofWrappers = []any{ + file_tools_proto_msgTypes[1].OneofWrappers = []interface{}{} + file_tools_proto_msgTypes[2].OneofWrappers = []interface{}{} + file_tools_proto_msgTypes[3].OneofWrappers = []interface{}{} + file_tools_proto_msgTypes[4].OneofWrappers = []interface{}{ (*DefaultValue_String_)(nil), (*DefaultValue_Integer)(nil), (*DefaultValue_Float)(nil), (*DefaultValue_Bool)(nil), } - file_tools_proto_msgTypes[7].OneofWrappers = []any{} - file_tools_proto_msgTypes[8].OneofWrappers = []any{} - file_tools_proto_msgTypes[10].OneofWrappers = []any{} + file_tools_proto_msgTypes[7].OneofWrappers = []interface{}{} + file_tools_proto_msgTypes[8].OneofWrappers = []interface{}{} + file_tools_proto_msgTypes[10].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/tools/proto/tools.proto b/tools/proto/tools.proto index 935cb685b..3de978e2a 100644 --- a/tools/proto/tools.proto +++ b/tools/proto/tools.proto @@ -128,6 +128,7 @@ message ResponseFieldConfig { bool sortable = 9; // Based on @sortable() // Set if this field is a FK and link to a get/list action on the target model + // Or set to an appropriate list action if this field is a to-many field - for example, linking to listSaleItems(sale.id) optional ActionLink link = 10; // for file fields only, display images inline diff --git a/tools/testdata/blog/tools.json b/tools/testdata/blog/tools.json index 5edc32691..58368e69a 100644 --- a/tools/testdata/blog/tools.json +++ b/tools/testdata/blog/tools.json @@ -146,6 +146,20 @@ "data": [{ "key": "$.id", "path": { "path": "$.categoryId" } }] } }, + { + "fieldLocation": { "path": "$.comments" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Comments", + "displayOrder": 8, + "visible": true, + "link": { + "toolId": "listComments", + "data": [ + { "key": "$.where.parent.id.equals", "path": { "path": "$.id" } } + ] + } + }, { "fieldLocation": { "path": "$.id" }, "fieldType": "TYPE_ID", @@ -315,6 +329,20 @@ "data": [{ "key": "$.id", "path": { "path": "$.categoryId" } }] } }, + { + "fieldLocation": { "path": "$.comments" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Comments", + "displayOrder": 8, + "visible": true, + "link": { + "toolId": "listComments", + "data": [ + { "key": "$.where.parent.id.equals", "path": { "path": "$.id" } } + ] + } + }, { "fieldLocation": { "path": "$.id" }, "fieldType": "TYPE_ID", @@ -478,6 +506,20 @@ "data": [{ "key": "$.id", "path": { "path": "$.categoryId" } }] } }, + { + "fieldLocation": { "path": "$.comments" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Comments", + "displayOrder": 8, + "visible": true, + "link": { + "toolId": "listComments", + "data": [ + { "key": "$.where.parent.id.equals", "path": { "path": "$.id" } } + ] + } + }, { "fieldLocation": { "path": "$.id" }, "fieldType": "TYPE_ID", @@ -1010,6 +1052,23 @@ ] } }, + { + "fieldLocation": { "path": "$.results[*].comments" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Comments", + "displayOrder": 8, + "visible": true, + "link": { + "toolId": "listComments", + "data": [ + { + "key": "$.where.parent.id.equals", + "path": { "path": "$.results[*].id" } + } + ] + } + }, { "fieldLocation": { "path": "$.results[*].id" }, "fieldType": "TYPE_ID", @@ -1251,6 +1310,23 @@ ] } }, + { + "fieldLocation": { "path": "$.results[*].author.comments" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Comments", + "displayOrder": 8, + "visible": true, + "link": { + "toolId": "listComments", + "data": [ + { + "key": "$.where.parent.id.equals", + "path": { "path": "$.results[*].id" } + } + ] + } + }, { "fieldLocation": { "path": "$.results[*].author.id" }, "fieldType": "TYPE_ID", @@ -1361,6 +1437,23 @@ ] } }, + { + "fieldLocation": { "path": "$.results[*].category.comments" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Comments", + "displayOrder": 8, + "visible": true, + "link": { + "toolId": "listComments", + "data": [ + { + "key": "$.where.parent.id.equals", + "path": { "path": "$.results[*].id" } + } + ] + } + }, { "fieldLocation": { "path": "$.results[*].category.id" }, "fieldType": "TYPE_ID", @@ -1395,6 +1488,23 @@ ] } }, + { + "fieldLocation": { "path": "$.results[*].comments" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Comments", + "displayOrder": 8, + "visible": true, + "link": { + "toolId": "listComments", + "data": [ + { + "key": "$.where.parent.id.equals", + "path": { "path": "$.results[*].id" } + } + ] + } + }, { "fieldLocation": { "path": "$.results[*].id" }, "fieldType": "TYPE_ID", @@ -1664,6 +1774,20 @@ "data": [{ "key": "$.id", "path": { "path": "$.categoryId" } }] } }, + { + "fieldLocation": { "path": "$.comments" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Comments", + "displayOrder": 8, + "visible": true, + "link": { + "toolId": "listComments", + "data": [ + { "key": "$.where.parent.id.equals", "path": { "path": "$.id" } } + ] + } + }, { "fieldLocation": { "path": "$.id" }, "fieldType": "TYPE_ID", diff --git a/tools/testdata/nested_inputs/tools.json b/tools/testdata/nested_inputs/tools.json index fc728bfd6..5c4645afb 100644 --- a/tools/testdata/nested_inputs/tools.json +++ b/tools/testdata/nested_inputs/tools.json @@ -70,6 +70,14 @@ "displayOrder": 1, "visible": true }, + { + "fieldLocation": { "path": "$.items" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Items", + "displayOrder": 2, + "visible": true + }, { "fieldLocation": { "path": "$.id" }, "fieldType": "TYPE_ID", diff --git a/tools/testdata/one_to_many_links/schema.keel b/tools/testdata/one_to_many_links/schema.keel new file mode 100644 index 000000000..bf94b6a82 --- /dev/null +++ b/tools/testdata/one_to_many_links/schema.keel @@ -0,0 +1,22 @@ +model Order { + fields { + items LineItem[] + } + + actions { + get getOrder(id) + list listOrders(items.id) + } +} + +model LineItem { + fields { + order Order + product Text + } + + actions { + list listLineItems(order.id) + } +} + diff --git a/tools/testdata/one_to_many_links/tools.json b/tools/testdata/one_to_many_links/tools.json new file mode 100644 index 000000000..0c40bd7b0 --- /dev/null +++ b/tools/testdata/one_to_many_links/tools.json @@ -0,0 +1,490 @@ +{ + "tools": [ + { + "id": "getOrder", + "name": "Get order", + "actionName": "getOrder", + "apiNames": ["Api"], + "modelName": "Order", + "actionType": "ACTION_TYPE_GET", + "implementation": "ACTION_IMPLEMENTATION_AUTO", + "inputs": [ + { + "fieldLocation": { "path": "$.id" }, + "fieldType": "TYPE_ID", + "displayName": "Id", + "visible": true, + "lookupAction": { "toolId": "listOrders" } + } + ], + "response": [ + { + "fieldLocation": { "path": "$.items" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Items", + "visible": true, + "link": { + "toolId": "listLineItems", + "data": [ + { "key": "$.where.order.id.equals", "path": { "path": "$.id" } } + ] + } + }, + { + "fieldLocation": { "path": "$.id" }, + "fieldType": "TYPE_ID", + "displayName": "Id", + "displayOrder": 2, + "visible": true + }, + { + "fieldLocation": { "path": "$.createdAt" }, + "fieldType": "TYPE_DATETIME", + "displayName": "Created at", + "displayOrder": 3, + "visible": true + }, + { + "fieldLocation": { "path": "$.updatedAt" }, + "fieldType": "TYPE_DATETIME", + "displayName": "Updated at", + "displayOrder": 4, + "visible": true + } + ], + "title": { "template": "Order" }, + "entitySingle": "order", + "entityPlural": "orders", + "capabilities": { "comments": true, "audit": true }, + "embeddedActions": [ + { + "toolId": "listLineItems", + "data": [ + { "key": "$.where.order.id.equals", "path": { "path": "$.id" } } + ], + "title": { "template": "items" } + } + ] + }, + { + "id": "listLineItems", + "name": "List line items", + "actionName": "listLineItems", + "apiNames": ["Api"], + "modelName": "LineItem", + "actionType": "ACTION_TYPE_LIST", + "implementation": "ACTION_IMPLEMENTATION_AUTO", + "inputs": [ + { + "fieldLocation": { "path": "$.where" }, + "fieldType": "TYPE_MESSAGE", + "displayName": "Where", + "visible": true + }, + { + "fieldLocation": { "path": "$.where.order" }, + "fieldType": "TYPE_MESSAGE", + "displayName": "Order", + "visible": true + }, + { + "fieldLocation": { "path": "$.where.order.id" }, + "fieldType": "TYPE_MESSAGE", + "displayName": "Id", + "visible": true + }, + { + "fieldLocation": { "path": "$.where.order.id.equals" }, + "fieldType": "TYPE_ID", + "displayName": "Equals", + "visible": true + }, + { + "fieldLocation": { "path": "$.where.order.id.oneOf" }, + "fieldType": "TYPE_ID", + "repeated": true, + "displayName": "One of", + "displayOrder": 1, + "visible": true + }, + { + "fieldLocation": { "path": "$.where.order.id.notEquals" }, + "fieldType": "TYPE_ID", + "displayName": "Not equals", + "displayOrder": 2, + "visible": true + }, + { + "fieldLocation": { "path": "$.first" }, + "fieldType": "TYPE_INT", + "displayName": "First", + "displayOrder": 1, + "visible": true + }, + { + "fieldLocation": { "path": "$.after" }, + "fieldType": "TYPE_STRING", + "displayName": "After", + "displayOrder": 2, + "visible": true + }, + { + "fieldLocation": { "path": "$.last" }, + "fieldType": "TYPE_INT", + "displayName": "Last", + "displayOrder": 3, + "visible": true + }, + { + "fieldLocation": { "path": "$.before" }, + "fieldType": "TYPE_STRING", + "displayName": "Before", + "displayOrder": 4, + "visible": true + } + ], + "response": [ + { + "fieldLocation": { "path": "$.pageInfo" }, + "fieldType": "TYPE_OBJECT", + "displayName": "PageInfo" + }, + { + "fieldLocation": { "path": "$.pageInfo.count" }, + "fieldType": "TYPE_INT", + "displayName": "Count" + }, + { + "fieldLocation": { "path": "$.pageInfo.totalCount" }, + "fieldType": "TYPE_INT", + "displayName": "Total count" + }, + { + "fieldLocation": { "path": "$.pageInfo.hasNextPage" }, + "fieldType": "TYPE_BOOL", + "displayName": "Has next page" + }, + { + "fieldLocation": { "path": "$.pageInfo.startCursor" }, + "fieldType": "TYPE_STRING", + "displayName": "Start cursor" + }, + { + "fieldLocation": { "path": "$.pageInfo.endCursor" }, + "fieldType": "TYPE_STRING", + "displayName": "End cursor" + }, + { + "fieldLocation": { "path": "$.results" }, + "fieldType": "TYPE_OBJECT", + "repeated": true, + "displayName": "Results", + "visible": true + }, + { + "fieldLocation": { "path": "$.results[*].orderId" }, + "fieldType": "TYPE_ID", + "displayName": "Order", + "visible": true, + "link": { + "toolId": "getOrder", + "data": [ + { "key": "$.id", "path": { "path": "$.results[*].orderId" } } + ] + } + }, + { + "fieldLocation": { "path": "$.results[*].product" }, + "fieldType": "TYPE_STRING", + "displayName": "Product", + "displayOrder": 1, + "visible": true + }, + { + "fieldLocation": { "path": "$.results[*].id" }, + "fieldType": "TYPE_ID", + "displayName": "Id", + "displayOrder": 4, + "visible": true + }, + { + "fieldLocation": { "path": "$.results[*].createdAt" }, + "fieldType": "TYPE_DATETIME", + "displayName": "Created at", + "displayOrder": 5, + "visible": true + }, + { + "fieldLocation": { "path": "$.results[*].updatedAt" }, + "fieldType": "TYPE_DATETIME", + "displayName": "Updated at", + "displayOrder": 6, + "visible": true + } + ], + "title": { "template": "Line items" }, + "entitySingle": "line item", + "entityPlural": "line items", + "capabilities": {}, + "pagination": { + "start": { + "requestInput": "after", + "responseField": { "path": "$.pageInfo.startCursor" } + }, + "end": { + "requestInput": "before", + "responseField": { "path": "$.pageInfo.endCursor" } + }, + "pageSize": { + "requestInput": "first", + "responseField": { "path": "$.pageInfo.count" }, + "defaultValue": 50 + }, + "nextPage": { "path": "$.pageInfo.hasNextPage" }, + "totalCount": { "path": "$.pageInfo.totalCount" } + } + }, + { + "id": "listOrders", + "name": "List orders", + "actionName": "listOrders", + "apiNames": ["Api"], + "modelName": "Order", + "actionType": "ACTION_TYPE_LIST", + "implementation": "ACTION_IMPLEMENTATION_AUTO", + "inputs": [ + { + "fieldLocation": { "path": "$.where" }, + "fieldType": "TYPE_MESSAGE", + "displayName": "Where", + "visible": true + }, + { + "fieldLocation": { "path": "$.where.items" }, + "fieldType": "TYPE_MESSAGE", + "displayName": "Items", + "visible": true + }, + { + "fieldLocation": { "path": "$.where.items.id" }, + "fieldType": "TYPE_MESSAGE", + "displayName": "Id", + "visible": true + }, + { + "fieldLocation": { "path": "$.where.items.id.equals" }, + "fieldType": "TYPE_ID", + "displayName": "Equals", + "visible": true + }, + { + "fieldLocation": { "path": "$.where.items.id.oneOf" }, + "fieldType": "TYPE_ID", + "repeated": true, + "displayName": "One of", + "displayOrder": 1, + "visible": true + }, + { + "fieldLocation": { "path": "$.where.items.id.notEquals" }, + "fieldType": "TYPE_ID", + "displayName": "Not equals", + "displayOrder": 2, + "visible": true + }, + { + "fieldLocation": { "path": "$.first" }, + "fieldType": "TYPE_INT", + "displayName": "First", + "displayOrder": 1, + "visible": true + }, + { + "fieldLocation": { "path": "$.after" }, + "fieldType": "TYPE_STRING", + "displayName": "After", + "displayOrder": 2, + "visible": true + }, + { + "fieldLocation": { "path": "$.last" }, + "fieldType": "TYPE_INT", + "displayName": "Last", + "displayOrder": 3, + "visible": true + }, + { + "fieldLocation": { "path": "$.before" }, + "fieldType": "TYPE_STRING", + "displayName": "Before", + "displayOrder": 4, + "visible": true + } + ], + "response": [ + { + "fieldLocation": { "path": "$.pageInfo" }, + "fieldType": "TYPE_OBJECT", + "displayName": "PageInfo" + }, + { + "fieldLocation": { "path": "$.pageInfo.count" }, + "fieldType": "TYPE_INT", + "displayName": "Count" + }, + { + "fieldLocation": { "path": "$.pageInfo.totalCount" }, + "fieldType": "TYPE_INT", + "displayName": "Total count" + }, + { + "fieldLocation": { "path": "$.pageInfo.hasNextPage" }, + "fieldType": "TYPE_BOOL", + "displayName": "Has next page" + }, + { + "fieldLocation": { "path": "$.pageInfo.startCursor" }, + "fieldType": "TYPE_STRING", + "displayName": "Start cursor" + }, + { + "fieldLocation": { "path": "$.pageInfo.endCursor" }, + "fieldType": "TYPE_STRING", + "displayName": "End cursor" + }, + { + "fieldLocation": { "path": "$.results" }, + "fieldType": "TYPE_OBJECT", + "repeated": true, + "displayName": "Results", + "visible": true + }, + { + "fieldLocation": { "path": "$.results[*].items" }, + "fieldType": "TYPE_MODEL", + "repeated": true, + "displayName": "Items", + "visible": true, + "link": { + "toolId": "listLineItems", + "data": [ + { + "key": "$.where.order.id.equals", + "path": { "path": "$.results[*].id" } + } + ] + } + }, + { + "fieldLocation": { "path": "$.results[*].id" }, + "fieldType": "TYPE_ID", + "displayName": "Id", + "displayOrder": 2, + "visible": true + }, + { + "fieldLocation": { "path": "$.results[*].createdAt" }, + "fieldType": "TYPE_DATETIME", + "displayName": "Created at", + "displayOrder": 3, + "visible": true + }, + { + "fieldLocation": { "path": "$.results[*].updatedAt" }, + "fieldType": "TYPE_DATETIME", + "displayName": "Updated at", + "displayOrder": 4, + "visible": true + } + ], + "title": { "template": "Orders" }, + "entitySingle": "order", + "entityPlural": "orders", + "capabilities": {}, + "pagination": { + "start": { + "requestInput": "after", + "responseField": { "path": "$.pageInfo.startCursor" } + }, + "end": { + "requestInput": "before", + "responseField": { "path": "$.pageInfo.endCursor" } + }, + "pageSize": { + "requestInput": "first", + "responseField": { "path": "$.pageInfo.count" }, + "defaultValue": 50 + }, + "nextPage": { "path": "$.pageInfo.hasNextPage" }, + "totalCount": { "path": "$.pageInfo.totalCount" } + }, + "entryActivityActions": [ + { + "toolId": "getOrder", + "data": [{ "key": "$.id", "path": { "path": "$.results[*].id" } }] + } + ], + "getEntryAction": { + "toolId": "getOrder", + "data": [{ "key": "$.id", "path": { "path": "$.results[*].id" } }] + } + }, + { + "id": "requestPasswordReset", + "name": "Request password reset", + "actionName": "requestPasswordReset", + "apiNames": ["Api"], + "modelName": "Identity", + "actionType": "ACTION_TYPE_WRITE", + "implementation": "ACTION_IMPLEMENTATION_RUNTIME", + "inputs": [ + { + "fieldLocation": { "path": "$.email" }, + "fieldType": "TYPE_STRING", + "displayName": "Email", + "visible": true + }, + { + "fieldLocation": { "path": "$.redirectUrl" }, + "fieldType": "TYPE_STRING", + "displayName": "Redirect url", + "displayOrder": 1, + "visible": true + } + ], + "title": { "template": "Request password reset" }, + "entitySingle": "identity", + "entityPlural": "identities", + "capabilities": {} + }, + { + "id": "resetPassword", + "name": "Reset password", + "actionName": "resetPassword", + "apiNames": ["Api"], + "modelName": "Identity", + "actionType": "ACTION_TYPE_WRITE", + "implementation": "ACTION_IMPLEMENTATION_RUNTIME", + "inputs": [ + { + "fieldLocation": { "path": "$.token" }, + "fieldType": "TYPE_STRING", + "displayName": "Token", + "visible": true + }, + { + "fieldLocation": { "path": "$.password" }, + "fieldType": "TYPE_STRING", + "displayName": "Password", + "displayOrder": 1, + "visible": true + } + ], + "title": { "template": "Reset password" }, + "entitySingle": "identity", + "entityPlural": "identities", + "capabilities": {} + } + ] +}