Module: Hecks::Forms::QueryFormRenderer

Defined in:
lib/hecks/forms/query_form_renderer.rb

Overview

Query -> the page body for its GET view — the working half of what query_form.bluebook names (see docs/command-form-and-query-form- bluebook.md): a canonical, shareable link (queries are GETs — every one of them is already a URL, so this says so up front rather than hiding that behind a form); a row of ready-made links for any closed-set parameter, since a caller filtering by an enum should not have to fill in a form to get a link they could just click; a filter FORM for everything else, which — being a GET form — produces exactly the same kind of link on submit; and the results table once a request actually supplies parameters.

Class Method Summary collapse

Class Method Details

.badges(query) ⇒ Object



46
47
48
49
50
# File 'lib/hecks/forms/query_form_renderer.rb', line 46

def self.badges(query)
  parts = []
  parts << %(<span class="badge">limit #{Escape.html(query.limit.to_h[:value])}</span>) if query.limit
  parts.join
end


52
53
54
55
56
57
58
59
# File 'lib/hecks/forms/query_form_renderer.rb', line 52

def self.canonical_link(action, fields)
  paths = Params.paths(fields)
  template = paths.empty? ? action : "#{action}?#{paths.map { |path| "#{path}={#{path}}" }.join('&')}"
  <<~HTML
    <p class="help">Every query is a plain GET — this exact URL is bookmarkable, linkable from a dashboard, curlable, whatever a feature needs:</p>
    <div class="link-row"><code id="canonical-link">#{Escape.html(template)}</code><button type="button" class="copy" data-copy="#canonical-link">copy</button></div>
  HTML
end

.error_banner(error) ⇒ Object



89
90
91
92
93
# File 'lib/hecks/forms/query_form_renderer.rb', line 89

def self.error_banner(error)
  return "" unless error

  %(<div class="error-banner" role="alert"><p><strong>#{Escape.html(error.class.name.split('::').last)}</strong> — #{Escape.html(error.message)}</p></div>)
end

.filter_form(action, fields, params, reference_options) ⇒ Object



78
79
80
81
82
83
84
85
86
87
# File 'lib/hecks/forms/query_form_renderer.rb', line 78

def self.filter_form(action, fields, params, reference_options)
  return "" if fields.empty?

  <<~HTML
    <form method="get" action="#{Escape.attr(action)}">
      #{fields.map { |f| FieldRenderer.render(f, values: params, reference_options: reference_options) }.join}
      <div class="actions"><button type="submit">Run query</button></div>
    </form>
  HTML
end

.header(domain, aggregate, query) ⇒ Object



38
39
40
41
42
43
44
# File 'lib/hecks/forms/query_form_renderer.rb', line 38

def self.header(domain, aggregate, query)
  <<~HTML
    <h1>#{Escape.html("#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}")}</h1>
    #{query.description ? %(<p class="goal">#{Escape.html(query.description)}</p>) : ''}
    #{badges(query)}
  HTML
end

.inspect_panel(domain, aggregate, query, fields) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/hecks/forms/query_form_renderer.rb', line 101

def self.inspect_panel(domain, aggregate, query, fields)
  verb = "#{domain}::#{aggregate.hecks_name}.#{query.hecks_name}"
  paths = Params.paths(fields)
  <<~HTML
    <details class="inspect">
      <summary>Inspect — #{Escape.html(verb)}</summary>
      <p>Parameters: #{paths.empty? ? '<em>none</em>' : paths.map { |p| "<code>#{Escape.html(p)}</code>" }.join(', ')}</p>
      <pre>#{Escape.html(JSON.pretty_generate(query.to_h))}</pre>
    </details>
  HTML
end

The one enum-shaped parameter (if there is one) rendered as literal, clickable links — no form to fill in for "show me the suspended ones". Capped at the FIRST closed-set field on purpose: a second one would mean a cross product of links, which reads as noise rather than help. A query with more than one is still fully reachable through the filter form below.



67
68
69
70
71
72
73
74
75
76
# File 'lib/hecks/forms/query_form_renderer.rb', line 67

def self.quick_links(action, fields)
  field = fields.find { |f| %i[select radio].include?(f.kind) }
  return "" unless field

  links = field.options.map do |value, label|
    href = "#{action}?#{field.path}=#{URI.encode_www_form_component(value)}"
    %(<a href="#{Escape.attr(href)}">#{Escape.html(field.label)}: #{Escape.html(label)}</a>)
  end
  %(<div class="example-links">#{links.join}</div>)
end

.render(registry:, domain:, aggregate:, query:, action:, params: {}, results: nil, error: nil) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/hecks/forms/query_form_renderer.rb', line 23

def self.render(registry:, domain:, aggregate:, query:, action:, params: {}, results: nil, error: nil)
  fields = query.attributes.map { |a| FieldShape.resolve(a, aggregate: aggregate) }
  reference_options = ReferenceOptions.collect(registry, domain, fields)

  <<~HTML
    #{header(domain, aggregate, query)}
    #{canonical_link(action, fields)}
    #{quick_links(action, fields)}
    #{filter_form(action, fields, params, reference_options)}
    #{error_banner(error)}
    #{results_section(aggregate, results, domain)}
    #{inspect_panel(domain, aggregate, query, fields)}
  HTML
end

.results_section(aggregate, results, domain) ⇒ Object



95
96
97
98
99
# File 'lib/hecks/forms/query_form_renderer.rb', line 95

def self.results_section(aggregate, results, domain)
  return "" unless results

  "<h2>Results (#{results.size})</h2>#{RecordTable.render(aggregate, results, domain: domain)}"
end