Merge pull request #6631 from tchak/fix-graphql-log-parse
fix(graphql): fix and improve query parsing for logs
This commit is contained in:
commit
bf66346f6a
3 changed files with 124 additions and 34 deletions
|
@ -1,4 +1,6 @@
|
||||||
class API::V2::GraphqlController < API::V2::BaseController
|
class API::V2::GraphqlController < API::V2::BaseController
|
||||||
|
include GraphqlOperationLogConcern
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
variables = ensure_hash(params[:variables])
|
variables = ensure_hash(params[:variables])
|
||||||
|
|
||||||
|
@ -24,43 +26,10 @@ class API::V2::GraphqlController < API::V2::BaseController
|
||||||
super
|
super
|
||||||
|
|
||||||
payload.merge!({
|
payload.merge!({
|
||||||
graphql_operation: operation_log(params[:query], params[:operationName], params[:variables])
|
graphql_operation: operation_log(params[:query], params[:operationName], params[:variables]&.to_unsafe_h)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def operation_log(query, operation_name, variables)
|
|
||||||
return "NoQuery" if query.nil?
|
|
||||||
|
|
||||||
operation = GraphQL.parse(query).children.find do |node|
|
|
||||||
if node.is_a?(GraphQL::Language::Nodes::OperationDefinition)
|
|
||||||
node.name == operation_name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return "InvalidQuery" if operation.nil?
|
|
||||||
return "IntrospectionQuery" if operation.name == "IntrospectionQuery"
|
|
||||||
|
|
||||||
message = operation.operation_type
|
|
||||||
if operation.name
|
|
||||||
message += ": #{operation.name} { "
|
|
||||||
end
|
|
||||||
message += operation.selections.map(&:name).join(', ')
|
|
||||||
message += " }"
|
|
||||||
if variables.present?
|
|
||||||
message += " "
|
|
||||||
message += variables.to_unsafe_h.flat_map do |(name, value)|
|
|
||||||
if name == "input"
|
|
||||||
value.map do |(name, value)|
|
|
||||||
"#{name}: \"#{value.to_s.truncate(10)}\""
|
|
||||||
end
|
|
||||||
else
|
|
||||||
"#{name}: \"#{value.to_s.truncate(10)}\""
|
|
||||||
end
|
|
||||||
end.join(', ')
|
|
||||||
end
|
|
||||||
message
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_action(*args)
|
def process_action(*args)
|
||||||
super
|
super
|
||||||
rescue ActionDispatch::Http::Parameters::ParseError => exception
|
rescue ActionDispatch::Http::Parameters::ParseError => exception
|
||||||
|
|
62
app/controllers/concerns/graphql_operation_log_concern.rb
Normal file
62
app/controllers/concerns/graphql_operation_log_concern.rb
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
module GraphqlOperationLogConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
# This method parses GraphQL query and creates a short description of the query. It is useful for logging.
|
||||||
|
def operation_log(query, operation_name, variables)
|
||||||
|
return "NoQuery" if query.nil?
|
||||||
|
|
||||||
|
operation = parse_graphql_query(query, operation_name)
|
||||||
|
|
||||||
|
return "InvalidQuery" if operation.nil?
|
||||||
|
return "IntrospectionQuery" if operation.name == "IntrospectionQuery"
|
||||||
|
|
||||||
|
message = "#{operation.operation_type}: "
|
||||||
|
message += if operation.name.present?
|
||||||
|
"#{operation.name} { "
|
||||||
|
else
|
||||||
|
"{ "
|
||||||
|
end
|
||||||
|
message += operation.selections.map(&:name).join(', ')
|
||||||
|
message += " } "
|
||||||
|
message += if variables.present?
|
||||||
|
variables.flat_map do |(name, value)|
|
||||||
|
format_graphql_variable(name, value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
operation.selections.flat_map(&:arguments).flat_map do |argument|
|
||||||
|
format_graphql_variable(argument.name, argument.value)
|
||||||
|
end
|
||||||
|
end.join(', ')
|
||||||
|
|
||||||
|
message.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse_graphql_query(query, operation_name)
|
||||||
|
operations = GraphQL.parse(query).children.filter do |node|
|
||||||
|
node.is_a?(GraphQL::Language::Nodes::OperationDefinition)
|
||||||
|
end
|
||||||
|
if operations.size == 1
|
||||||
|
operations.first
|
||||||
|
else
|
||||||
|
operations.find { |node| node.name == operation_name }
|
||||||
|
end
|
||||||
|
rescue
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_graphql_variable(name, value)
|
||||||
|
if value.is_a?(Hash)
|
||||||
|
value.map do |(name, value)|
|
||||||
|
format_graphql_variable(name, value)
|
||||||
|
end
|
||||||
|
elsif value.is_a?(GraphQL::Language::Nodes::InputObject)
|
||||||
|
value.arguments.map do |argument|
|
||||||
|
format_graphql_variable(argument.name, argument.value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
"#{name}: \"#{value.to_s.truncate(10)}\""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,59 @@
|
||||||
|
RSpec.describe GraphqlOperationLogConcern, type: :controller do
|
||||||
|
class TestController < ActionController::Base
|
||||||
|
include GraphqlOperationLogConcern
|
||||||
|
end
|
||||||
|
|
||||||
|
controller TestController do
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#operation_log' do
|
||||||
|
let(:query) { nil }
|
||||||
|
let(:variables) { nil }
|
||||||
|
let(:operation_name) { nil }
|
||||||
|
|
||||||
|
subject { controller.operation_log(query, operation_name, variables) }
|
||||||
|
|
||||||
|
context 'with no query' do
|
||||||
|
it { expect(subject).to eq('NoQuery') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with invalid query' do
|
||||||
|
let(:query) { 'query { demarche {} }' }
|
||||||
|
|
||||||
|
it { expect(subject).to eq('InvalidQuery') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with two queries' do
|
||||||
|
let(:query) { 'query demarche { demarche } query dossier { dossier }' }
|
||||||
|
let(:operation_name) { 'dossier' }
|
||||||
|
|
||||||
|
it { expect(subject).to eq('query: dossier { dossier }') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with arguments' do
|
||||||
|
let(:query) { 'query demarche { demarche(number: 123) { id } }' }
|
||||||
|
|
||||||
|
it { expect(subject).to eq('query: demarche { demarche } number: "123"') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with variables' do
|
||||||
|
let(:query) { 'query { demarche(number: 123) { id } }' }
|
||||||
|
let(:variables) { { number: 124 } }
|
||||||
|
|
||||||
|
it { expect(subject).to eq('query: { demarche } number: "124"') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with mutation and arguments' do
|
||||||
|
let(:query) { 'mutation { passerDossierEnInstruction(input: { number: 123 }) { id } }' }
|
||||||
|
|
||||||
|
it { expect(subject).to eq('mutation: { passerDossierEnInstruction } number: "123"') }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with mutation and variables' do
|
||||||
|
let(:query) { 'mutation { passerDossierEnInstruction(input: { number: 123 }) { id } }' }
|
||||||
|
let(:variables) { { input: { number: 124 } } }
|
||||||
|
|
||||||
|
it { expect(subject).to eq('mutation: { passerDossierEnInstruction } number: "124"') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Reference in a new issue