mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
actions: fix behaviour when using remote relationship in mutation action's relationship (#4982)
* resolve the remote server when a remote join query used in action mutation
This commit is contained in:
parent
e4e4856085
commit
d6de3592ed
@ -181,7 +181,7 @@ mutFldToTx fld = do
|
||||
validateHdrs userInfo (_docHeaders ctx)
|
||||
noRespHeaders $ RM.convertDeleteByPk ctx rjCtx fld
|
||||
MCAction ctx ->
|
||||
RA.resolveActionMutation fld ctx (_uiSession userInfo)
|
||||
RA.resolveActionMutation fld ctx userInfo
|
||||
|
||||
getOpCtx
|
||||
:: ( MonadReusability m
|
||||
|
@ -34,9 +34,9 @@ import qualified Language.GraphQL.Draft.Syntax as G
|
||||
import qualified Network.HTTP.Client as HTTP
|
||||
import qualified Network.HTTP.Types as HTTP
|
||||
import qualified Network.Wreq as Wreq
|
||||
|
||||
import qualified Hasura.GraphQL.Resolve.Select as GRS
|
||||
import qualified Hasura.RQL.DML.Select as RS
|
||||
import qualified Hasura.RQL.DML.RemoteJoin as RJ
|
||||
|
||||
import Hasura.EncJSON
|
||||
import Hasura.GraphQL.Resolve.Context
|
||||
@ -128,14 +128,14 @@ resolveActionMutation
|
||||
)
|
||||
=> Field
|
||||
-> ActionMutationExecutionContext
|
||||
-> SessionVariables
|
||||
-> UserInfo
|
||||
-> m (RespTx, HTTP.ResponseHeaders)
|
||||
resolveActionMutation field executionContext sessionVariables =
|
||||
resolveActionMutation field executionContext userInfo =
|
||||
case executionContext of
|
||||
ActionMutationSyncWebhook executionContextSync ->
|
||||
resolveActionMutationSync field executionContextSync sessionVariables
|
||||
resolveActionMutationSync field executionContextSync userInfo
|
||||
ActionMutationAsync ->
|
||||
(,[]) <$> resolveActionMutationAsync field sessionVariables
|
||||
(,[]) <$> resolveActionMutationAsync field userInfo
|
||||
|
||||
-- | Synchronously execute webhook handler and resolve response to action "output"
|
||||
resolveActionMutationSync
|
||||
@ -152,11 +152,12 @@ resolveActionMutationSync
|
||||
)
|
||||
=> Field
|
||||
-> ActionExecutionContext
|
||||
-> SessionVariables
|
||||
-> UserInfo
|
||||
-> m (RespTx, HTTP.ResponseHeaders)
|
||||
resolveActionMutationSync field executionContext sessionVariables = do
|
||||
resolveActionMutationSync field executionContext userInfo = do
|
||||
let inputArgs = J.toJSON $ fmap annInpValueToJson $ _fArguments field
|
||||
actionContext = ActionContext actionName
|
||||
sessionVariables = _uiSession userInfo
|
||||
handlerPayload = ActionWebhookPayload actionContext sessionVariables inputArgs
|
||||
manager <- asks getter
|
||||
reqHeaders <- asks getter
|
||||
@ -168,8 +169,16 @@ resolveActionMutationSync field executionContext sessionVariables = do
|
||||
processOutputSelectionSet webhookResponseExpression outputType definitionList
|
||||
(_fType field) $ _fSelSet field
|
||||
astResolved <- RS.traverseAnnSimpleSel resolveValTxt selectAstUnresolved
|
||||
let (astResolvedWithoutRemoteJoins,maybeRemoteJoins) = RJ.getRemoteJoins astResolved
|
||||
let jsonAggType = mkJsonAggSelect outputType
|
||||
return $ (,respHeaders) $ asSingleRowJsonResp (Q.fromBuilder $ toSQL $ RS.mkSQLSelect jsonAggType astResolved) []
|
||||
return $ (,respHeaders) $
|
||||
case maybeRemoteJoins of
|
||||
Just remoteJoins ->
|
||||
let query = Q.fromBuilder $ toSQL $ RS.mkSQLSelect jsonAggType astResolvedWithoutRemoteJoins
|
||||
in RJ.executeQueryWithRemoteJoins manager reqHeaders userInfo query [] remoteJoins
|
||||
Nothing ->
|
||||
asSingleRowJsonResp (Q.fromBuilder $ toSQL $ RS.mkSQLSelect jsonAggType astResolved) []
|
||||
|
||||
where
|
||||
ActionExecutionContext actionName outputType outputFields definitionList resolvedWebhook confHeaders
|
||||
forwardClientHeaders = executionContext
|
||||
@ -243,9 +252,10 @@ resolveActionMutationAsync
|
||||
, Has [HTTP.Header] r
|
||||
)
|
||||
=> Field
|
||||
-> SessionVariables
|
||||
-> UserInfo
|
||||
-> m RespTx
|
||||
resolveActionMutationAsync field sessionVariables = do
|
||||
resolveActionMutationAsync field userInfo = do
|
||||
let sessionVariables = _uiSession userInfo
|
||||
reqHeaders <- asks getter
|
||||
let inputArgs = J.toJSON $ fmap annInpValueToJson $ _fArguments field
|
||||
pure $ do
|
||||
|
@ -0,0 +1,49 @@
|
||||
- description: Create a new remote relationship
|
||||
url: /v1/query
|
||||
status: 200
|
||||
response:
|
||||
message: success
|
||||
query:
|
||||
type: create_remote_relationship
|
||||
args:
|
||||
name: messageBasic
|
||||
table: user
|
||||
hasura_fields:
|
||||
- id
|
||||
remote_schema: actions-remote-join-schema
|
||||
remote_field:
|
||||
message:
|
||||
arguments:
|
||||
id: "$id"
|
||||
|
||||
|
||||
- description: Run create_users action
|
||||
url: /v1/graphql
|
||||
status: 200
|
||||
query:
|
||||
query: |
|
||||
mutation {
|
||||
create_user(email: "clarke@gmail.com", name: "Clarke"){
|
||||
id
|
||||
user {
|
||||
name
|
||||
email
|
||||
is_admin
|
||||
messageBasic {
|
||||
name
|
||||
msg
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response:
|
||||
data:
|
||||
create_user:
|
||||
id: 1
|
||||
user:
|
||||
name: Clarke
|
||||
email: clarke@gmail.com
|
||||
is_admin: false
|
||||
messageBasic:
|
||||
name: Clarke
|
||||
msg: Welcome to the team, Clarke
|
@ -0,0 +1,67 @@
|
||||
type: bulk
|
||||
args:
|
||||
- type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
CREATE TABLE "user"(
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
is_admin BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
- type: track_table
|
||||
args:
|
||||
name: user
|
||||
schema: public
|
||||
|
||||
- type: set_custom_types
|
||||
args:
|
||||
input_objects:
|
||||
- name: UserInput
|
||||
fields:
|
||||
- name: name
|
||||
type: String!
|
||||
- name: email
|
||||
type: String!
|
||||
|
||||
- name: InObject
|
||||
fields:
|
||||
- name: id
|
||||
type: ID
|
||||
- name: name
|
||||
type: String
|
||||
- name: age
|
||||
type: Int
|
||||
|
||||
objects:
|
||||
- name: UserId
|
||||
fields:
|
||||
- name: id
|
||||
type: Int!
|
||||
relationships:
|
||||
- name: user
|
||||
type: object
|
||||
remote_table: user
|
||||
field_mapping:
|
||||
id: id
|
||||
|
||||
- type: create_action
|
||||
args:
|
||||
name: create_user
|
||||
definition:
|
||||
kind: synchronous
|
||||
arguments:
|
||||
- name: email
|
||||
type: String!
|
||||
- name: name
|
||||
type: String!
|
||||
output_type: UserId
|
||||
handler: http://127.0.0.1:5593/create-user
|
||||
|
||||
- type: add_remote_schema
|
||||
args:
|
||||
name: actions-remote-join-schema
|
||||
definition:
|
||||
url: http://localhost:4001
|
||||
forward_client_headers: false
|
@ -0,0 +1,20 @@
|
||||
type: bulk
|
||||
args:
|
||||
- type: drop_action
|
||||
args:
|
||||
name: create_user
|
||||
clear_data: true
|
||||
|
||||
- type: set_custom_types
|
||||
args: {}
|
||||
|
||||
- type: run_sql
|
||||
args:
|
||||
cascade: true
|
||||
sql: |
|
||||
DROP TABLE "user";
|
||||
|
||||
# also drops remote relationship as direct dep
|
||||
- type: remove_remote_schema
|
||||
args:
|
||||
name: actions-remote-join-schema
|
@ -0,0 +1,7 @@
|
||||
type: bulk
|
||||
args:
|
||||
- type: run_sql
|
||||
args:
|
||||
sql: |
|
||||
DELETE FROM "user";
|
||||
SELECT setval('user_id_seq', 1, FALSE);
|
@ -0,0 +1,79 @@
|
||||
const { ApolloServer, ApolloError } = require('apollo-server');
|
||||
const gql = require('graphql-tag');
|
||||
const { print } = require('graphql');
|
||||
|
||||
|
||||
const allMessages = [
|
||||
{ id: 1, name: "Clarke", msg: "Welcome to the team, Clarke"},
|
||||
{ id: 2, name: "Alice", msg: "Welcome to the team, Alice"},
|
||||
];
|
||||
|
||||
const typeDefs = gql`
|
||||
|
||||
interface Communication {
|
||||
id: Int!
|
||||
msg: String!
|
||||
}
|
||||
|
||||
type Message implements Communication {
|
||||
id: Int!
|
||||
name: String!
|
||||
msg: String!
|
||||
errorMsg: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
hello: String
|
||||
message(id: Int!) : Message
|
||||
}
|
||||
`;
|
||||
|
||||
const resolvers = {
|
||||
|
||||
Message: {
|
||||
errorMsg : () => {
|
||||
throw new ApolloError("intentional-error", "you asked for it");
|
||||
}
|
||||
},
|
||||
|
||||
Query: {
|
||||
hello: () => "world",
|
||||
message: (_, { id }) => {
|
||||
return allMessages.find(m => m.id == id);
|
||||
}
|
||||
},
|
||||
Communication: {
|
||||
__resolveType(communication, context, info){
|
||||
if(communication.name) {
|
||||
return "Message";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BasicLogging {
|
||||
requestDidStart({queryString, parsedQuery, variables}) {
|
||||
const query = queryString || print(parsedQuery);
|
||||
console.log(query);
|
||||
console.log(variables);
|
||||
}
|
||||
|
||||
willSendResponse({graphqlResponse}) {
|
||||
console.log(JSON.stringify(graphqlResponse, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
const schema = new ApolloServer(
|
||||
{ typeDefs,
|
||||
resolvers,
|
||||
extensions: [() => new BasicLogging()],
|
||||
formatError: (err) => {
|
||||
// Stack traces make expected test output brittle and noisey:
|
||||
delete err.extensions;
|
||||
return err;
|
||||
} });
|
||||
|
||||
schema.listen({ port: process.env.PORT || 4001 }).then(({ url }) => {
|
||||
console.log(`schema ready at ${url}`);
|
||||
});
|
20
server/tests-py/remote_server.py
Normal file
20
server/tests-py/remote_server.py
Normal file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
class NodeGraphQL():
|
||||
|
||||
def __init__(self, cmd):
|
||||
self.cmd = cmd
|
||||
self.proc = None
|
||||
|
||||
def start(self):
|
||||
proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
time.sleep(1)
|
||||
proc.poll()
|
||||
if proc.returncode is not None:
|
||||
raise Exception("It seems our node graphql test server stopped unexpectedly:\n" + proc.stdout.read().decode('utf-8'))
|
||||
self.proc = proc
|
||||
|
||||
def stop(self):
|
||||
self.proc.terminate()
|
@ -2,19 +2,36 @@
|
||||
|
||||
import pytest
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
from validate import check_query_f, check_query, get_conf_f
|
||||
from remote_server import NodeGraphQL
|
||||
|
||||
"""
|
||||
TODO:- Test Actions metadata
|
||||
"""
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def graphql_service():
|
||||
svc = NodeGraphQL(["node", "remote_schemas/nodejs/actions_remote_join_schema.js"])
|
||||
svc.start()
|
||||
yield svc
|
||||
svc.stop()
|
||||
|
||||
|
||||
use_action_fixtures = pytest.mark.usefixtures(
|
||||
"actions_fixture",
|
||||
'per_class_db_schema_for_mutation_tests',
|
||||
'per_method_db_data_for_mutation_tests'
|
||||
)
|
||||
|
||||
use_action_fixtures_with_remote_joins = pytest.mark.usefixtures(
|
||||
"graphql_service",
|
||||
"actions_fixture",
|
||||
"per_class_db_schema_for_mutation_tests",
|
||||
"per_method_db_data_for_mutation_tests"
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("transport", ['http', 'websocket'])
|
||||
@use_action_fixtures
|
||||
class TestActionsSyncWebsocket:
|
||||
@ -64,6 +81,16 @@ class TestActionsSync:
|
||||
def test_mirror_action_success(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/mirror_action_success.yaml')
|
||||
|
||||
@use_action_fixtures_with_remote_joins
|
||||
class TestActionsSyncWithRemoteJoins:
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return 'queries/actions/sync/remote_joins'
|
||||
|
||||
def test_action_with_remote_joins(self,hge_ctx):
|
||||
check_query_f(hge_ctx,self.dir() + '/action_with_remote_joins.yaml')
|
||||
|
||||
# Check query with admin secret tokens
|
||||
def check_query_secret(hge_ctx, f):
|
||||
conf = get_conf_f(f)
|
||||
|
@ -5,23 +5,7 @@ import subprocess
|
||||
import time
|
||||
|
||||
from validate import check_query_f, check_query
|
||||
|
||||
class NodeGraphQL():
|
||||
|
||||
def __init__(self, cmd):
|
||||
self.cmd = cmd
|
||||
self.proc = None
|
||||
|
||||
def start(self):
|
||||
proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
time.sleep(1)
|
||||
proc.poll()
|
||||
if proc.returncode is not None:
|
||||
raise Exception("It seems our node graphql test server stopped unexpectedly:\n" + proc.stdout.read().decode('utf-8'))
|
||||
self.proc = proc
|
||||
|
||||
def stop(self):
|
||||
self.proc.terminate()
|
||||
from remote_server import NodeGraphQL
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def graphql_service():
|
||||
|
Loading…
Reference in New Issue
Block a user