Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ruby/rails8-sidekiq/app/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ gem "turbo-rails"
gem "stimulus-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
gem "graphql"

# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"
Expand Down
8 changes: 7 additions & 1 deletion ruby/rails8-sidekiq/app/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: /integration
specs:
appsignal (4.7.5)
appsignal (4.8.4)
logger
rack (>= 2.0.0)

Expand Down Expand Up @@ -116,11 +116,16 @@ GEM
erubi (1.13.1)
et-orbi (1.2.11)
tzinfo
fiber-storage (1.0.1)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
graphql (2.6.1)
base64
fiber-storage
logger
i18n (1.14.7)
concurrent-ruby (~> 1.0)
importmap-rails (2.0.3)
Expand Down Expand Up @@ -394,6 +399,7 @@ DEPENDENCIES
brakeman
capybara
debug
graphql
importmap-rails
jbuilder
kamal
Expand Down
142 changes: 142 additions & 0 deletions ruby/rails8-sidekiq/app/app/controllers/examples_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
require "net/http"
require "json"

class ReportError < StandardError; end

class ExamplesController < ApplicationController
Expand Down Expand Up @@ -48,6 +51,115 @@ def error_reporter
render :html => "An error has been reported through the Rails error reporter"
end

def graphql_demo
query = <<~GRAPHQL
query CustomerOverview($customerId: ID!, $ordersLimit: Int!, $status: OrderStatus) {
customer(id: $customerId) {
id
fullName
tier
loyaltyPoints
primaryAddress {
city
country
}
orders(limit: $ordersLimit, status: $status) {
id
orderNumber
status
total {
amount
currency
}
lineItems {
sku
quantity
totalPrice {
amount
currency
}
product {
id
name
category
supplier {
name
region
}
}
}
shipment {
carrier
trackingNumber
estimatedDeliveryOn
}
}
}
}

query WarehouseOperations($warehouseId: ID!, $productQuery: String!, $limit: Int!) {
warehouse(id: $warehouseId) {
id
code
name
address {
city
country
}
inventorySnapshots {
productId
available
reserved
reorderThreshold
onHand
}
}
searchProducts(query: $productQuery, limit: $limit) {
id
name
category
unitPrice {
amount
currency
}
supplier {
id
name
}
}
}
GRAPHQL

batch_payload = [
{
query: query,
operationName: "CustomerOverview",
variables: {
customerId: "cust-001",
ordersLimit: 2,
status: "SHIPPED"
}
},
{
query: query,
operationName: "WarehouseOperations",
variables: {
warehouseId: "wh-east-1",
productQuery: "pro",
limit: 3
}
}
]

response = post_graphql_request(batch_payload)

render json: {
description: "Batch request with multiple operationName values in one HTTP request",
request_payload: batch_payload,
graphql_response: response[:body]
}, status: response[:status]
end

def queries
user_count = User.count
User.destroy_all if user_count > 10_000
Expand All @@ -57,6 +169,36 @@ def queries
end
@users = User.all
end

private

def post_graphql_request(payload)
uri = URI.parse("#{request.base_url}/graphql")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == "https"

request = Net::HTTP::Post.new(uri.request_uri)
request["Content-Type"] = "application/json"
request.body = payload.to_json

response = http.request(request)
{
status: response.code.to_i,
body: JSON.parse(response.body)
}
rescue JSON::ParserError
{
status: :bad_gateway,
body: {
errors: [
{
message: "GraphQL endpoint returned invalid JSON",
raw_body: response&.body
}
]
}
}
end
end

class CauseCauser
Expand Down
72 changes: 72 additions & 0 deletions ruby/rails8-sidekiq/app/app/controllers/graphql_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
class GraphqlController < ApplicationController
skip_forgery_protection

def execute
if batch_request?
render json: ExampleAppSchema.multiplex(multiplex_payload)
else
render json: ExampleAppSchema.execute(
params[:query],
variables: prepare_variables(params[:variables]),
operation_name: params[:operationName],
context: graphql_context
)
end
rescue StandardError => e
render json: { errors: [{ message: e.message }] }, status: :unprocessable_entity
ensure
transaction_name = params[:operationName] || "Unknown"
puts "!!! controller transaction name: #{transaction_name}"
Appsignal::Transaction.current.set_action(transaction_name)
end

private

def batch_request?
params[:_json].is_a?(Array)
end

def multiplex_payload
params[:_json].map do |entry|
payload = normalize_entry(entry)
puts "!!! controller multiplex action name: #{payload["operationName"] || payload[:operationName]}"
{
query: payload["query"] || payload[:query],
variables: prepare_variables(payload["variables"] || payload[:variables]),
operation_name: payload["operationName"] || payload[:operationName],
context: graphql_context
}
end
end

def normalize_entry(entry)
if entry.respond_to?(:to_unsafe_h)
entry.to_unsafe_h
else
entry.to_h
end
end

def graphql_context
{
request_id: request.request_id,
remote_ip: request.remote_ip,
:set_appsignal_action_name => true
}
end

def prepare_variables(variables_param)
case variables_param
when String
variables_param.present? ? prepare_variables(JSON.parse(variables_param)) : {}
when ActionController::Parameters
variables_param.to_unsafe_h
when Hash
variables_param
when nil
{}
else
raise ArgumentError, "Unexpected variables format: #{variables_param.class}"
end
end
end
4 changes: 4 additions & 0 deletions ruby/rails8-sidekiq/app/app/graphql/example_app_schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class ExampleAppSchema < GraphQL::Schema
trace_with(GraphQL::Tracing::AppsignalTrace)
query Types::QueryType
end
Loading
Loading