Module: PgInsights::ApplicationHelper

Defined in:
app/helpers/pg_insights/application_helper.rb

Instance Method Summary collapse

Instance Method Details

#render_plan_node(node, level = 0) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'app/helpers/pg_insights/application_helper.rb', line 3

def render_plan_node(node, level = 0)
  return "".html_safe unless node

  # Build the node display text
  node_text = []
  node_text << "#{node['Node Type']}"
  node_text << "on #{node['Relation Name']}" if node["Relation Name"]

  # Cost information
  cost_info = []
  if node["Startup Cost"] && node["Total Cost"]
    cost_info << "cost=#{node['Startup Cost']}..#{node['Total Cost']}"
  elsif node["Total Cost"]
    cost_info << "cost=#{node['Total Cost']}"
  end

  if node["Plan Rows"]
    cost_info << "rows=#{node['Plan Rows']}"
  end

  if node["Plan Width"]
    cost_info << "width=#{node['Plan Width']}"
  end

  node_text << "(#{cost_info.join(' ')})" if cost_info.any?

  # Actual execution stats
  actual_info = []
  if node["Actual Total Time"]
    actual_info << "actual time=#{node['Actual Total Time']}ms"
  end
  if node["Actual Rows"]
    actual_info << "rows=#{node['Actual Rows']}"
  end
  if node["Actual Loops"]
    actual_info << "loops=#{node['Actual Loops']}"
  end

  node_text << "[#{actual_info.join(' ')}]" if actual_info.any?

  # Build the HTML for this node
  indent = "  " * level
  prefix = level == 0 ? "" : "├─ "

  result = (:div, class: "plan-node level-#{level}") do
    content = (:span, "#{indent}#{prefix}", class: "plan-indent")
    content += (:span, node_text.join(" "), class: "plan-node-text")

    # Add filter information if present
    if node["Filter"]
      content += (:div, class: "plan-filter") do
        (:span, "#{indent}    Filter: #{node['Filter']}", class: "filter-text")
      end
    end

    # Add index condition if present
    if node["Index Cond"]
      content += (:div, class: "plan-condition") do
        (:span, "#{indent}    Index Cond: #{node['Index Cond']}", class: "condition-text")
      end
    end

    # Add other important fields
    %w[Sort Key Hash Cond Join Filter].each do |field|
      if node[field]
        content += (:div, class: "plan-detail") do
          (:span, "#{indent}    #{field}: #{node[field]}", class: "detail-text")
        end
      end
    end

    content
  end

  # Recursively render child nodes
  if node["Plans"] && node["Plans"].any?
    node["Plans"].each do |child_plan|
      result += render_plan_node(child_plan, level + 1)
    end
  end

  result
end

#render_plan_node_modern(node, level = 0) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'app/helpers/pg_insights/application_helper.rb', line 87

def render_plan_node_modern(node, level = 0)
  return "".html_safe unless node

  # Get node type and determine color/icon
  node_type = node["Node Type"] || "Unknown"
  operation_class = get_operation_class(node_type)
  operation_icon = get_operation_icon(node_type)

  # Build timing info
  timing_info = []
  if node["Actual Total Time"]
    timing_info << "#{node['Actual Total Time']}ms"
  end
  if node["Actual Rows"]
    timing_info << "#{node['Actual Rows']} rows"
  end

  # Build cost info
  cost_info = node["Total Cost"] ? node["Total Cost"].round(2) : nil

  result = (:div, class: "plan-node-modern level-#{level} #{operation_class}") do
    content = ""

    # Tree connector
    if level > 0
      content += (:div, class: "tree-connector") do
        "├─".html_safe
      end
    end

    # Node card
    content += (:div, class: "node-card") do
      card_content = ""

      # Header row
      card_content += (:div, class: "node-header") do
        header_content = (:span, operation_icon, class: "node-icon")
        header_content += (:span, node_type, class: "node-type")

        if node["Relation Name"]
          header_content += (:span, "#{node['Relation Name']}", class: "relation-name")
        end

        if timing_info.any?
          header_content += (:div, timing_info.join(" • "), class: "timing-badge")
        end

        header_content
      end

      # Details row (if present)
      details = []
      if cost_info
        details << "Cost: #{cost_info}"
      end
      if node["Filter"]
        details << "Filter: #{truncate_filter(node['Filter'])}"
      end
      if node["Sort Key"]
        details << "Sort: #{node['Sort Key']}"
      end
      if node["Hash Cond"]
        details << "Join: #{truncate_filter(node['Hash Cond'])}"
      end

      if details.any?
        card_content += (:div, class: "node-details") do
          details.map { |detail| (:span, detail, class: "detail-item") }.join(" • ").html_safe
        end
      end

      card_content.html_safe
    end

    content.html_safe
  end

  # Recursively render child nodes
  if node["Plans"] && node["Plans"].any?
    node["Plans"].each do |child_plan|
      result += render_plan_node_modern(child_plan, level + 1)
    end
  end

  result
end