Skip to content

Commit

Permalink
Add tests for Dashboard controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
rmosolgo committed Feb 24, 2025
1 parent 735eed9 commit 86b526e
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 14 deletions.
14 changes: 12 additions & 2 deletions lib/graphql/dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Dashboard < Rails::Engine
root "landings#show"
resources :statics, only: :show, constraints: { id: /[0-9A-Za-z\-.]+/ }
resources :traces, only: [:index, :show, :destroy]
delete "/traces/delete_all", to: "traces#delete_all", as: :traces_delete_all
end

class ApplicationController < ActionController::Base
Expand Down Expand Up @@ -53,7 +54,9 @@ class TracesController < ApplicationController
def index
@perfetto_sampler_installed = !!schema_class.perfetto_sampler
if @perfetto_sampler_installed
@traces = schema_class.perfetto_sampler.traces(first: 50, after: params["after"])
@last = params[:last]&.to_i || 50
@before = params[:before]&.to_i
@traces = schema_class.perfetto_sampler.traces(last: @last, before: @before)
end
end

Expand All @@ -66,6 +69,11 @@ def destroy
schema_class.perfetto_sampler.delete_trace(params[:id])
head :no_content
end

def destroy_all
schema_class.perfetto_sampler.delete_all_traces
head :no_content
end
end

class StaticsController < ApplicationController
Expand Down Expand Up @@ -96,5 +104,7 @@ def show
end
end


# Rails expects the engine to be called `Graphql::Dashboard`,
# but `GraphQL::Dashboard` is consistent with this gem's naming.
# So define both constants to refer to the same class.
GraphQL::Dashboard = Graphql::Dashboard
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@
<tr>
<td><%= trace.operation_name %></td>
<td><%= trace.duration_ms.round(2) %></td>
<td><%= trace.timestamp %></td>
<td><%= Time.at(trace.timestamp / 1000.0).strftime("%Y-%m-%d %H:%M:%S.%L") %></td>
<td><%= link_to "View ↗", "#", data: { perfetto_open: trace.operation_name, perfetto_path: "#{graphql_dashboard.traces_path}/#{trace.id}" } %></td>
<td><%= link_to "Delete", "#", data: { perfetto_delete: "#{graphql_dashboard.traces_path}/#{trace.id}" }, class: "text-danger" %></td>
</tr>
<% end %>
</tbody>
</table>
<% if @last && @traces.size >= @last %>
<%= link_to("Previous >", graphql_dashboard.traces_path(last: @last, before: @traces.last.timestamp), class: "btn btn-outline-primary") %>
<% end %>
</div>
</div>
<% end %>
8 changes: 4 additions & 4 deletions lib/graphql/tracing/perfetto_sampler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ def save_trace(operation_name, duration_ms, timestamp, trace_data)
@storage.save_trace(operation_name, duration_ms, timestamp, trace_data)
end

# @param first [Integer]
# @param after [String] Timestamp
# @param last [Integer]
# @param before [Integer] Timestamp in milliseconds since epoch
# @return [Enumerable<StoredTrace>]
def traces(first: nil, after: nil)
@storage.traces(first: first, after: after)
def traces(last: nil, before: nil)
@storage.traces(last: last, before: before)
end

# @return [StoredTrace, nil]
Expand Down
12 changes: 10 additions & 2 deletions lib/graphql/tracing/perfetto_sampler/memory_backend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@ def initialize
@traces = {}
end

def traces(first:, after:)
@traces.values
def traces(last:, before:)
page = []
@traces.values.reverse_each do |trace|
if page.size == last
break
elsif before.nil? || trace.timestamp < before
page << trace
end
end
page
end

def find_trace(id)
Expand Down
6 changes: 4 additions & 2 deletions lib/graphql/tracing/perfetto_trace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,10 @@ def end_execute_multiplex(m)
)
unsubscribe_from_active_support_notifications
if @save_trace_mode && m.context[:trace_mode] == @save_trace_mode
duration_ms = (Time.now.to_f - @begin_time.to_f) * 1000
m.schema.perfetto_sampler.save_trace(@operation_name, duration_ms, @begin_time, Trace.encode(Trace.new(packet: @packets)))
begin_ts = (@begin_time.to_f * 1000).round
end_ts = (Time.now.to_f * 1000).round
duration_ms = end_ts - begin_ts
m.schema.perfetto_sampler.save_trace(@operation_name, duration_ms, begin_ts, Trace.encode(Trace.new(packet: @packets)))
end
super
end
Expand Down
17 changes: 17 additions & 0 deletions spec/dummy/test/controllers/dashboard/landings_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true
require "test_helper"

class DashboardLandingsControllerTest < ActionDispatch::IntegrationTest
def test_it_shows_a_landing_page
get graphql_dashboard.root_path
assert_includes response.body, "Welcome to the GraphQL-Ruby Dashboard"
end

def test_it_shows_version_and_schema_info
get graphql_dashboard.root_path
assert_includes response.body, "GraphQL-Ruby v#{GraphQL::VERSION}"
assert_includes response.body, "<code>DummySchema</code>"
get graphql_dashboard.root_path, params: { schema: "NotInstalledSchema" }
assert_includes response.body, "<code>NotInstalledSchema</code>"
end
end
22 changes: 22 additions & 0 deletions spec/dummy/test/controllers/dashboard/statics_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true
require "test_helper"

class DashboardStaticsControllerTest < ActionDispatch::IntegrationTest
def test_it_serves_assets
get graphql_dashboard.static_path("dashboard.css")
assert_includes response.body, "#header-icon {"
assert_equal response.headers["Cache-Control"], "max-age=31556952, public"
end

def test_it_responds_404_for_others
get graphql_dashboard.static_path("other.rb")
assert_equal 404, response.status

assert_raises ActionController::UrlGenerationError do
graphql_dashboard.static_path("invalid~char.js")
end

get graphql_dashboard.static_path("invalid-char.js").sub("-char", "~char")
assert_equal 404, response.status
end
end
42 changes: 39 additions & 3 deletions spec/dummy/test/controllers/dashboard/traces_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
require "test_helper"

class DashboardTracesControllerTest < ActionDispatch::IntegrationTest
def teardown
DummySchema.perfetto_sampler.delete_all_traces
end

def test_it_renders_not_installed
get graphql_dashboard.traces_path, params: { schema: "NotInstalledSchema" }
assert_includes response.body, "Traces aren't installed yet"
Expand All @@ -15,7 +19,36 @@ def test_it_renders_blank_state
end

def test_it_renders_trace_listing_with_pagination
skip :TODO
20.times do |n|
sleep 0.05
DummySchema.execute("query Query#{n} { str }", context: { trace_mode: :perfetto_sample })
end
assert_equal 20, DummySchema.perfetto_sampler.traces.size

get graphql_dashboard.traces_path, params: { last: 10 }

assert_includes response.body, "Query19"
assert_includes response.body, "Query10"
refute_includes response.body, "Query9"
last_trace = DummySchema.perfetto_sampler.traces[9]
last_ts = last_trace.timestamp
assert_includes response.body, "<td>#{Time.at(last_ts / 1000.0).strftime("%Y-%m-%d %H:%M:%S.%L")}</td>"
assert_includes response.body, "<a class=\"btn btn-outline-primary\" href=\"/dash/traces?before=#{last_ts}&amp;last=10\">Previous &gt;</a>"
get graphql_dashboard.traces_path, params: { last: 10, before: last_ts }
assert_includes response.body, "Query9"
assert_includes response.body, "Query0"
refute_includes response.body, "Query10"
very_last_trace = DummySchema.perfetto_sampler.traces.last
very_last_ts = very_last_trace.timestamp
very_last_td = "<td>#{Time.at(very_last_ts / 1000.0).strftime("%Y-%m-%d %H:%M:%S.%L")}</td>"
assert_includes response.body, very_last_td
very_last_previous_link = "<a class=\"btn btn-outline-primary\" href=\"/dash/traces?before=#{very_last_ts}&amp;last=10\">Previous &gt;</a>"
assert_includes response.body, very_last_previous_link

# Go beyond last trace:
get graphql_dashboard.traces_path, params: { last: 11, before: last_ts }
assert_includes response.body, very_last_td
refute_includes response.body, very_last_previous_link
end

def test_it_deletes_one_trace
Expand All @@ -26,7 +59,10 @@ def test_it_deletes_one_trace
assert_equal 0, DummySchema.perfetto_sampler.traces.size
end

def test_it_delets_all_traces
skip :TODO
def test_it_deletes_all_traces
DummySchema.execute("{ str }", context: { trace_mode: :perfetto_sample })
assert_equal 1, DummySchema.perfetto_sampler.traces.size
delete graphql_dashboard.traces_delete_all_path
assert_equal 0, DummySchema.perfetto_sampler.traces.size
end
end

0 comments on commit 86b526e

Please sign in to comment.