Skip to content

Commit e0bc033

Browse files
authored
Merge pull request #4783 from nhsuk/ops-tools
Ops tools
2 parents 6f79d82 + 73a5d1e commit e0bc033

34 files changed

+3439
-66
lines changed

app/components/app_timeline_component.rb

Lines changed: 104 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,37 @@ class AppTimelineComponent < ViewComponent::Base
66
<% @items.each do |item| %>
77
<% next if item.blank? %>
88
9-
<li class="app-timeline__item <%= 'app-timeline__item--past' if item[:is_past_item] %>">
10-
<% if item[:active] || item[:is_past_item] %>
11-
<svg class="app-timeline__badge" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28">
12-
<circle cx="14" cy="14" r="13" fill="#005EB8"/>
13-
</svg>
14-
<% else %>
15-
<svg class="app-timeline__badge app-timeline__badge--small" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
16-
<circle cx="7" cy="7" r="6" fill="white" stroke="#AEB7BD" stroke-width="2"/>
17-
</svg>
18-
<% end %>
19-
20-
<div class="app-timeline__content">
21-
<h3 class="app-timeline__header <%= 'nhsuk-u-font-weight-bold' if item[:active] %>">
22-
<%= item[:heading_text].html_safe %>
9+
<% if item[:type] == :section_header %>
10+
<li>
11+
<h3 class="nhsuk-u-margin-top-2">
12+
<%= content_tag(:strong, item[:date]) %>
2313
</h3>
24-
25-
<% if item[:description].present? %>
26-
<p class="app-timeline__description"><%= item[:description].html_safe %></p>
14+
</li>
15+
<% else %>
16+
<li class="app-timeline__item <%= 'app-timeline__item--past' if item[:is_past_item] %>">
17+
<% if item[:active] || item[:is_past_item] %>
18+
<svg class="app-timeline__badge" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28">
19+
<circle cx="14" cy="14" r="13" fill="#005EB8"/>
20+
</svg>
21+
<% else %>
22+
<svg class="app-timeline__badge app-timeline__badge--small" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
23+
<circle cx="7" cy="7" r="6" fill="white" stroke="#AEB7BD" stroke-width="2"/>
24+
</svg>
2725
<% end %>
28-
</div>
29-
</li>
26+
27+
<div class="app-timeline__content">
28+
<h3 class="app-timeline__header <%= 'nhsuk-u-font-weight-bold' if item[:active] %>">
29+
<%= format_heading(item).html_safe %>
30+
</h3>
31+
32+
<% if item[:description].present? || item[:details].present? %>
33+
<div class="app-timeline__description">
34+
<%= format_description(item).html_safe %>
35+
</div>
36+
<% end %>
37+
</div>
38+
</li>
39+
<% end %>
3040
<% end %>
3141
</ul>
3242
ERB
@@ -38,4 +48,79 @@ def initialize(items)
3848
def render?
3949
@items.present?
4050
end
51+
52+
private
53+
54+
EVENT_COLOUR_MAPPING = {
55+
"CohortImport" => "blue",
56+
"ClassImport" => "purple",
57+
"PatientSession" => "green",
58+
"Consent" => "yellow",
59+
"Triage" => "red",
60+
"VaccinationRecord" => "grey",
61+
"SchoolMove" => "orange",
62+
"SchoolMoveLogEntry" => "pink"
63+
}.freeze
64+
65+
def format_heading(item)
66+
if item[:type] && item[:created_at]
67+
time = format_time(item[:created_at])
68+
event_tag =
69+
govuk_tag(
70+
text: item[:event_type],
71+
colour: tag_colour(item[:event_type])
72+
)
73+
"#{event_tag} at #{time}"
74+
elsif item[:heading_text]
75+
item[:heading_text]
76+
end
77+
end
78+
79+
def format_description(item)
80+
if item[:details].present?
81+
formatted_details = format_event_details(item[:details])
82+
id_info =
83+
item[:id] ? "<p class=\"timeline__byline\">id: #{item[:id]}</p>" : ""
84+
"#{id_info}#{formatted_details}"
85+
elsif item[:description].present?
86+
item[:description]
87+
end
88+
end
89+
90+
def format_time(date_time)
91+
date_time.strftime("%H:%M:%S")
92+
end
93+
94+
def format_event_details(details)
95+
return "" if details.blank?
96+
97+
if details.is_a?(Hash)
98+
formatted =
99+
details.map do |key, value|
100+
if value.is_a?(Hash)
101+
nested =
102+
value.map do |sub_key, sub_value|
103+
nested_value =
104+
sub_value.is_a?(String) ? sub_value : sub_value.inspect
105+
"<div style='margin-left: 1em;'><strong>#{sub_key}:</strong> #{nested_value}</div>"
106+
end
107+
"<div><strong>#{key}:</strong>#{nested.join}</div>"
108+
else
109+
"<div><strong>#{key}:</strong> #{value}</div>"
110+
end
111+
end
112+
formatted.join
113+
else
114+
details.to_s
115+
end
116+
end
117+
118+
def tag_colour(type)
119+
return "light-blue" if type.end_with?("-Audit")
120+
EVENT_COLOUR_MAPPING.fetch(type, "grey")
121+
end
122+
123+
def govuk_tag(text:, colour:)
124+
helpers.govuk_tag(text: text, colour: colour)
125+
end
41126
end
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
<%= render AppCardComponent.new(filters: true) do |card| %>
2+
<% card.with_heading { "Customise timeline" } %>
3+
<%= form_with url: url,
4+
method: :get,
5+
data: { module: "autosubmit",
6+
turbo: "true",
7+
turbo_action: "replace" },
8+
builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f| %>
9+
10+
<div class="nhsuk-u-margin-bottom-4">
11+
<% if pii_access_allowed %>
12+
<%= f.govuk_check_box :show_pii, true,
13+
label: { text: "Show PII" },
14+
checked: show_pii,
15+
"data-autosubmit-target": "field",
16+
"data-action": "autosubmit#submit",
17+
"data-turbo-permanent": "true" %>
18+
<% else %>
19+
<%= f.govuk_check_box :show_pii, true,
20+
label: { text: "Show PII (not allowed for this user)" },
21+
checked: false,
22+
"disabled": "true" %>
23+
<% end %>
24+
</div>
25+
26+
27+
<%= f.govuk_fieldset legend: { text: "Events to display:", size: "s" } do %>
28+
<% event_options.keys.each do |value| %>
29+
<%= f.govuk_check_boxes_fieldset :event_names, legend: { hidden: true } do %>
30+
<%= f.govuk_check_box :event_names,
31+
value,
32+
label: { text: value.to_s.humanize },
33+
checked: value.to_s.in?(params[:event_names] || event_options.keys.map(&:to_s)),
34+
"data-autosubmit-target": "field",
35+
"data-action": "autosubmit#submit",
36+
"data-turbo-permanent": "true" %>
37+
38+
<% available_fields = timeline_fields[value.to_sym] || [] %>
39+
<% if available_fields.any? && value.to_s.in?(params[:event_names]) %>
40+
<div class="nhsuk-checkboxes__conditional nhsuk-u-margin-bottom-2">
41+
<% available_fields.each do |field| %>
42+
<%= f.govuk_check_box "detail_config[#{value}]",
43+
field,
44+
small: true,
45+
label: { text: field },
46+
checked: field.to_s.in?(params.dig("detail_config", value) || []),
47+
"data-autosubmit-target": "field",
48+
"data-action": "autosubmit#submit",
49+
"data-turbo-permanent": "true" %>
50+
<% end %>
51+
</div>
52+
<% end %>
53+
<% end %>
54+
<% end %>
55+
56+
<%= f.govuk_check_boxes_fieldset :audit_config, legend: { hidden: true } do %>
57+
<%= f.govuk_check_box :event_names, "audits",
58+
label: { text: "Audits" },
59+
checked: "audits".in?(params[:event_names]),
60+
"data-autosubmit-target": "field",
61+
"data-action": "autosubmit#submit",
62+
"data-turbo-permanent": "true" %>
63+
<% if "audits".in?(params[:event_names]) %>
64+
<div class="nhsuk-checkboxes__conditional nhsuk-u-margin-bottom-2">
65+
<%= f.govuk_check_box "audit_config[include_associated_audits]", true, false,
66+
multiple: false,
67+
label: { text: "include associated audits" },
68+
checked: params.dig(:audit_config, :include_associated_audits) == "true",
69+
"data-autosubmit-target": "field",
70+
"data-action": "autosubmit#submit",
71+
"data-turbo-permanent": "true" %>
72+
73+
<%= f.govuk_check_box "audit_config[include_filtered_audit_changes]", true, false,
74+
multiple: false,
75+
label: { text: "include filtered audit changes" },
76+
checked: params.dig(:audit_config, :include_filtered_audit_changes) == "true",
77+
"data-autosubmit-target": "field",
78+
"data-action": "autosubmit#submit",
79+
"data-turbo-permanent": "true" %>
80+
</div>
81+
<% end %>
82+
<% end %>
83+
84+
<%= f.govuk_check_box :event_names, "org_cohort_imports",
85+
label: { text: "Cohort Imports for Team #{teams.join(",")} excluding patient" },
86+
checked: "org_cohort_imports".in?(params[:event_names]),
87+
"data-autosubmit-target": "field",
88+
"data-action": "autosubmit#submit",
89+
"data-turbo-permanent": "true" %>
90+
91+
<% (additional_class_imports).each do |location_id, import_ids| %>
92+
<%= f.govuk_check_box :event_names, "add_class_imports_#{location_id}",
93+
label: { text: "Class Imports for Location-#{location_id} excluding Patient" },
94+
checked: "add_class_imports_#{location_id}".in?(params[:event_names]),
95+
"data-autosubmit-target": "field",
96+
"data-action": "autosubmit#submit",
97+
"data-turbo-permanent": "true" %>
98+
<% end %>
99+
100+
101+
<%= f.govuk_radio_buttons_fieldset :compare_option, legend: { text: "Compare with another patient:", size: "s" } do %>
102+
<%= f.govuk_radio_button :compare_option,
103+
nil,
104+
label: { text: "Do not compare" },
105+
checked: params[:compare_option].blank?,
106+
"data-autosubmit-target": "field",
107+
"data-action": "autosubmit#submit",
108+
"data-turbo-permanent": "true" %>
109+
110+
<% if class_imports.present? %>
111+
<%= f.govuk_radio_button :compare_option,
112+
"class_import",
113+
label: { text: "From a Class Import" },
114+
checked: params[:compare_option] == "class_import",
115+
"data-autosubmit-target": "field",
116+
"data-action": "autosubmit#submit",
117+
"data-turbo-permanent": "true" do %>
118+
<% class_imports.each do |import| %>
119+
<%= f.govuk_radio_button :compare_option_class_import,
120+
import,
121+
label: { text: "ClassImport-#{import}" },
122+
checked: params[:compare_option_class_import].to_s == import.to_s,
123+
"data-autosubmit-target": "field",
124+
"data-action": "autosubmit#submit",
125+
"data-turbo-permanent": "true" %>
126+
<% end %>
127+
<% end %>
128+
<% end %>
129+
130+
<% if cohort_imports.present? %>
131+
<%= f.govuk_radio_button :compare_option,
132+
"cohort_import",
133+
label: { text: "From a Cohort Import" },
134+
checked: params[:compare_option] == "cohort_import",
135+
"data-autosubmit-target": "field",
136+
"data-action": "autosubmit#submit",
137+
"data-turbo-permanent": "true" do %>
138+
<% cohort_imports.each do |import| %>
139+
<%= f.govuk_radio_button :compare_option_cohort_import,
140+
import,
141+
label: { text: "CohortImport-#{import}" },
142+
checked: params[:compare_option_cohort_import].to_s == import.to_s,
143+
"data-autosubmit-target": "field",
144+
"data-action": "autosubmit#submit",
145+
"data-turbo-permanent": "true" %>
146+
<% end %>
147+
<% end %>
148+
<% end %>
149+
150+
<% if sessions.present? %>
151+
<%= f.govuk_radio_button :compare_option,
152+
"session",
153+
label: { text: "In a Session" },
154+
checked: params[:compare_option] == "session" do %>
155+
<% sessions.each do |session| %>
156+
<%= f.govuk_radio_button :compare_option_session,
157+
session,
158+
label: { text: "Session-#{session}" },
159+
checked: params[:compare_option_session].to_s == session.to_s && params[:compare_option] == "session",
160+
"data-autosubmit-target": "field",
161+
"data-action": "autosubmit#submit",
162+
"data-turbo-permanent": "true" %>
163+
<% end %>
164+
<% end %>
165+
<% end %>
166+
167+
<%= f.govuk_radio_button :compare_option,
168+
"manual_entry",
169+
label: { text: "With a specific Patient ID" },
170+
checked: params[:compare_option] == "manual_entry" do %>
171+
<%= f.govuk_number_field :manual_patient_id,
172+
label: { hidden: true },
173+
width: 10,
174+
"data-autosubmit-target": "field",
175+
"data-action": "autosubmit#submit",
176+
"data-turbo-permanent": "true" %>
177+
<% end %>
178+
<% end %>
179+
180+
<%= helpers.govuk_button_link_to "Reset filters",
181+
reset_url,
182+
class: "govuk-button govuk-button--secondary nhsuk-u-display-block app-button--small",
183+
secondary: true,
184+
"data-autosubmit-target": "reset",
185+
"data-action": "autosubmit#submit",
186+
"data-turbo-permanent": "true" %>
187+
<%= f.govuk_submit "Filter" %>
188+
<% end %>
189+
<% end %>
190+
<% end %>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
class AppTimelineFilterComponent < ViewComponent::Base
4+
def initialize(
5+
url:,
6+
patient:,
7+
teams:,
8+
event_options:,
9+
timeline_fields:,
10+
additional_class_imports:,
11+
class_imports:,
12+
cohort_imports:,
13+
sessions:,
14+
reset_url:,
15+
show_pii:,
16+
pii_access_allowed:
17+
)
18+
@url = url
19+
@patient = patient
20+
@teams = teams.map(&:id)
21+
@event_options = event_options
22+
@timeline_fields = timeline_fields
23+
@additional_class_imports = additional_class_imports
24+
@class_imports = class_imports
25+
@cohort_imports = cohort_imports
26+
@sessions = sessions
27+
@reset_url = reset_url
28+
@show_pii = show_pii
29+
@pii_access_allowed = pii_access_allowed
30+
end
31+
32+
attr_reader :url,
33+
:reset_url,
34+
:patient,
35+
:teams,
36+
:event_options,
37+
:timeline_fields,
38+
:additional_class_imports,
39+
:class_imports,
40+
:cohort_imports,
41+
:sessions,
42+
:show_pii,
43+
:pii_access_allowed
44+
end

0 commit comments

Comments
 (0)